Home Assistant integration, and the Python examples move beside the library - #5
Merged
Merged
Conversation
… the library The Python examples lived inside the package (`libkp.examples`), which is where neither the Rust (`rust/examples/`) nor the Swift (separate executable targets) examples live, and which would have shipped a Home Assistant integration in the wheel. They now sit in `python/examples/`: `uv run examples/meters.py`, `uv run --extra tui examples/meters_tui.py`. The console scripts are gone with them; the tests import the examples off a second pytest path. `python/examples/homeassistant/` is a custom integration (`custom_components/ kemper`) over the model, and the third front-end on the library after the two terminal views. It exposes only the slow lane — the rig, amp and cabinet names — plus one derived reading: an `active` binary sensor with a `last_activity` timestamp, computed from the meter stream inside the integration so Home Assistant never sees the 20 Hz frames. The detector is a per-frame integer compare and one lazily armed timer: two state writes per playing session however long it runs, with the quiet window and the level threshold as options that apply without reconnecting. Identity is the serial from discovery, so the device and its entities survive a DHCP lease change: setup asks the network where the serial is before dialing and follows it, a lost stream reloads the entry so the way back starts at discovery, and a manually entered host is identified with a directed poll so it is serial-keyed too. The CBOR control channel is left off — nothing here surfaces the morph, and it would cost the device a second socket for as long as Home Assistant runs. libkp is pure standard library, so it is vendored: the integration directory holds `libkp` as a symlink to the library and `build.py` dereferences it into `dist/custom_components/kemper/` (and a zip), which is the whole install. Tests run against libkp's `FakeDevice` under `pytest-homeassistant-custom-component`; CI gets a `homeassistant` job. Verified against a Profiler Player on 14.2.1, including the case where the stored address was stale and the serial answered from a new one. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KA85wBck75RTmQZ7SioUHV
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Two things, the second needing the first:
python/examples/— the Python examples move out of the package (libkp.examples) to sit beside the library, the wayrust/examples/and Swift's executable targets do.uv run examples/meters.py/uv run --extra tui examples/meters_tui.py; the console scripts go away. Nothing undersrc/libkp/changes.python/examples/homeassistant/— a Home Assistant custom integration,custom_components/kemper, over the device model.The integration
sensor.<name>_rig,_amp,_cabinet;binary_sensor.<name>_activeandsensor.<name>_last_activity.activeis the one consumer of the 20 Hz meter stream, and it never reaches HA: a per-frame integer compare onrig_out_level(post rig-volume, blind to the monitor/main knobs — reasoning inactivity.py), one lazily armed timer, two state writes per playing session. Quiet window (default 5 min) and level threshold (default 2 %) are options that apply in place — no reload, no new session for the device.SER#; a manually typed host is identified with a directed poll so it is too. Setup asks the network where the serial is before dialing and follows it (updates the stored host/name/firmware), and a lost stream reloads the entry so every reconnect starts at discovery. Re-adding a moved device updates the existing entry instead of duplicating it.ConfigEntryNotReadyspacing plus libkp's own per-peer cooldown, never a loop.custom_components/kemper/libkpis a symlink to../../../../src/libkp;build.pydereferences it intodist/custom_components/kemper/anddist/kemper-0.1.0.zip, andbuild.py --install <ha-config-dir>drops it straight into a config directory. README in the directory has the steps.FakeDeviceunderpytest-homeassistant-custom-component(HA 2026.8.3, Python 3.14): setup/unload, config flow (discovery, manual, serial adoption, moved-host update), the detector's transitions (a burst of frames writes no extra state), stale-host-then-serial-follows, hangup → reload, diagnostics, and the bundle build. NewhomeassistantCI job.Verified live
Against the Profiler Player (14.2.1) in a real
hass2026.8.3: discovery lists it, the flow creates the entry, the three sensors read the loaded rig; 0 coordinator updates in 60 s at rest; the options flow applies without a reload; and with the stored host deliberately set to the previous DHCP address, setup loggedProfiler … answered from 192.168.7.151 instead of 192.168.7.220; following it, rewrote the entry, and came up with the same entity ids.Not in this PR
Tempo, volumes, morph, effect slots, tuner — each is one
SensorEntityDescriptionrow when wanted. The morph would need the control channel back on.🤖 Generated with Claude Code
https://claude.ai/code/session_01KA85wBck75RTmQZ7SioUHV