new alarm mechanism
This commit is contained in:
@@ -15,12 +15,15 @@ Protocol:
|
||||
3. Text frame: {"type":"alarm_stop"}
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import asyncio
|
||||
import logging
|
||||
from random import randint
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
import websockets
|
||||
|
||||
from alarm_scheduler import DEFAULT_CONFIG_PATH, load_config, should_fire
|
||||
from audio_handler import find_wav, read_wav, stream_alarm
|
||||
from image_handler import IMG_DIR, load_status_image, send_status_image
|
||||
|
||||
@@ -28,6 +31,20 @@ logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(mess
|
||||
log = logging.getLogger("contents_server")
|
||||
|
||||
PORT = 8766
|
||||
PI_DIR = Path(__file__).parent
|
||||
|
||||
# Set by main(), read by handler()
|
||||
_config_path: Path = DEFAULT_CONFIG_PATH
|
||||
|
||||
TICK_INTERVAL = 5 # seconds between schedule checks
|
||||
|
||||
|
||||
def _resolve_path(relative: str) -> Path:
|
||||
"""Resolve a config path relative to pi/ directory."""
|
||||
p = Path(relative)
|
||||
if not p.is_absolute():
|
||||
p = PI_DIR / p
|
||||
return p
|
||||
|
||||
|
||||
async def handler(ws):
|
||||
@@ -35,27 +52,43 @@ async def handler(ws):
|
||||
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)
|
||||
config = load_config(_config_path)
|
||||
|
||||
# Resolve audio and image paths from config (or defaults)
|
||||
if config:
|
||||
audio_path = find_wav(_resolve_path(config.get("alarm_audio", "assets/alarm/alarm_test.wav")))
|
||||
alarm_img_path = _resolve_path(config.get("alarm_image", "assets/img/on_alarm.png"))
|
||||
else:
|
||||
audio_path = None
|
||||
alarm_img_path = IMG_DIR / "on_alarm.png"
|
||||
|
||||
# Load status images
|
||||
img_idle = load_status_image(IMG_DIR / "idle.png")
|
||||
img_alarm = load_status_image(IMG_DIR / "on_alarm.png")
|
||||
img_alarm = load_status_image(alarm_img_path)
|
||||
|
||||
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)
|
||||
if not config:
|
||||
log.info("No alarms configured — idling forever")
|
||||
await asyncio.Future()
|
||||
return
|
||||
|
||||
pcm, sr, ch, bits = read_wav(audio_path)
|
||||
last_fired_minute = None
|
||||
|
||||
while True:
|
||||
if should_fire(config):
|
||||
current_minute = datetime.now().strftime("%Y%m%d%H%M")
|
||||
|
||||
if current_minute != last_fired_minute:
|
||||
last_fired_minute = current_minute
|
||||
log.info("Alarm firing at %s", current_minute)
|
||||
await send_status_image(ws, img_alarm)
|
||||
await stream_alarm(ws, pcm, sr, ch, bits)
|
||||
await send_status_image(ws, img_idle)
|
||||
|
||||
await asyncio.sleep(TICK_INTERVAL)
|
||||
|
||||
# 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])
|
||||
|
||||
@@ -63,8 +96,13 @@ async def handler(ws):
|
||||
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
|
||||
await asyncio.Future()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="Alarm contents server")
|
||||
parser.add_argument("--config", type=Path, default=DEFAULT_CONFIG_PATH,
|
||||
help="Path to alarm config JSON (default: %(default)s)")
|
||||
args = parser.parse_args()
|
||||
_config_path = args.config
|
||||
asyncio.run(main())
|
||||
|
||||
Reference in New Issue
Block a user