IR remote: accept irserver's lowercase button and remote names

irserver (musicdolphin's IRTrans daemon) serves lircd's network protocol on port 8765,
but sends "key_play hauppauge" where lircd sends "KEY_PLAY Hauppauge", so every press
was dropped. Uppercase button names when parsing and compare the remote name
case-insensitively.

irserver also always reports repeat 00, so a held transport button is not debounced
there; volume ramps as intended.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-03 15:57:50 +02:00
parent f67f84ded3
commit 92644e9519
4 changed files with 39 additions and 2 deletions

View File

@@ -158,6 +158,7 @@ class LircConfig(_Strict):
port: int = Field(default=2222, ge=1, le=65535) port: int = Field(default=2222, ge=1, le=65535)
#: Only button events from this remote are acted on - other remotes registered #: Only button events from this remote are acted on - other remotes registered
#: with the same lircd (an LED remote, say) are ignored. #: with the same lircd (an LED remote, say) are ignored.
#: Compared case-insensitively: irserver lowercases what lircd capitalises.
remote_name: str = "Hauppauge" remote_name: str = "Hauppauge"
reconnect_interval: float = Field(default=5.0, gt=0) reconnect_interval: float = Field(default=5.0, gt=0)

View File

@@ -7,6 +7,15 @@ A line looks like::
that is ``<code> <repeat, hex> <button name> <remote name>``. ``repeat`` is ``00`` for that is ``<code> <repeat, hex> <button name> <remote name>``. ``repeat`` is ``00`` for
the first press and increments while the button is held - lircd has no separate the first press and increments while the button is held - lircd has no separate
key-up event, just repeats stopping. key-up event, just repeats stopping.
irserver (the IRTrans daemon, which serves this protocol on its own port 8765) sends
the same shape but lowercases the button and remote names and always reports repeat
``00``::
b7b2000000000000 00 key_play hauppauge
Button names are therefore uppercased here, so the rest of the code sees lircd's
spelling either way.
""" """
from __future__ import annotations from __future__ import annotations
@@ -34,4 +43,4 @@ def parse_line(line: str) -> LircButtonEvent | None:
repeat = int(repeat_hex, 16) repeat = int(repeat_hex, 16)
except ValueError: except ValueError:
return None return None
return LircButtonEvent(code=code, repeat=repeat, button=button, remote=remote) return LircButtonEvent(code=code, repeat=repeat, button=button.upper(), remote=remote)

View File

@@ -99,7 +99,7 @@ class LircService:
if not raw: if not raw:
return return
event = parse_line(raw.decode(errors="replace")) event = parse_line(raw.decode(errors="replace"))
if event is None or event.remote != self.config.remote_name: if event is None or event.remote.casefold() != self.config.remote_name.casefold():
continue continue
self._handle(event) self._handle(event)
finally: finally:

View File

@@ -60,6 +60,13 @@ def test_surrounding_whitespace_is_ignored() -> None:
assert parse_line(" 0000000000001781 00 BTN_1 Hauppauge \n") is not None assert parse_line(" 0000000000001781 00 BTN_1 Hauppauge \n") is not None
def test_irservers_lowercase_button_names_are_uppercased() -> None:
# Verbatim from irserver's lircd-compatible port on musicdolphin.
event = parse_line("b7b2000000000000 00 key_play hauppauge\n")
assert event is not None
assert event.button == "KEY_PLAY"
# --------------------------------------------------------------------------- service # --------------------------------------------------------------------------- service
@@ -195,6 +202,26 @@ async def test_events_from_another_remote_are_ignored(
assert not any(isinstance(event, IntentEvent) for event in seen) assert not any(isinstance(event, IntentEvent) for event in seen)
async def test_irservers_lowercase_lines_are_acted_on(
lircd: tuple[ScriptedLircd, asyncio.base_events.Server], sim: Simulation
) -> None:
station, server = lircd
port = server.sockets[0].getsockname()[1]
seen: list[Event] = []
sim.bus.subscribe_all(seen.append)
# The config still says "Hauppauge" (the default); irserver says "hauppauge".
task = await _run_service(sim, _lirc_config(port))
try:
await station.wait_connected()
await station.send("b7b2000000000000 00 key_play hauppauge")
await sim.bus.drain()
finally:
task.cancel()
assert [type(event) for event in seen if isinstance(event, IntentEvent)] == [PlayRequested]
async def test_transport_buttons_only_act_on_the_first_press( async def test_transport_buttons_only_act_on_the_first_press(
lircd: tuple[ScriptedLircd, asyncio.base_events.Server], sim: Simulation lircd: tuple[ScriptedLircd, asyncio.base_events.Server], sim: Simulation
) -> None: ) -> None: