User report: with 4 spools mapped, every print deducted the same
several-gram amount from ALL 4 spools regardless of which one was
actually printing with, adding up to ~150g of drift over time on an
unused spool.
Root cause: _spoolman_last_sync was reset to 0.0 (epoch) at print
start instead of the current time. The poll loop calls
_spoolman_sync_midprint() before _spoolman_attribute_tick() in the
same iteration, so with last_sync=0.0 the sync-due check was true on
the very first tick after print start - before any per-slot
attribution existed yet. _spoolman_unreported() then fell back to
splitting the printer's full (possibly already nonzero) supplies_usage
equally across every mapped spool, matching the reported log
(identical mm reported to all 4 spools, right after upload before the
purge even started).
Fixes:
- _on_print() now resets _spoolman_last_sync to time.time(), not 0.0,
so the first sync check is only due after a real interval has
passed with attribution data available.
- _spoolman_unreported()'s fallback now only applies with exactly one
mapped slot (single-extruder, no ambiguity) - with multiple slots
and no attribution data it reports nothing instead of guessing,
since a wrong equal-split is worse than a temporarily-missed report.
Added tests/test_spoolman_unreported.py covering both fixes.
Adds an optional per-printer integration with KXGauge
(https://gitea.it-drui.de/viewit/kxgauge), a small ESP32 round-face
display that shows printer status as an emotion and hotend temperature
as a color ring. KXGauge only exposes a GET-only HTTP API with no
push/websocket, so the bridge actively pushes to it from the existing
MQTT callbacks (_on_temp for the heat ring, _on_print + offline
transitions for the emotion) whenever state actually changes, with a
built-in dedupe so it doesn't spam the device every poll tick.
- kxgauge_client.py: thin synchronous HTTP client (mirrors
spoolman_client.py's shape - called from the MQTT reader thread).
- New [kxgauge]/[kxgauge_mapping] config.ini sections; the mapping
(kobra_state -> KXGauge emotion) is user-editable with a sane
default and falls back per-key if only partially configured.
- Settings UI: new card under Integrations with enable/URL/target-temp
fields, a per-state emotion mapping list, and a connection-test
button (/api/kxgauge/test).
- Multi-printer aware: kxgauge_url/enabled/heat_peak merge per
[printer_N] like the existing power-switch settings.
Also fixes a real bug found while testing this: _find_config_path()
resolves to the live project config/config.ini, not a sandboxed path,
so any test hitting /api/settings POST without stubbing it out will
silently overwrite the real printer config. test_settings.py already
guards against this - test_kxgauge.py now does too.