diff --git a/uC/main_uC/Mentor/main.py b/uC/main_uC/Mentor/main.py new file mode 100644 index 0000000..81f84ad --- /dev/null +++ b/uC/main_uC/Mentor/main.py @@ -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", 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) +