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:
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
Reference in New Issue
Block a user