from machine import Pin, UART import time import network import socket # ============================================================ # AUDIO / GPIO # ============================================================ UART_ID = 1 UART_TX_GPIO = 8 UART_RX_GPIO = 9 GPIO_CRY = 2 GPIO_WHIM = 3 GPIO_GIG = 4 GPIO_STOP = 5 GPIO_POOP = 10 # Optional: Status-LED led = Pin("LED", Pin.OUT) # ============================================================ # WLAN / UDP # ============================================================ AP_SSID = "BABY_PUPPE" AP_PASSWORD = "TheaterBaby2026" UDP_PORT = 4210 SHARED_SECRET = "babysecret" REMOTE_TIMEOUT_MS = 5000 # Funkverbindung gilt als "aktiv", wenn in den letzten 5 s ein PING kam # ============================================================ # DFPLAYER # ============================================================ uart = UART( UART_ID, baudrate=9600, bits=8, parity=None, stop=1, tx=Pin(UART_TX_GPIO), rx=Pin(UART_RX_GPIO), ) def dfplayer_cmd(cmd, param=0, feedback=False): start = 0x7E ver = 0xFF ln = 0x06 fb = 0x01 if feedback else 0x00 ph = (param >> 8) & 0xFF pl = param & 0xFF checksum = -(ver + ln + cmd + fb + ph + pl) & 0xFFFF ch = (checksum >> 8) & 0xFF cl = checksum & 0xFF packet = bytes([start, ver, ln, cmd, fb, ph, pl, ch, cl, 0xEF]) uart.write(packet) def df_set_volume(vol): vol = max(0, min(30, vol)) dfplayer_cmd(0x06, vol) def df_play_mp3(track_no): dfplayer_cmd(0x12, track_no) def df_stop(): dfplayer_cmd(0x16, 0) time.sleep_ms(800) df_set_volume(30) # ============================================================ # BUTTONS # ============================================================ btn_cry = Pin(GPIO_CRY, Pin.IN, Pin.PULL_UP) btn_whim = Pin(GPIO_WHIM, Pin.IN, Pin.PULL_UP) btn_gig = Pin(GPIO_GIG, Pin.IN, Pin.PULL_UP) btn_stop = Pin(GPIO_STOP, Pin.IN, Pin.PULL_UP) btn_poop = Pin(GPIO_POOP, Pin.IN, Pin.PULL_UP) DEBOUNCE_MS = 80 last_ms = { "cry": 0, "whim": 0, "gig": 0, "stop": 0, "poop": 0, } def is_pressed(pin: Pin) -> bool: return pin.value() == 0 def allow(key: str) -> bool: now = time.ticks_ms() if time.ticks_diff(now, last_ms[key]) > DEBOUNCE_MS: last_ms[key] = now return True return False # ============================================================ # COMMAND HANDLING # ============================================================ last_seq = -1 last_remote_seen_ms = 0 def execute_command(cmd: str): if cmd == "STOP": df_stop() return if cmd == "CRY": df_play_mp3(1) # /mp3/0001.mp3 elif cmd == "WHIM": df_play_mp3(2) # /mp3/0002.mp3 elif cmd == "POOP": df_play_mp3(3) # /mp3/0003.mp3 elif cmd == "GIG": df_play_mp3(4) # /mp3/0004.mp3 def handle_udp_message(msg: str): global last_seq, last_remote_seen_ms # Format: SECRET|SEQ|CMD parts = msg.strip().split("|") if len(parts) != 3: return secret, seq_text, cmd = parts if secret != SHARED_SECRET: return try: seq = int(seq_text) except ValueError: return # Heartbeat immer akzeptieren, wenn seq neu genug ist # Sequenznummern müssen monoton steigen if seq <= last_seq: return last_seq = seq last_remote_seen_ms = time.ticks_ms() if cmd == "PING": return execute_command(cmd) def remote_alive() -> bool: return time.ticks_diff(time.ticks_ms(), last_remote_seen_ms) < REMOTE_TIMEOUT_MS # ============================================================ # WLAN ACCESS POINT # ============================================================ ap = network.WLAN(network.AP_IF) ap.active(True) ap.config(essid=AP_SSID, password=AP_PASSWORD, authmode=network.AUTH_WPA_WPA2_PSK) timeout = time.ticks_add(time.ticks_ms(), 10000) while not ap.active(): if time.ticks_diff(timeout, time.ticks_ms()) <= 0: break time.sleep_ms(100) print("AP config:", ap.ifconfig()) # ============================================================ # UDP SOCKET # ============================================================ sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.bind(("0.0.0.0", UDP_PORT)) sock.setblocking(False) # ============================================================ # MAIN LOOP # ============================================================ last_led_toggle = time.ticks_ms() led_state = 0 while True: # 1) lokale STOP-Taste mit höchster Priorität if is_pressed(btn_stop) and allow("stop"): execute_command("STOP") # 2) lokale Taster if is_pressed(btn_cry) and allow("cry"): execute_command("CRY") if is_pressed(btn_whim) and allow("whim"): execute_command("WHIM") if is_pressed(btn_gig) and allow("gig"): execute_command("GIG") if is_pressed(btn_poop) and allow("poop"): execute_command("POOP") # 3) Funkbefehle try: data, addr = sock.recvfrom(128) try: msg = data.decode("utf-8") handle_udp_message(msg) except Exception: pass except OSError: pass # 4) Status-LED: # langsam blinken = kein aktiver Sender # dauerhaft an = Heartbeat vorhanden if remote_alive(): led.value(1) else: if time.ticks_diff(time.ticks_ms(), last_led_toggle) > 500: last_led_toggle = time.ticks_ms() led_state = 0 if led_state else 1 led.value(led_state) time.sleep_ms(5)