|
|
1 ay önce | |
|---|---|---|
| firmware | 1 ay önce | |
| host | 1 ay önce | |
| packaging | 1 ay önce | |
| .gitignore | 1 ay önce | |
| README.md | 1 ay önce | |
| luxstats.sh | 1 ay önce |
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
Wire NeoPixel data-in to GP0 (configurable), power from 5V/USB, share
ground. Default pixel order GRBW (SK6812 RGBW); use GRB for WS2812B.
neopixel.mpy into CIRCUITPY/lib/.firmware/boot.py and firmware/code.py to the CIRCUITPY root.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.
./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.
Everything on the strip is an ordered list of zones. A zone is either a status source or an ambient effect region:
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.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.
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.
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.
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.
| 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.