diff --git a/.idea/micropython-tools-settings.xml b/.idea/micropython-tools-settings.xml index 2850a2e..2ab3317 100644 --- a/.idea/micropython-tools-settings.xml +++ b/.idea/micropython-tools-settings.xml @@ -1,6 +1,7 @@ + diff --git a/main_doll.py b/main_doll.py new file mode 100644 index 0000000..12792b8 --- /dev/null +++ b/main_doll.py @@ -0,0 +1,224 @@ +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) \ No newline at end of file diff --git a/main.py b/main_single.py similarity index 79% rename from main.py rename to main_single.py index 0ebc49d..8ff0964 100644 --- a/main.py +++ b/main_single.py @@ -14,7 +14,7 @@ GPIO_CRY = 2 GPIO_WHIM = 3 GPIO_GIG = 4 GPIO_STOP = 5 -GPIO_PEE = 6 +GPIO_POOP = 10 # Pumpen-Ausgang (später Treiber/ULN2003 etc.) GPIO_PUMP_OUT = 7 @@ -88,10 +88,7 @@ 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_pee = Pin(GPIO_PEE, Pin.IN, Pin.PULL_UP) - -pump_out = Pin(GPIO_PUMP_OUT, Pin.OUT) -pump_out.value(0) +btn_poop = Pin(GPIO_POOP, Pin.IN, Pin.PULL_UP) # Entprellung DEBOUNCE_MS = 80 @@ -102,6 +99,7 @@ last_ms = { "gig": 0, "stop": 0, "pee": 0, + "poop": 0, } def is_pressed(pin: Pin) -> bool: @@ -115,16 +113,6 @@ def allow(key: str) -> bool: return True return False -# ============================================================ -# PIPI-LOGIK (erstmal als Impuls, bühnenfreundlicher als 'solange gedrückt') -# ============================================================ -PIPI_PULSE_MS = 900 # Dauer Pumpen-Impuls (später anpassen) -pipi_until = 0 - -def pipi_trigger(): - global pipi_until - pipi_until = time.ticks_add(time.ticks_ms(), PIPI_PULSE_MS) - # ============================================================ # MAIN LOOP # ============================================================ @@ -132,8 +120,6 @@ while True: # STOP hat Priorität (soll sofort alles beenden) if is_pressed(btn_stop) and allow("stop"): df_stop() - pipi_until = 0 - pump_out.value(0) # Sounds if is_pressed(btn_cry) and allow("cry"): @@ -143,16 +129,10 @@ while True: df_play_mp3(2) # /mp3/0002.mp3 if is_pressed(btn_gig) and allow("gig"): + df_play_mp3(4) # /mp3/0004.mp3 + + if is_pressed(btn_poop) and allow("poop"): df_play_mp3(3) # /mp3/0003.mp3 - # Pipi-Trigger (Impuls) - if is_pressed(btn_pee) and allow("pee"): - pipi_trigger() - - # Pumpen-Ausgang entsprechend Impulszeit - if time.ticks_diff(pipi_until, time.ticks_ms()) > 0: - pump_out.value(1) - else: - pump_out.value(0) time.sleep_ms(10) diff --git a/mp3/0003.mp3 b/mp3/0003.mp3 index 4850bc5..ca6712c 100644 Binary files a/mp3/0003.mp3 and b/mp3/0003.mp3 differ diff --git a/mp3/0003_lach.mp3 b/mp3/0003_lach.mp3 deleted file mode 100644 index f1b4ea1..0000000 Binary files a/mp3/0003_lach.mp3 and /dev/null differ