v1.10.0
Remote-control buttons — the D-pad, menu, info, setup, back, exit and the numeric keys — are now a real, usable API. They have been sitting in the codebase unreachable since before this library was public.
New — remote keys (#46)
from lyngdorf import RemoteKey
if receiver.has_remote_keys:
receiver.press(RemoteKey.MENU)
receiver.send_remote_commands(["down", "down", "enter"], num_repeats=1)RemoteKey, press(), send_remote_commands(), available_remote_keys and has_remote_keys.
Strings are the primary interface, not the enum. The consumer is Home Assistant's remote platform, whose RemoteEntity.async_send_command is handed Iterable[str] — so the integration needs no translation layer:
async def async_send_command(self, command, **kwargs):
self._receiver.send_remote_commands(
command, num_repeats=kwargs.get(ATTR_NUM_REPEATS, 1)
)String resolution is case-insensitive, because YAML says up, scripts say UP and blueprints say Up.
The whole batch is validated before anything is sent. A typo in item five of six raises before the first of the other five reaches the device, rather than leaving it half-navigated through a menu on the way to discovering the mistake.
Capability is an explicit per-model set, never inferred. Inference is precisely how these commands stayed broken:
| Keys | |
|---|---|
| MP-40 / MP-50 / MP-60, P200 | 21 |
| P100 / P300 | 20 — no MULTIVIEW, which the P-series manual restricts to the P200 |
| TDAI-1120 / 2170 / 2210 / 3400 | none — has_remote_keys is False |
Internally the keys moved out of Msg into their own write-only table. Msg is a bidirectional registry — every entry is registered for an inbound callback and looked up for an outbound write — but the device never replies to a button press. Sitting in the wrong structure is why nothing looked wrong while they were unreachable: a registry entry whose callback never fires is indistinguishable from one that simply hasn't fired yet.
Fixed
TDAI2210Receiver was not exported. The class existed, async_create_receiver returned it, the model was in LyngdorfModel and in the README — but from lyngdorf import TDAI2210Receiver raised ImportError while all nine sibling classes imported fine. The new test derives from LyngdorfModel rather than a hand-written list, so a future model cannot be added without exporting its class.
Tests no longer hang for two minutes on the failure path (#45). An autouse fixture disconnects every receiver a test connects, whether it passed, failed or raised. LyngdorfApi's poll loop runs while self._connection_enabled:, and only async_disconnect() clears that flag — so a test that failed before its own disconnect left the poll retrying indefinitely, each retry stranding another blocking HTTP call in an executor thread. Teardown is bounded at two seconds, so a receiver that refuses to stop is reported rather than stalling the suite.
This does not make those calls cancellable. An http.client request already in flight in an executor thread still cannot be interrupted, and KNOWN_ISSUES.md was retitled to say so plainly rather than claim the problem solved.
Documentation
Every supported model now links to its vendor product page, and a capability matrix states what this library supports per model — Zone B, video, surround trims, streaming, remote-key count, volume and trim ranges with their differing steps, lip sync and MAXVOL.
The matrix is generated from ModelConfig and a test regenerates every cell and compares, so it fails rather than rots when a model gains a feature.
The examples were also brought up to date: monitor.py had no visibility of play mode, transport capability or remote keys, and get_set_volume.py demonstrated the exact connect-without-disconnect pattern that #45 exists to prevent.
Verification
660 tests, no device required. CI now runs on non-deprecated actions, and its python-version matrix is actually wired to setup-python — previously the matrix was decorative, so adding a version would have silently run 3.11 twice.
Three defects in the remote-key work were caught in review before merge and are worth naming, because green tests did not catch any of them:
num_repeatsnested backwards, sending112233where Home Assistant means123123. The original test used a single-key batch, which cannot distinguish the two orderings. Verified against Broadlink and Harmony upstream.BACKwas removed from the MP models on the strength of the manuals. A real MP-60 on firmware 5.4.2 echoes#BACKat!VERB(2)while rejecting deliberate garbage in the same session — the manuals are simply missing it.MULTIVIEWwas advertised on the P100 and P300 from a shared table, contradicting the P-series manual's explicit "P200 only".
Every MP wire token was confirmed accepted by real hardware. Functional confirmation that the buttons drive the on-screen menu rests on jsoutter/uc-intg-lyngdorf binding these same commands to a physical D-pad in a shipping product, not on a direct observation here.