Added mentor main.py
This commit is contained in:
parent
3bd002dead
commit
190e194da5
1 changed files with 252 additions and 0 deletions
252
uC/main_uC/Mentor/main.py
Normal file
252
uC/main_uC/Mentor/main.py
Normal file
|
|
@ -0,0 +1,252 @@
|
|||
# ESP32-S3 MicroPython: Modbus RTU (RS485) + DFPlayer Mini (HW UART) merged
|
||||
# RS485: UART1 TX=GPIO9 RX=GPIO10 DIR=GPIO18
|
||||
# DFPlayer: UART2 TX=GPIO40 RX=GPIO41 BUSY=GPIO42 (BUSY typically LOW while playing)
|
||||
|
||||
from machine import UART, Pin
|
||||
import time, struct
|
||||
|
||||
# =========================
|
||||
# DFPLAYER (UART2)
|
||||
# =========================
|
||||
DF_UART_ID = 2
|
||||
DF_TX_PIN = 21 # ESP32 TX -> DFPlayer RX
|
||||
DF_RX_PIN = 20 # ESP32 RX <- DFPlayer TX (optional, but harmless)
|
||||
DF_BUSY_PIN = 47 # DFPlayer BUSY -> ESP32 GPIO (usually LOW when playing)
|
||||
DF_BAUD = 9600
|
||||
|
||||
_START = 0x7E
|
||||
_VER = 0xFF
|
||||
_LEN = 0x06
|
||||
_ACK = 0x00
|
||||
_END = 0xEF
|
||||
|
||||
CMD_SET_VOLUME = 0x06
|
||||
CMD_PLAY_TRACK = 0x03
|
||||
CMD_PLAY_FOLDER = 0x0F
|
||||
CMD_STOP = 0x16
|
||||
CMD_RESET = 0x0C
|
||||
|
||||
def _checksum(cmd, p1, p2):
|
||||
s = _VER + _LEN + cmd + _ACK + p1 + p2
|
||||
c = (0 - s) & 0xFFFF
|
||||
return (c >> 8) & 0xFF, c & 0xFF
|
||||
|
||||
class DFPlayer:
|
||||
def __init__(self, uart: UART, busy_pin=None):
|
||||
self.uart = uart
|
||||
self.busy = Pin(busy_pin, Pin.IN, Pin.PULL_UP) if busy_pin is not None else None
|
||||
time.sleep_ms(800)
|
||||
|
||||
def _send(self, cmd, param=0):
|
||||
p1 = (param >> 8) & 0xFF
|
||||
p2 = param & 0xFF
|
||||
ch, cl = _checksum(cmd, p1, p2)
|
||||
frame = bytes([_START, _VER, _LEN, cmd, _ACK, p1, p2, ch, cl, _END])
|
||||
self.uart.write(frame)
|
||||
|
||||
def reset(self):
|
||||
self._send(CMD_RESET, 0)
|
||||
time.sleep_ms(1200)
|
||||
|
||||
def volume(self, level):
|
||||
level = max(0, min(30, int(level)))
|
||||
self._send(CMD_SET_VOLUME, level)
|
||||
|
||||
def stop(self):
|
||||
self._send(CMD_STOP, 0)
|
||||
|
||||
def play_track(self, track_num):
|
||||
self._send(CMD_PLAY_TRACK, int(track_num))
|
||||
|
||||
def play_folder_file(self, folder, file):
|
||||
folder = max(1, min(99, int(folder)))
|
||||
file = max(1, min(255, int(file)))
|
||||
self._send(CMD_PLAY_FOLDER, (folder << 8) | file)
|
||||
|
||||
def is_playing(self):
|
||||
if self.busy is None:
|
||||
return None
|
||||
# Typical DFPlayer: BUSY LOW = playing, HIGH = idle
|
||||
return self.busy.value() == 0
|
||||
|
||||
def wait_done(self, timeout_ms=20000):
|
||||
"""Wait for BUSY to go LOW (start) then HIGH (done)."""
|
||||
if self.busy is None:
|
||||
return False
|
||||
|
||||
t0 = time.ticks_ms()
|
||||
|
||||
# wait for playback to begin (BUSY goes LOW)
|
||||
while self.busy.value() == 1:
|
||||
if time.ticks_diff(time.ticks_ms(), t0) > 1000:
|
||||
return False
|
||||
time.sleep_ms(5)
|
||||
|
||||
# wait for playback to finish (BUSY goes HIGH)
|
||||
while self.busy.value() == 0:
|
||||
if time.ticks_diff(time.ticks_ms(), t0) > timeout_ms:
|
||||
return False
|
||||
time.sleep_ms(10)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
df_uart = UART(
|
||||
DF_UART_ID,
|
||||
baudrate=DF_BAUD,
|
||||
bits=8,
|
||||
parity=None,
|
||||
stop=1,
|
||||
tx=Pin(DF_TX_PIN),
|
||||
rx=Pin(DF_RX_PIN),
|
||||
timeout=100
|
||||
)
|
||||
player = DFPlayer(df_uart, busy_pin=DF_BUSY_PIN)
|
||||
|
||||
|
||||
# =========================
|
||||
# MODBUS RTU (RS485 UART1)
|
||||
# =========================
|
||||
MB_UART_ID = 1
|
||||
MB_TX_PIN = 45
|
||||
MB_RX_PIN = 0
|
||||
MB_BAUD = 9600
|
||||
|
||||
SLAVE_ID = 10
|
||||
|
||||
# RS485 direction pin (DE/RE tied together)
|
||||
RS485_DIR_PIN = 18
|
||||
rs485_dir = Pin(RS485_DIR_PIN, Pin.OUT)
|
||||
rs485_dir.value(0) # receive mode
|
||||
|
||||
mb_uart = UART(
|
||||
MB_UART_ID,
|
||||
baudrate=MB_BAUD,
|
||||
bits=8,
|
||||
parity=None,
|
||||
stop=1,
|
||||
tx=Pin(MB_TX_PIN),
|
||||
rx=Pin(MB_RX_PIN),
|
||||
timeout=200
|
||||
)
|
||||
|
||||
def crc16(data):
|
||||
crc = 0xFFFF
|
||||
for b in data:
|
||||
crc ^= b
|
||||
for _ in range(8):
|
||||
crc = (crc >> 1) ^ 0xA001 if (crc & 1) else (crc >> 1)
|
||||
return crc
|
||||
|
||||
def read_holding(start_reg, count):
|
||||
frame = bytearray(6)
|
||||
frame[0] = SLAVE_ID
|
||||
frame[1] = 3 # FC03
|
||||
frame[2] = start_reg >> 8
|
||||
frame[3] = start_reg & 0xFF
|
||||
frame[4] = count >> 8
|
||||
frame[5] = count & 0xFF
|
||||
|
||||
frame += struct.pack("<H", crc16(frame))
|
||||
|
||||
# ---- transmit ----
|
||||
rs485_dir.value(1)
|
||||
time.sleep_us(200)
|
||||
|
||||
mb_uart.write(frame)
|
||||
mb_uart.flush()
|
||||
|
||||
time.sleep_us(200)
|
||||
rs485_dir.value(0)
|
||||
|
||||
# ---- receive ----
|
||||
time.sleep_ms(100)
|
||||
resp = mb_uart.read()
|
||||
|
||||
if not resp or len(resp) < 5:
|
||||
return None
|
||||
|
||||
# basic sanity
|
||||
if resp[0] != SLAVE_ID or resp[1] != 3:
|
||||
return None
|
||||
|
||||
byte_count = resp[2]
|
||||
if len(resp) < (3 + byte_count + 2):
|
||||
return None
|
||||
|
||||
regs = []
|
||||
for i in range(0, byte_count, 2):
|
||||
regs.append((resp[3+i] << 8) | resp[4+i])
|
||||
return regs
|
||||
|
||||
|
||||
# =========================
|
||||
# APP LOGIC
|
||||
# =========================
|
||||
RunOnceOver = False
|
||||
RunOnceOver1 = False
|
||||
|
||||
def decode_env(regs):
|
||||
global RunOnceOver
|
||||
global RunOnceOver1
|
||||
|
||||
counter = regs[0]
|
||||
status = regs[1]
|
||||
mode = regs[2]
|
||||
errors = regs[3]
|
||||
|
||||
temp = struct.unpack(">h", struct.pack(">H", regs[4]))[0] / 100
|
||||
rh = regs[5] / 100
|
||||
pressure = regs[6] / 10
|
||||
iaq = regs[7]
|
||||
|
||||
print("------------")
|
||||
print("Counter :", counter)
|
||||
print("Status :", bin(status))
|
||||
print("Mode :", "SIM" if mode else "LIVE")
|
||||
print("Errors :", errors)
|
||||
print("Temp :", temp, "C")
|
||||
print("RH :", rh, "%")
|
||||
print("Press :", pressure, "hPa")
|
||||
print("IAQ :", iaq)
|
||||
print("------------\n")
|
||||
|
||||
# Example trigger: if IAQ > 10, play one file once
|
||||
if iaq > 100 and not RunOnceOver:
|
||||
player.volume(50)
|
||||
time.sleep_ms(50)
|
||||
print("play")
|
||||
# folder=2, file=105 must exist as numeric file in /02/
|
||||
player.play_folder_file(2, 105)
|
||||
|
||||
ok = player.wait_done(timeout_ms=25000)
|
||||
time.sleep(1)
|
||||
print("Audio done:", ok)
|
||||
|
||||
RunOnceOver = True
|
||||
RunOnceOver1 = False
|
||||
|
||||
if iaq < 100 and not RunOnceOver1:
|
||||
player.volume(15)
|
||||
time.sleep_ms(50)
|
||||
print("play")
|
||||
# folder=2, file=105 must exist as numeric file in /02/
|
||||
player.play_folder_file(2, 106)
|
||||
|
||||
ok = player.wait_done(timeout_ms=25000)
|
||||
time.sleep(1)
|
||||
print("Audio done:", ok)
|
||||
RunOnceOver = False
|
||||
RunOnceOver1 = True
|
||||
|
||||
# =========================
|
||||
# MAIN LOOP
|
||||
# =========================
|
||||
while True:
|
||||
#player.play_folder_file(2, 106)
|
||||
#time.sleep(5)
|
||||
regs = read_holding(0, 8)
|
||||
if regs:
|
||||
decode_env(regs)
|
||||
#time.sleep_ms(100)
|
||||
|
||||
Loading…
Reference in a new issue