#!/usr/bin/env python3 """Standalone network API server for the RPi Audio Mixer. Starts the OSC, REST, and WebSocket servers. The DSP engine must be running with Carla and JACK for full functionality. Usage: python -m src.network.run [--host HOST] [--port PORT] [--osc-port PORT] Environment: MIXER_API_KEY — API key for authentication (default: mixer-local) """ import argparse import asyncio import logging import os import signal import sys # Add project root to path sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) from src.mixer.dsp_engine import DSPEngine, DSPEngineConfig from src.network.server import NetworkServer logging.basicConfig( level=logging.INFO, format="%(asctime)s [%(levelname)s] %(name)s: %(message)s", datefmt="%H:%M:%S", ) logger = logging.getLogger("mixer-network") def parse_args(): parser = argparse.ArgumentParser( description="RPi Audio Mixer — Network API Server", ) parser.add_argument( "--host", default="0.0.0.0", help="HTTP/WS listen address (default: 0.0.0.0)", ) parser.add_argument( "--port", type=int, default=8080, help="HTTP/WS listen port (default: 8080)", ) parser.add_argument( "--osc-port", type=int, default=9001, help="OSC UDP listen port (default: 9001)", ) parser.add_argument( "--channels", type=int, default=16, help="Number of mixer channels (default: 16)", ) parser.add_argument( "--no-osc", action="store_true", help="Disable OSC server", ) parser.add_argument( "--api-key", help="API key (overrides MIXER_API_KEY env var)", ) return parser.parse_args() async def main(): args = parse_args() # API key if args.api_key: os.environ["MIXER_API_KEY"] = args.api_key # Create DSP engine (simplified — no Carla OSC connection) config = DSPEngineConfig( num_channels=args.channels, osc_enabled=False, # Don't connect to Carla in standalone mode jack_routing_enabled=False, # Don't touch JACK connections in standalone ) engine = DSPEngine(config) engine.start(connect_osc=False) # Create network server server = NetworkServer( engine=engine, osc_host=args.host, osc_port=args.osc_port if not args.no_osc else 0, http_host=args.host, http_port=args.port, ) # Graceful shutdown shutdown_event = asyncio.Event() def _shutdown(sig=None, frame=None): logger.info("Shutdown signal received") shutdown_event.set() signal.signal(signal.SIGINT, _shutdown) signal.signal(signal.SIGTERM, _shutdown) # Start server await server.start() if not args.no_osc: logger.info("OSC server: udp://%s:%d", args.host, args.osc_port) logger.info("REST API: http://%s:%d/api/v1", args.host, args.port) logger.info("WebSocket: ws://%s:%d/ws", args.host, args.port) logger.info("API Key: %s", os.environ.get("MIXER_API_KEY", "mixer-local")) # Wait for shutdown await shutdown_event.wait() # Stop await server.stop() engine.stop() logger.info("Server stopped") if __name__ == "__main__": asyncio.run(main())