doll und backstage variante
This commit is contained in:
+224
@@ -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)
|
||||
Reference in New Issue
Block a user