As-built live per-device WiFi bandwidth strip along the bottom of the camwall’s cctv pane on
telep-mainframe. Every 5 s it polls telep-router over one persistent SSH connection, diffs
per-station byte counters into Mbps, resolves MACs to friendly names, renders an ASS overlay and
pushes it into the already-running mpv. Every device on telep1 gets a tile with live ↓/↑; a header
tile shows the 5 GHz WAN total.
Design in 2026-07-22-wifi-usage-strip-design, task-by-task plan in 2026-07-22-wifi-usage-strip-plan — this note is the completion record and the hard-won knowledge, not a restatement of either.
For Agents
Service:
wifi-usage.serviceon telep-mainframe (enabled, survives reboot, runs as root). Code:/home/levander/wifi-usage/—iwparse.py,rates.py,names.py,render.py,mpvipc.py,collect.py+ 44 unit tests (44/44 pass, python3 stdlib only). Sockets: mpv IPC/run/camwall.sock; SSH ControlMaster/run/wifi-usage/router.sock. Refresh: 5 s. Router access: the same LAN-only dropbear + key from Prerequisite router access.
The four things that made this work
1. The mpv IPC connection must be persistent
mpv destroys
osd-overlayentries when the IPC client disconnectsA connect → send → close client silently does nothing. mpv even answers
{"error":"success"}, then removes the overlay the instant the socket closes. Proven empirically: the naive version produced no strip at all; holding the same socket open with a byte-identical payload made it appear immediately.Consequences:
- The daemon holds one long-lived connection to
/run/camwall.sock.- It must drain mpv’s event stream (non-blocking
recv) or the socket buffer fills and the push eventually blocks.- Reconnect-on-failure is the self-healing mechanism — when
camwallrestarts it recreates the socket, the daemon reconnects, and the strip comes back unaided (measured ~23 s).
2. Bytes come from iw, not iwinfo
iw dev <ap> station dumpgives per-stationrx bytes/tx bytes
iwinfo assoclist(what 2026-07-17-intruder-alarm uses for presence) reports only packet counts and link rates — no byte counters.iw dev <ap> station dumpgives per-station byte totals plus signal, andiwis already installed on the router.So per-device bandwidth needs no extra package, no nftables accounting rules, no conntrack parsing, no nlbwmon. This collapsed the whole “how do we meter per-device traffic” question.
3. Direction is from the AP’s point of view
Station
tx bytesis the device's DOWNLOAD — the easiest bug to shipThe counters belong to the access point, so they are inverted relative to intuition:
- station
tx bytes= AP → device = the device’s download (↓)- station
rx bytes= device → AP = the device’s upload (↑)The WAN interface is not inverted:
wan rx_bytesreally is internet download. Verified in production by cross-check: a device reading 35.4 ↑ while the WAN independently read 35.1 ↑ at the same moment.
4. osd-overlay, never camwall-reload
camwall-reload issues loadfile on the birdseye stream, which restarts video playback. Fine
for the weekly TOP KÉPEK refresh; catastrophic at 5 s intervals — it would stutter the live cameras
continuously. osd-overlay over the existing /run/camwall.sock touches neither the stream nor the
lavfi filter chain, which also sidesteps the pad-config-fail incident that once killed the whole
composite (see SESSION-HANDOVER).
Untrusted input: LAN device names
Device names are attacker-influenceable — a DHCP hostname is whatever the client claims.
subprocess.run(..., text=True)decodes strictly and can crash a daemonNon-UTF-8 bytes in router output (most plausibly a device-supplied DHCP hostname) raise
UnicodeDecodeError, which is aValueErrorand therefore not caught byexcept (subprocess.SubprocessError, OSError). Useerrors="replace". Generic lesson: anything parsing LAN-supplied names is an untrusted input path.
- Escaping is necessary but not sufficient. ASS escaping (
\{,\},\\) does correctly prevent override-tag injection — validated against the box’s real libass 0.17.3 — but a 400-char DHCP hostname still renders straight across the 4K frame over the camera feeds. Names are truncated to 22 chars. - Camera VLAN excluded by construction. Only
phy0-ap0andphy1-ap0(ESSIDtelep1) are ever queried;phy1-ap1(telep-cc) is never referenced, so a camera cannot enrol as a “device”. Same failure mode as Camera-VLAN gotcha — telep-cc leaked an enrollable device, avoided structurally rather than by filtering. - Names are reused, not reinvented. Lookup order: the intruder alarm’s
~/alarm/trusted.json(the Telegram/renamenames) → the router’s DHCP hostname →eszköz-<last4>. No second naming system was created.
SSH ControlMaster hides credential loss
Removing the router SSH key showed no degradation — because of multiplexing
A test that deleted the key appeared to prove the daemon didn’t need it. It did: the multiplexed ControlPersist connection was already authenticated and the key file is never re-read. To actually test degradation you must also drop the control socket (
/run/wifi-usage/router.sock). Operationally: the daemon keeps polling for up to ~60 s after key loss before it notices.
Socket ownership
/run/camwall.sock is srw------- root root and camwall.service runs as root — so anything
pushing overlays must also run as root. As levander the connect fails with EACCES. This is
why wifi-usage.service is a root unit.
Layout & behaviour
- Occupies the previously-empty strip at y 1880–2140, below the TOP KÉPEK row (which ends at y 1855). Nothing is ever drawn over the camera image.
- Fits 7 tiles. Beyond that: the 6 busiest plus a
+N továbbitile — a stated cap, not silent truncation. - Router failure keeps the last values with a
⚠marker; after 60 s they become—. The wall is never blanked. - Verified live: camwall
NRestartsunchanged by the daemon, 2026-07-17-intruder-alarm unaffected, 44/44 tests pass, self-healing after a camwall restart in ~23 s.
Open / known-minor
Recorded honestly — these are not fixed:
push_overlayreturnsTrueon “bytes written to socket”. It cannot confirm mpv actually applied the overlay.- Worst-case cycle (~24 s: 12 s SSH + 2×3 s IPC + …) is tight against
MAX_GAP_SECONDS = 30. A slow cycle blanks rates to—rather than crashing, but the margin is thin. - Each reconnect momentarily destroys the overlay before resending → sub-frame flicker.
_escapedoes not strip bidi/control characters (e.g.U+202E) — cosmetic scrambling only, no injection path.- Continuous 60 s stutter-free playback was judged from periodic framebuffer grabs, not video. A human eye should confirm.
Related
- 2026-07-22-wifi-usage-strip-design — the design this implements
- 2026-07-22-wifi-usage-strip-plan — the task-by-task implementation plan
- telep-mainframe — the host; the camwall and its mpv IPC socket
- telep-router — the data source (
iw station dump, WAN counters), LAN-only dropbear - 2026-07-17-intruder-alarm — shares the router SSH key, the
telep1AP map, andtrusted.jsonnames - SESSION-HANDOVER
- homelab
- LOG
- TOPICS