Skip to content

Home Assistant integration, and the Python examples move beside the library - #5

Merged
gotwalt merged 1 commit into
mainfrom
feat/home-assistant-integration
Aug 27, 2026
Merged

Home Assistant integration, and the Python examples move beside the library#5
gotwalt merged 1 commit into
mainfrom
feat/home-assistant-integration

Conversation

@gotwalt

@gotwalt gotwalt commented Aug 27, 2026

Copy link
Copy Markdown
Owner

What

Two things, the second needing the first:

  1. python/examples/ — the Python examples move out of the package (libkp.examples) to sit beside the library, the way rust/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 under src/libkp/ changes.
  2. python/examples/homeassistant/ — a Home Assistant custom integration, custom_components/kemper, over the device model.

The integration

  • Entities (v1, slow lane only): sensor.<name>_rig, _amp, _cabinet; binary_sensor.<name>_active and sensor.<name>_last_activity.
  • active is the one consumer of the 20 Hz meter stream, and it never reaches HA: a per-frame integer compare on rig_out_level (post rig-volume, blind to the monitor/main knobs — reasoning in activity.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.
  • Identity is the serial. Discovered devices are keyed by 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.
  • One socket. The CBOR control channel stays off (nothing here needs the morph); reconnects go through HA's ConfigEntryNotReady spacing plus libkp's own per-peer cooldown, never a loop.
  • Install: libkp is pure stdlib, so it is vendored. custom_components/kemper/libkp is a symlink to ../../../../src/libkp; build.py dereferences it into dist/custom_components/kemper/ and dist/kemper-0.1.0.zip, and build.py --install <ha-config-dir> drops it straight into a config directory. README in the directory has the steps.
  • Tests: 36, against libkp's FakeDevice under pytest-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. New homeassistant CI job.

Verified live

Against the Profiler Player (14.2.1) in a real hass 2026.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 logged Profiler … 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 SensorEntityDescription row when wanted. The morph would need the control channel back on.

🤖 Generated with Claude Code

https://claude.ai/code/session_01KA85wBck75RTmQZ7SioUHV

… 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
@gotwalt
gotwalt merged commit 077d3ff into main Aug 27, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant