pi features docs
This commit is contained in:
70
pi/contents_server.py
Normal file
70
pi/contents_server.py
Normal file
@@ -0,0 +1,70 @@
|
||||
"""
|
||||
Contents server — serves alarm audio and status images over WebSocket.
|
||||
|
||||
Streams WAV PCM chunks and pushes 1-bit monochrome status images to the
|
||||
connected ESP32 dashboard client on port 8766.
|
||||
|
||||
Protocol:
|
||||
Status image:
|
||||
1. Text frame: {"type":"status_image","width":120,"height":120}
|
||||
2. Binary frame: 1-bit monochrome bitmap
|
||||
|
||||
Alarm audio:
|
||||
1. Text frame: {"type":"alarm_start","sample_rate":N,"channels":N,"bits":N}
|
||||
2. Binary frames: raw PCM chunks (4096 bytes each, paced at ~90% real-time)
|
||||
3. Text frame: {"type":"alarm_stop"}
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from random import randint
|
||||
|
||||
import websockets
|
||||
|
||||
from audio_handler import find_wav, read_wav, stream_alarm
|
||||
from image_handler import IMG_DIR, load_status_image, send_status_image
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
|
||||
log = logging.getLogger("contents_server")
|
||||
|
||||
PORT = 8766
|
||||
|
||||
|
||||
async def handler(ws):
|
||||
"""Handle a single WebSocket connection."""
|
||||
remote = ws.remote_address
|
||||
log.info("Client connected: %s:%d", remote[0], remote[1])
|
||||
|
||||
wav_path = find_wav()
|
||||
pcm, sr, ch, bits = read_wav(wav_path)
|
||||
|
||||
# Load status images
|
||||
img_idle = load_status_image(IMG_DIR / "idle.png")
|
||||
img_alarm = load_status_image(IMG_DIR / "on_alarm.png")
|
||||
|
||||
try:
|
||||
# Send idle image on connect
|
||||
await send_status_image(ws, img_idle)
|
||||
|
||||
while True:
|
||||
delay = randint(30, 60)
|
||||
log.info("Next alarm in %ds", delay)
|
||||
await asyncio.sleep(delay)
|
||||
|
||||
# Switch to alarm image before audio
|
||||
await send_status_image(ws, img_alarm)
|
||||
await stream_alarm(ws, pcm, sr, ch, bits)
|
||||
# Switch back to idle after alarm
|
||||
await send_status_image(ws, img_idle)
|
||||
except websockets.exceptions.ConnectionClosed:
|
||||
log.info("Client disconnected: %s:%d", remote[0], remote[1])
|
||||
|
||||
|
||||
async def main():
|
||||
log.info("Contents server starting on port %d", PORT)
|
||||
async with websockets.serve(handler, "0.0.0.0", PORT):
|
||||
await asyncio.Future() # run forever
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
Reference in New Issue
Block a user