92 lines
2.8 KiB
Markdown
92 lines
2.8 KiB
Markdown
|
|
# Pi Dashboard Servers
|
||
|
|
|
||
|
|
WebSocket servers that feed system stats, alarm audio, and status images to the ESP32-S3 RLCD dashboard.
|
||
|
|
|
||
|
|
## File Structure
|
||
|
|
|
||
|
|
```
|
||
|
|
pi/
|
||
|
|
run_all.py # Launches both servers as child processes
|
||
|
|
stats_server.py # Real system stats over WebSocket (port 8765)
|
||
|
|
contents_server.py # Alarm audio + status images over WebSocket (port 8766)
|
||
|
|
mock_server.py # Drop-in replacement for stats_server with random data
|
||
|
|
audio_handler.py # WAV loading, PCM chunking, alarm streaming
|
||
|
|
image_handler.py # PNG to 1-bit monochrome conversion, alpha compositing
|
||
|
|
requirements.txt
|
||
|
|
assets/
|
||
|
|
alarm/ # WAV files for alarm audio
|
||
|
|
img/ # Status images (idle.png, on_alarm.png)
|
||
|
|
```
|
||
|
|
|
||
|
|
## Requirements
|
||
|
|
|
||
|
|
Python 3.10+
|
||
|
|
|
||
|
|
```
|
||
|
|
pip install -r requirements.txt
|
||
|
|
```
|
||
|
|
|
||
|
|
Dependencies: `websockets`, `psutil`, `Pillow`
|
||
|
|
|
||
|
|
## Running
|
||
|
|
|
||
|
|
Start both servers:
|
||
|
|
|
||
|
|
```
|
||
|
|
python run_all.py
|
||
|
|
```
|
||
|
|
|
||
|
|
Or run individually:
|
||
|
|
|
||
|
|
```
|
||
|
|
python stats_server.py # port 8765 only
|
||
|
|
python contents_server.py # port 8766 only
|
||
|
|
python mock_server.py # port 8765, random data (no psutil needed)
|
||
|
|
```
|
||
|
|
|
||
|
|
## Servers
|
||
|
|
|
||
|
|
### stats_server.py -- port 8765
|
||
|
|
|
||
|
|
Pushes a JSON object every 2 seconds with real system metrics from `psutil`:
|
||
|
|
|
||
|
|
- `cpu_pct`, `mem_pct`, `mem_used_mb`, `disk_pct`
|
||
|
|
- `cpu_temp` (reads `/sys/class/thermal/` as fallback)
|
||
|
|
- `uptime_hrs`, `net_rx_kbps`, `net_tx_kbps`
|
||
|
|
- `services` (mocked until systemd integration)
|
||
|
|
- `local_time` fields for RTC sync (`y`, `mo`, `d`, `h`, `m`, `s`)
|
||
|
|
|
||
|
|
### contents_server.py -- port 8766
|
||
|
|
|
||
|
|
Serves alarm audio and status images. Protocol:
|
||
|
|
|
||
|
|
**Status image:**
|
||
|
|
1. Text frame: `{"type":"status_image","width":120,"height":120}`
|
||
|
|
2. Binary frame: 1-bit monochrome bitmap (1800 bytes)
|
||
|
|
|
||
|
|
**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"}`
|
||
|
|
|
||
|
|
On connect, sends `idle.png` as the status image. Alarm cycles switch to `on_alarm.png` during playback, then back to `idle.png`.
|
||
|
|
|
||
|
|
### mock_server.py -- port 8765
|
||
|
|
|
||
|
|
Same JSON schema and 2-second push interval as `stats_server.py`, but all values are randomized. No `psutil` dependency -- useful for development on non-Pi machines.
|
||
|
|
|
||
|
|
Does not include `local_time` fields.
|
||
|
|
|
||
|
|
## Modules
|
||
|
|
|
||
|
|
### audio_handler.py
|
||
|
|
|
||
|
|
- `find_wav()` -- finds the first `.wav` in `assets/alarm/`
|
||
|
|
- `read_wav(path)` -- reads WAV, returns `(pcm_bytes, sample_rate, channels, bits)`
|
||
|
|
- `stream_alarm(ws, pcm, sr, ch, bits)` -- streams one alarm cycle over WebSocket
|
||
|
|
|
||
|
|
### image_handler.py
|
||
|
|
|
||
|
|
- `load_status_image(path)` -- loads PNG, composites transparency onto white, converts to 1-bit 120x120 monochrome bitmap (black=1, MSB-first)
|
||
|
|
- `send_status_image(ws, img_bytes)` -- sends status image header + binary over WebSocket
|