doll und backstage variante

This commit is contained in:
2026-03-25 10:40:43 +01:00
parent 1a69085384
commit 4914ea84cb
5 changed files with 231 additions and 26 deletions
+1
View File
@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="MicroPythonTools"> <component name="MicroPythonTools">
<option name="lastShownVersion" value="2026.1.1" />
<option name="pluginEnabled" value="true" /> <option name="pluginEnabled" value="true" />
<option name="portName" value="COM3" /> <option name="portName" value="COM3" />
</component> </component>
+224
View File
@@ -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)
+6 -26
View File
@@ -14,7 +14,7 @@ GPIO_CRY = 2
GPIO_WHIM = 3 GPIO_WHIM = 3
GPIO_GIG = 4 GPIO_GIG = 4
GPIO_STOP = 5 GPIO_STOP = 5
GPIO_PEE = 6 GPIO_POOP = 10
# Pumpen-Ausgang (später Treiber/ULN2003 etc.) # Pumpen-Ausgang (später Treiber/ULN2003 etc.)
GPIO_PUMP_OUT = 7 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_whim = Pin(GPIO_WHIM, Pin.IN, Pin.PULL_UP)
btn_gig = Pin(GPIO_GIG, 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_stop = Pin(GPIO_STOP, Pin.IN, Pin.PULL_UP)
btn_pee = Pin(GPIO_PEE, Pin.IN, Pin.PULL_UP) btn_poop = Pin(GPIO_POOP, Pin.IN, Pin.PULL_UP)
pump_out = Pin(GPIO_PUMP_OUT, Pin.OUT)
pump_out.value(0)
# Entprellung # Entprellung
DEBOUNCE_MS = 80 DEBOUNCE_MS = 80
@@ -102,6 +99,7 @@ last_ms = {
"gig": 0, "gig": 0,
"stop": 0, "stop": 0,
"pee": 0, "pee": 0,
"poop": 0,
} }
def is_pressed(pin: Pin) -> bool: def is_pressed(pin: Pin) -> bool:
@@ -115,16 +113,6 @@ def allow(key: str) -> bool:
return True return True
return False 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 # MAIN LOOP
# ============================================================ # ============================================================
@@ -132,8 +120,6 @@ while True:
# STOP hat Priorität (soll sofort alles beenden) # STOP hat Priorität (soll sofort alles beenden)
if is_pressed(btn_stop) and allow("stop"): if is_pressed(btn_stop) and allow("stop"):
df_stop() df_stop()
pipi_until = 0
pump_out.value(0)
# Sounds # Sounds
if is_pressed(btn_cry) and allow("cry"): if is_pressed(btn_cry) and allow("cry"):
@@ -143,16 +129,10 @@ while True:
df_play_mp3(2) # /mp3/0002.mp3 df_play_mp3(2) # /mp3/0002.mp3
if is_pressed(btn_gig) and allow("gig"): 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 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) time.sleep_ms(10)
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.