# Control Service Development Guide ## Overview The Hotel Pi Control Service is a Node.js WebSocket server that: - Listens for HDMI-CEC remote input - Translates input to navigation events - Executes system commands (launch apps, etc.) - Communicates with the frontend via WebSocket ## Architecture ``` src/ ├── server.js # Main server & WebSocket handler ├── cec-handler.js # HDMI-CEC input processing ├── commands.js # System command execution └── ... package.json # Dependencies .eslintrc.json # Linting config Dockerfile # Container image ``` ## Setup & Development ### Local Setup ```bash cd control-service npm install npm run dev ``` Server runs on http://localhost:3001 ### Commands ```bash npm run dev # Start with hot reload npm run start # Run production npm run lint # Check code style ``` ## Core Modules ### server.js Main HTTP/WebSocket server. **Features:** - HTTP server on port 3001 - WebSocket server for client connections - Health check endpoint (`GET /health`) - Message routing to handlers **HTTP Routes:** ``` GET / - Server info GET /health - Health status WS / - WebSocket connection ``` **WebSocket Messages:** Client → Server: - `launch-plex` - Launch Plex media center - `return-to-kiosk` - Kill current app, return to kiosk - `restart-kiosk` - Restart kiosk application - `execute` - Execute shell command - `ping` - Ping for heartbeat Server → Client: - `connected` - Connection confirmed - `input` - Remote input event - `error` - Error message - Response to commands ### cec-handler.js HDMI-CEC input listener. **Class: CECHandler** ```javascript const cec = new CECHandler(devicePath); await cec.init(); // Initialize CEC cec.on('input', callback); // Listen for input cec.startMonitoring(callback); // Start listening ``` **Input Event Mapping:** ``` CEC Button → Event Type OK/Select → select Up arrow → up Down arrow → down Left arrow → left Right arrow → right Exit/Back → back ``` **Notes:** - Requires `cec-client` system package - Device path: `/dev/ttyAMA0` (Raspberry Pi UART) - Gracefully falls back if cec-client unavailable ### commands.js System command executor. **Class: CommandExecutor** ```javascript const executor = new CommandExecutor(config); await executor.launchPlex(); // Launch Plex await executor.restartKiosk(); // Restart kiosk await executor.returnToKiosk(); // Kill Plex, return to kiosk await executor.executeCommand(cmd); // Execute arbitrary command executor.getHealth(); // Get service health ``` **Configuration:** ```javascript { plexLaunchCommand: '/usr/bin/plex-htpc', kioskLaunchCommand: '/home/pi/scripts/launch-kiosk.sh' } ``` **Executing Commands:** ```javascript // Launch Plex const result = await executor.launchPlex(); // Returns: { success: true, message: '...' } // Execute custom command const result = await executor.executeCommand('ls -la /tmp'); // Returns: { success: true, stdout: '...', stderr: '' } ``` ## WebSocket Protocol ### Connection Lifecycle 1. Client connects to `ws://localhost:3001` 2. Server sends `{ type: 'connected', payload: {...} }` 3. Client can send commands 4. Server processes and responds 5. Client can emit input events ### Example Client Usage ```javascript const ws = new WebSocket('ws://localhost:3001'); ws.onopen = () => { console.log('Connected'); ws.send(JSON.stringify({ type: 'launch-plex', payload: {} })); }; ws.onmessage = (event) => { const data = JSON.parse(event.data); console.log('Response:', data); }; ws.onerror = (error) => { console.error('Error:', error); }; ws.onclose = () => { console.log('Disconnected'); }; ``` ### Message Format All messages are JSON: ```json { "type": "command-name", "payload": { "key": "value" } } ``` ## Configuration ### Environment Variables ```bash PORT=3001 # Server port CEC_DEVICE=/dev/ttyAMA0 # CEC serial device PLEX_LAUNCH_COMMAND=/usr/bin/plex-htpc # Plex executable KIOSK_LAUNCH_COMMAND=/home/pi/... # Kiosk script path ``` ### Runtime Options Edit `config` object in `server.js`: ```javascript const executor = new CommandExecutor({ plexLaunchCommand: process.env.PLEX_LAUNCH_COMMAND, kioskLaunchCommand: process.env.KIOSK_LAUNCH_COMMAND, }); ``` ## Building & Deployment ### Docker Build ```bash docker build -t hotel-pi-control . docker run -p 3001:3001 hotel-pi-control ``` ### Production Deployment ```bash # Build for production npm run build # Start service npm run start ``` ### Systemd Service Create `/etc/systemd/system/hotel-pi-control.service`: ```ini [Unit] Description=Hotel Pi Control Service After=network.target [Service] Type=simple User=pi WorkingDirectory=/home/pi/hotel-pi ExecStart=/usr/bin/node src/server.js Restart=always RestartSec=10 Environment="PATH=/usr/local/bin:/usr/bin" [Install] WantedBy=multi-user.target ``` Enable: ```bash sudo systemctl daemon-reload sudo systemctl enable hotel-pi-control sudo systemctl start hotel-pi-control ``` ## Error Handling ### Connection Errors ```javascript // Client disconnected ws.on('close', () => { console.log('Client disconnected'); clients.delete(ws); }); // WebSocket error ws.on('error', (error) => { console.error('WebSocket error:', error.message); }); ``` ### Command Execution Errors Commands wrap in try-catch and return success flag: ```javascript try { await executor.launchPlex(); // Send success response } catch (error) { ws.send(JSON.stringify({ type: 'error', payload: { message: error.message } })); } ``` ## Logging Service logs to console with emoji indicators: ``` ✓ Success ✗ Error ❌ Critical error 📡 Connection event 🎮 Input event 🎬 Plex launch 🔙 Return to kiosk ⚙ Command execution 🛑 Shutdown ``` For persistent logs, redirect output: ```bash npm run start > /var/log/hotel-pi-control.log 2>&1 & ``` ## Testing ### Health Check ```bash curl http://localhost:3001/health { "status": "healthy", "timestamp": "2024-03-20T12:34:56Z", "processes": ["plex"] } ``` ### WebSocket Test Using `wscat`: ```bash npm install -g wscat wscat -c ws://localhost:3001 # Type messages: > {"type":"ping","payload":{}} < {"type":"pong","timestamp":"..."} ``` ### Command Testing ```bash # Execute command curl -X POST http://localhost:3001 \ -H "Content-Type: application/json" \ -d '{"type":"execute","payload":{"command":"echo hello"}}' # Test launch (won't actually launch without Plex installed) curl -X POST http://localhost:3001 \ -H "Content-Type: application/json" \ -d '{"type":"launch-plex","payload":{}}' ``` ## Troubleshooting ### Port Already in Use ```bash # Find process using port 3001 lsof -i :3001 # Kill process kill -9 ``` ### CEC Not Working ```bash # Check if cec-client is installed which cec-client # Install if missing (Ubuntu/Debian) sudo apt-get install libcec-dev # Test CEC connection echo "as" | cec-client -s ``` ### WebSocket Connection Fails - Verify server is running: `curl http://localhost:3001` - Check firewall: `sudo ufw allow 3001` - Check browser console for CORS/connection errors - Verify WebSocket URL in frontend `.env` ### Memory Leaks Check active connections: ```bash # In health response curl http://localhost:3001/health | jq '.processes' # Monitor over time watch curl http://localhost:3001/health ``` ## Performance Optimization 1. **Connection Pooling:** - Reuse WebSocket connections - Don't create new connection per message 2. **Message Batching:** - Send multiple events in one message if possible - Avoid rapid successive messages 3. **Resource Cleanup:** - Properly close WebSocket connections - Kill child processes when done 4. **Monitoring:** - Log important events - Track connection count - Monitor memory usage ## Security Considerations 1. **Input Validation:** - Validate command strings - Prevent shell injection - Whitelist allowed commands 2. **Authentication:** - In production, add auth before executing commands - Use JWT or similar for WebSocket auth 3. **CORS:** - Configure CORS_ORIGIN for specific domains - Don't allow all origins in production 4. **Network:** - Firewall port 3001 to local network only - Use HTTPS/WSS in production - Disable debug endpoints in production ## Code Quality ### Linting ```bash npm run lint ``` ### Best Practices - Use async/await (not callbacks) - Handle errors in try-catch - Log all important events - Close connections properly - Validate input data ## Resources - [Node.js Docs](https://nodejs.org/docs/) - [ws Library](https://github.com/websockets/ws) - [libcec Documentation](https://github.com/libcec/libcec)