Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions flux_led/base_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -1279,6 +1279,14 @@ def _set_protocol_from_msg(
# Extended state format (0xEA 0x81): model at byte 4, version at byte 5.
self._model_num = full_msg[4]
version_num = full_msg[5]
# A device that answers with the extended format cannot be driven by
# the 8-byte protocol: ProtocolLEDENET8Byte reports these responses as
# valid but never implements extended_state_to_state, so the abstract
# stub returns None and unpacking it raises TypeError. Probing hands us
# the probe protocol as the fallback, which is only used for model
# numbers missing from models_db, so point unknown models at a protocol
# that can actually parse what the device just sent.
fallback_protocol = PROTOCOL_LEDENET_EXTENDED_CUSTOM
else:
# Standard state format (0x81): model at byte 1, version at byte 10.
self._model_num = full_msg[1]
Expand Down
29 changes: 29 additions & 0 deletions tests/test_aio.py
Original file line number Diff line number Diff line change
Expand Up @@ -4244,6 +4244,35 @@ async def test_extended_state_color_parsing(
)


@pytest.mark.asyncio
async def test_setup_unknown_model_that_only_speaks_extended_state(mock_aio_protocol):
"""An unknown model_num answering only 0xEA 0x81 must still set up.

Captured from a Surplife AK001-ZJ21413 reporting model_num 0x77, which never
sends the 14-byte state response. Probing hands the 8-byte protocol in as the
fallback for unknown models, but ProtocolLEDENET8Byte reports extended
responses as valid while inheriting the abstract extended_state_to_state, so
unpacking its None result raised TypeError inside data_received.
"""
light = AIOWifiLedBulb("192.168.1.166", timeout=0.01)

def _updated_callback(*args, **kwargs):
pass

task = asyncio.create_task(light.async_setup(_updated_callback))
await mock_aio_protocol()
light._aio_protocol.data_received(
b"\xea\x81\x01\x00\x77\x09\x23\x25\x03\x50\xf0\x0b\xe4\x64"
b"\x00\x00\x01\x00\x64\x00\x00\x00\x80\x03\x00\x00\x00"
)
await task

assert light.available
assert light.model_num == 0x77
assert light.protocol == PROTOCOL_LEDENET_EXTENDED_CUSTOM
assert light.is_on is True


@pytest.mark.asyncio
async def test_setup_0x35_with_version_num_10(
mock_aio_protocol, caplog: pytest.LogCaptureFixture
Expand Down
Loading