2026-02-17 12:39:14 +09:00
2026-02-17 12:39:14 +09:00
2026-02-16 23:32:32 +09:00
2026-02-17 12:18:13 +09:00
2026-02-17 12:18:13 +09:00
2026-02-15 23:12:25 +09:00
2026-02-15 04:15:30 +09:00
2026-02-17 12:18:13 +09:00
2026-02-17 12:39:14 +09:00
2026-02-15 21:46:18 +09:00
2026-02-15 22:44:36 +09:00
2026-02-16 21:08:40 +09:00

Pi Dashboard Servers

WebSocket servers that feed system stats, alarm audio, and status images to the ESP32-S3 RLCD dashboard.

File Structure

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
alarm_scheduler.py    # Loads and validates alarm config, checks firing schedule
requirements.txt
config/
  alarms.json         # Alarm schedule configuration
assets/
  alarm/              # WAV files for alarm audio
  img/                # Status images (idle.png, on_alarm.png, sleep.png)
scripts/
  setup.sh            # Install deps + create and enable systemd service
  edit.sh             # Edit alarm config and restart service
  remove.sh           # Stop, disable, and remove systemd service

Requirements

Python 3.10+

pip install -r requirements.txt

Dependencies: websockets, psutil, Pillow

Running

Start both servers:

python run_all.py                        # both servers, default config
python run_all.py --config path/to.json  # both servers, custom config

Or run individually:

python stats_server.py                       # port 8765 only
python contents_server.py --config path/to.json  # port 8766, custom config
python mock_server.py                        # port 8765, random data (no psutil needed)

Running as a systemd service

Use the helper scripts in scripts/ to manage a pi-dashboard systemd service:

bash scripts/setup.sh    # install deps, create + enable service
bash scripts/edit.sh     # edit alarm config, restart service
bash scripts/remove.sh   # stop + remove service

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 (values are in kB/s despite the field names)
  • services — live Docker container statuses via docker ps -a, with a ternary status model (running, warning, stopped). Monitored containers: gitea, samba, pihole, qbittorrent, frpc (ny), pinepods, frpc (ssh), jellyfin.
  • 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":200,"height":200}
  2. Binary frame: 1-bit monochrome bitmap (5000 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"}

Loads alarm config from config/alarms.json (override with --config). Checks schedule every 5 seconds, fires once per matched minute. If no config or empty config, sends idle image and blocks forever. On alarm: switches to alarm image, streams audio, switches back to idle.

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.

Alarm Configuration

Config file: config/alarms.json -- a single alarm object or an array of alarm objects.

Example with two alarms:

[
  {
    "alarm_time": "0730",
    "alarm_days": ["Mon", "Tue", "Wed", "Thu", "Fri"],
    "alarm_audio": "assets/alarm/alarm_test.wav",
    "alarm_image": "assets/img/on_alarm.png"
  },
  {
    "alarm_time": "2300",
    "alarm_audio": "assets/alarm/sleep.wav",
    "alarm_image": "assets/img/sleep.png"
  }
]
Field Type Required Description
alarm_time string Yes 4-digit HHMM, 24-hour. Fires on the matched minute.
alarm_days string[] No 3-letter abbreviations: MonSun. If omitted, fires every day.
alarm_dates string[] No MM/DD strings. Ignored if alarm_days is also set.
alarm_audio string No WAV path, relative to project root. Silent if not set. "default" (case-insensitive) uses assets/alarm/alarm.wav.
alarm_image string No Status PNG path, relative to project root. Default: assets/img/on_alarm.png.

If both alarm_days and alarm_dates are present, alarm_days takes priority.

Modules

audio_handler.py

  • find_wav(path=None) -- uses the given path if it exists, otherwise falls back to glob in assets/alarm/
  • read_wav(path) -- reads WAV, normalizes audio to 0 dBFS, 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 200x200 monochrome bitmap (black=1, MSB-first)
  • send_status_image(ws, img_bytes) -- sends status image header + binary over WebSocket

alarm_scheduler.py

  • load_config(path) -- reads and validates alarm JSON; returns list of alarm dicts or None
  • should_fire(config) -- checks a single alarm entry against current local time
Description
server that runs on the pi for pushing stuff to the rlcd
Readme 761 KiB
Languages
Python 91.9%
Shell 8.1%