Tidak Ada Deskripsi

ArtyomV2X a71719c259 Gitignore for internal testing 1 bulan lalu
firmware b2a6f60849 Initial Commit 1 bulan lalu
host b2a6f60849 Initial Commit 1 bulan lalu
packaging b2a6f60849 Initial Commit 1 bulan lalu
.gitignore a71719c259 Gitignore for internal testing 1 bulan lalu
README.md b2a6f60849 Initial Commit 1 bulan lalu
luxstats.sh b2a6f60849 Initial Commit 1 bulan lalu

README.md

LuxStats

Hardware/software status LEDs for your desk: a Pimoroni Tiny 2040 drives a NeoPixel strip while a PyQt5 configurator applet owns the configuration, maps LED zones to live statuses, and pushes every change to the board instantly. The board is treated as read-only: config lives on the host and is re-pushed on connect.

luxstats/
├── firmware/
│   ├── boot.py     # enables the second USB CDC (data) serial channel
│   └── code.py     # LuxStats firmware v2 (CircuitPython, RAM-only config)
├── host/
│   ├── luxstats_configurator.py   # PyQt5 applet
│   ├── system_monitor.py          # status probes + notification watcher
│   └── requirements.txt
└── README.md

Hardware setup

Wire NeoPixel data-in to GP0 (configurable), power from 5V/USB, share ground. Default pixel order GRBW (SK6812 RGBW); use GRB for WS2812B.

  1. Install CircuitPython 8/9 on the Tiny 2040.
  2. Copy Adafruit neopixel.mpy into CIRCUITPY/lib/.
  3. Copy firmware/boot.py and firmware/code.py to the CIRCUITPY root.
  4. Power-cycle once; the board then exposes two serial ports (REPL + the LuxStats data channel).

Host setup

cd host
pip install -r requirements.txt        # PyQt5, pyserial, psutil
python3 luxstats_configurator.py

Linux extras: dialout group for serial, fuser (psmisc) for camera detection, dbus-monitor for the notification watcher, nvidia-smi for NVIDIA GPUs (AMD uses sysfs/hwmon automatically), and the drivetemp/nvme kernel sensors for drive temperatures.

Running in the background (Arch Linux)

./luxstats.sh auto-detects the newest installed Python 3, runs directly on the system interpreter when python-pyqt5 python-pyserial python-psutil are installed via pacman (no venv), and otherwise creates a managed venv under ~/.local/share/luxstats/venv. It launches the app detached (start/stop/status), logging to ~/.local/state/luxstats/luxstats.log. An optional systemd user unit is in packaging/luxstats.service.

The app auto-connects at startup (and on every link drop) to the device named Tiny 2040 (8MB) - CircuitPython CDC2 control on any /dev/ttyACM# port - the name is stored in the config and editable on the Connection tab.

Zone model

Everything on the strip is an ordered list of zones. A zone is either a status source or an ambient effect region:

  • Status zones — known sources are locked to their correct type: cpu/gpu/ram/disk* are percent (gradient, optional bar-graph fill); *_temp sources report raw Celsius and map onto a gradient between a per-zone min/max °C; eth and wifi are positional — one color per position (down/10M/100M/1G/2.5G/10G and down/2.4/5/6 GHz, no blending) — and eth reads 0 unless the link carrier is actually up; vpn/cam are on/off colors; custom takes pushed RGBW values.
  • Ambient zones — off/solid/breathe/rainbow/sparkle with color and speed, assigned to an explicit range. Pixels not claimed by any zone stay off.

Zones may not overlap: the app flags conflicts (and ranges past the end of the strip) and blocks pushes until fixed. Reorder zones with Move up/down. Each row has a Test start/stop toggle that blinks that exact LED range on the strip while you dial in positions; the camera glow zone (start/count) has its own start/stop test button.

The camera glow overlay uses assigned LEDs with range syntax (1-5,9-13) and the same animation presets as ambient zones (solid/breathe/rainbow/sparkle). Overlays render on top of zones, in order: camera glow → notification blinker → range-test blink. Any start/stop test (zone range, camera glow, raw fill) pauses automated polling and resumes it when the test stops; fake-status buttons pause polling for 10 s.

Automation

The Automation tab discovers every source — including all mounted physical drives (usage per drive, temperature where a nvme/drivetemp sensor exists, plus disk/disk_temp aggregates) and separate CPU/GPU utilisation and temperature — and pushes the enabled ones at your chosen interval (adjustable in 100 ms steps; slow probes are cached internally so 10 Hz polling stays cheap). Camera use toggles the glow automatically — detection ignores PipeWire/WirePlumber/PulseAudio holding /dev/video* open, and both on AND off transitions are pushed. Desktop notifications (Linux/DBus) fire the blinker. The watcher toggle, camera-automation toggle, blinker period/count, and poll interval all persist in the host config.

Configuration & persistence

Every edit auto-pushes to the device after a short debounce — there is no Apply button. Config is saved on the host at ~/.config/luxstats/config.json (Linux/macOS) or %APPDATA%\luxstats (Windows), reloaded at startup, and pushed on every connect. Profiles can be exported/imported as JSON.

Connection reliability

The firmware uses non-blocking serial writes (it can never stall waiting on the host to read a reply) and only acks status updates when asked. The app drains stale input before every request so request/response can't desync, serialises writes across threads, pings the device after ~10 s of idle, and auto-reconnects every few seconds if the link drops without a manual disconnect.

Serial protocol (JSON lines, host → device)

Command Purpose
{"cmd":"ping"} health check, returns {"pong":1,"fw":2}
{"cmd":"config","config":{...}} apply full configuration (RAM only)
{"cmd":"get_config"} read back the active config
{"cmd":"status","values":{"cpu":0.4,"gpu_temp":0.6,"disk_sda":0.8,...}} push live values (silent; add "ack":1 for a reply)
{"cmd":"notify","color":[r,g,b,w],"period":0.4,"count":6} blinker (count:-1 = until cleared)
{"cmd":"notify_clear"} stop the blinker
{"cmd":"camera","active":true} camera glow on/off
{"cmd":"test","start":2,"count":3,"active":true} blink a range while configuring (auto-expires after 3 min)
{"cmd":"raw","pixels":[[r,g,b,w],...]} / {"cmd":"raw_off"} direct test mode

Custom sources: add a zone with any name (type custom or bool) and push matching keys via status — CI state, mic mute, meeting status, etc.