From 162de83e3f657b90de70f184d1b74f13d0b259ec Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 25 Nov 2021 13:55:12 -0600 Subject: [PATCH] A2 devices have yet another protocol (#179) --- flux_led/aiodevice.py | 4 +- flux_led/base_device.py | 88 ++++++++++++++++++++++------------------- flux_led/models_db.py | 21 +++------- flux_led/protocol.py | 72 ++++++++++++++++++++++++++++----- tests.py | 86 ++++++++++++++++++++++++++++++++++++---- tests_aio.py | 6 +-- 6 files changed, 198 insertions(+), 79 deletions(-) diff --git a/flux_led/aiodevice.py b/flux_led/aiodevice.py index 70b7e9d4..d086263d 100644 --- a/flux_led/aiodevice.py +++ b/flux_led/aiodevice.py @@ -214,7 +214,7 @@ async def async_set_brightness(self, brightness: int) -> None: await self.async_set_white_temp(self.color_temp, brightness) return if self.color_mode == COLOR_MODE_RGB: - await self.async_set_levels(*self.rgb_unscaled, brightness) + await self.async_set_levels(*self.rgb_unscaled, brightness=brightness) return if self.color_mode == COLOR_MODE_RGBW: await self.async_set_levels(*rgbw_brightness(self.rgbw, brightness)) @@ -275,7 +275,7 @@ def _async_process_message(self, msg): return assert self._updated_callback is not None prev_state = self.raw_state - if self.addressable and self._protocol.is_valid_addressable_response(msg): + if self._protocol.is_valid_addressable_response(msg): self.process_addressable_response(msg) if self._protocol.is_valid_state_response(msg): self.process_state_response(msg) diff --git a/flux_led/base_device.py b/flux_led/base_device.py index a6146887..db8e54c9 100644 --- a/flux_led/base_device.py +++ b/flux_led/base_device.py @@ -40,14 +40,12 @@ LevelWriteMode, ) from .models_db import ( - ADDRESSABLE_MODELS, BASE_MODE_MAP, CHANNEL_REMAP, MICROPHONE_MODELS, MODEL_DESCRIPTIONS, MODEL_MAP, MODEL_NUM_PROTOCOL, - ORIGINAL_ADDRESSABLE_MODELS, RGBW_PROTOCOL_MODELS, UNKNOWN_MODEL, USE_9BYTE_PROTOCOL_MODELS, @@ -69,18 +67,20 @@ PROTOCOL_LEDENET_8BYTE_DIMMABLE_EFFECTS, PROTOCOL_LEDENET_9BYTE, PROTOCOL_LEDENET_9BYTE_DIMMABLE_EFFECTS, - PROTOCOL_LEDENET_ADDRESSABLE, + PROTOCOL_LEDENET_ADDRESSABLE_A1, + PROTOCOL_LEDENET_ADDRESSABLE_A2, + PROTOCOL_LEDENET_ADDRESSABLE_A3, PROTOCOL_LEDENET_ORIGINAL, - PROTOCOL_LEDENET_ORIGINAL_ADDRESSABLE, LEDENETOriginalRawState, LEDENETRawState, ProtocolLEDENET8Byte, ProtocolLEDENET8ByteDimmableEffects, ProtocolLEDENET9Byte, ProtocolLEDENET9ByteDimmableEffects, - ProtocolLEDENETAddressable, + ProtocolLEDENETAddressableA1, + ProtocolLEDENETAddressableA2, + ProtocolLEDENETAddressableA3, ProtocolLEDENETOriginal, - ProtocolLEDENETOriginalAddressable, ) from .timer import BuiltInTimer from .utils import utils, white_levels_to_color_temp @@ -92,11 +92,24 @@ ProtocolLEDENET8ByteDimmableEffects, ProtocolLEDENET9Byte, ProtocolLEDENET9ByteDimmableEffects, - ProtocolLEDENETAddressable, + ProtocolLEDENETAddressableA1, + ProtocolLEDENETAddressableA2, + ProtocolLEDENETAddressableA3, ProtocolLEDENETOriginal, - ProtocolLEDENETOriginalAddressable, ] +ADDRESSABLE_PROTOCOLS = { + PROTOCOL_LEDENET_ADDRESSABLE_A1, + PROTOCOL_LEDENET_ADDRESSABLE_A2, + PROTOCOL_LEDENET_ADDRESSABLE_A3, +} + +OLD_EFFECTS_PROTOCOLS = {PROTOCOL_LEDENET_ADDRESSABLE_A1} +NEW_EFFECTS_PROTOCOLS = { + PROTOCOL_LEDENET_ADDRESSABLE_A2, + PROTOCOL_LEDENET_ADDRESSABLE_A3, +} + class DeviceType(Enum): Bulb = 0 @@ -124,6 +137,11 @@ def model_num(self) -> int: assert self.raw_state is not None return self.raw_state.model_num if self.raw_state else None + @property + def speed_adjust_off(self) -> int: + """Return if the speed of an effect can be adjusted while off.""" + return self.protocol != PROTOCOL_LEDENET_ADDRESSABLE_A1 + @property def model(self) -> str: """Return the human readable model description.""" @@ -152,29 +170,11 @@ def rgbwprotocol(self) -> bool: """Devices that don't require a separate rgb/w bit.""" return self.model_num in RGBW_PROTOCOL_MODELS - @property - def addressable(self) -> bool: - """Devices that have addressable leds.""" - return self._is_addressable(self.model_num) - - def _is_addressable(self, model_num: int) -> bool: - """Devices that have addressable leds.""" - return model_num in ADDRESSABLE_MODELS - @property def microphone(self) -> bool: """Devices that have a microphone built in.""" return self.model_num in MICROPHONE_MODELS - @property - def original_addressable(self) -> bool: - """Devices that have addressable leds using the original addressable protocol.""" - return self._is_original_addressable(self.model_num) - - def _is_original_addressable(self, model_num) -> bool: - """Devices that have addressable leds using the original addressable protocol.""" - return model_num in ORIGINAL_ADDRESSABLE_MODELS - @property def rgbwcapable(self) -> bool: """Devices that actually support rgbw.""" @@ -306,9 +306,10 @@ def warm_white(self) -> int: def effect_list(self) -> List[str]: """Return the list of available effects.""" effects: Iterable[str] = [] - if self.original_addressable: + protocol = self.protocol + if protocol in OLD_EFFECTS_PROTOCOLS: effects = ORIGINAL_ADDRESSABLE_EFFECT_ID_NAME.values() - elif self.addressable: + elif protocol in NEW_EFFECTS_PROTOCOLS: effects = ADDRESSABLE_EFFECT_ID_NAME.values() elif COLOR_MODES_RGB.intersection(self.color_modes): effects = EFFECT_LIST @@ -324,10 +325,11 @@ def effect(self) -> Optional[str]: if pattern_code == EFFECT_CUSTOM_CODE: return EFFECT_CUSTOM mode = self.raw_state.mode - if self.original_addressable: + protocol = self.protocol + if protocol in OLD_EFFECTS_PROTOCOLS: effect_id = (pattern_code << 8) + mode - 99 return ORIGINAL_ADDRESSABLE_EFFECT_ID_NAME.get(effect_id) - if self.addressable: + if protocol in NEW_EFFECTS_PROTOCOLS: if pattern_code == 0x25: return ADDRESSABLE_EFFECT_ID_NAME.get(mode) if pattern_code == 0x24: @@ -397,7 +399,7 @@ def _determineMode(self) -> Optional[str]: return MODE_PRESET elif BuiltInTimer.valid(pattern_code): return BuiltInTimer.valtostr(pattern_code) - elif self.addressable or self.original_addressable: + elif self.protocol in ADDRESSABLE_PROTOCOLS: return MODE_PRESET return None @@ -652,7 +654,7 @@ def getCCT(self) -> Tuple[int, int]: @property def speed(self) -> int: assert self.raw_state is not None - if self.addressable or self.original_addressable: + if self.protocol in ADDRESSABLE_PROTOCOLS: return self.raw_state.speed return utils.delayToSpeed(self.raw_state.speed) @@ -766,7 +768,7 @@ def _set_transition_complete_time(self) -> None: """ assert self.raw_state is not None latency = STATE_CHANGE_LATENCY - if self.addressable or self.original_addressable: + if self.protocol in ADDRESSABLE_PROTOCOLS: latency = ADDRESSABLE_STATE_CHANGE_LATENCY transition_time = latency + utils.speedToDelay(self.raw_state.speed) / 100 self._transition_complete_time = time.monotonic() + transition_time @@ -814,10 +816,12 @@ def setProtocol(self, protocol: str) -> None: self._protocol = ProtocolLEDENET9Byte() elif protocol == PROTOCOL_LEDENET_9BYTE_DIMMABLE_EFFECTS: self._protocol = ProtocolLEDENET9ByteDimmableEffects() - elif protocol == PROTOCOL_LEDENET_ADDRESSABLE: - self._protocol = ProtocolLEDENETAddressable() - elif protocol == PROTOCOL_LEDENET_ORIGINAL_ADDRESSABLE: - self._protocol = ProtocolLEDENETOriginalAddressable() + elif protocol == PROTOCOL_LEDENET_ADDRESSABLE_A3: + self._protocol = ProtocolLEDENETAddressableA3() + elif protocol == PROTOCOL_LEDENET_ADDRESSABLE_A2: + self._protocol = ProtocolLEDENETAddressableA2() + elif protocol == PROTOCOL_LEDENET_ADDRESSABLE_A1: + self._protocol = ProtocolLEDENETAddressableA1() else: raise ValueError(f"Invalid protocol: {protocol}") @@ -833,10 +837,11 @@ def _generate_preset_pattern( self, pattern: int, speed: int, brightness: int ) -> bytearray: """Generate the preset pattern protocol bytes.""" - if self.original_addressable: + protocol = self.protocol + if protocol in OLD_EFFECTS_PROTOCOLS: if pattern not in ORIGINAL_ADDRESSABLE_EFFECT_ID_NAME: raise ValueError("Pattern must be between 1 and 300") - elif self.addressable: + elif protocol in NEW_EFFECTS_PROTOCOLS: if pattern not in ADDRESSABLE_EFFECT_ID_NAME: raise ValueError("Pattern must be between 1 and 100") else: @@ -868,8 +873,9 @@ def _generate_custom_patterm( def _effect_to_pattern(self, effect: str) -> int: """Convert an effect to a pattern code.""" - if self.addressable: + protocol = self.protocol + if protocol in NEW_EFFECTS_PROTOCOLS: return ADDRESSABLE_EFFECT_NAME_ID[effect] - if self.original_addressable: + if protocol in OLD_EFFECTS_PROTOCOLS: return ORIGINAL_ADDRESSABLE_EFFECT_NAME_ID[effect] return PresetPattern.str_to_val(effect) diff --git a/flux_led/models_db.py b/flux_led/models_db.py index c41ee794..b4270d0b 100755 --- a/flux_led/models_db.py +++ b/flux_led/models_db.py @@ -21,9 +21,10 @@ PROTOCOL_LEDENET_8BYTE_DIMMABLE_EFFECTS, PROTOCOL_LEDENET_9BYTE, PROTOCOL_LEDENET_9BYTE_DIMMABLE_EFFECTS, - PROTOCOL_LEDENET_ADDRESSABLE, + PROTOCOL_LEDENET_ADDRESSABLE_A1, + PROTOCOL_LEDENET_ADDRESSABLE_A2, + PROTOCOL_LEDENET_ADDRESSABLE_A3, PROTOCOL_LEDENET_ORIGINAL, - PROTOCOL_LEDENET_ORIGINAL_ADDRESSABLE, ) LEDENETModel = namedtuple( @@ -484,7 +485,7 @@ models=[], description="RGB Symphony Original", # https://github.com/Danielhiversen/flux_led/pull/59/files#diff-09e60df19b61f5ceb8d2aef0ade582c2b84979cab660baa27c1ec65725144d76R776 always_writes_white_and_colors=False, - protocol=PROTOCOL_LEDENET_ORIGINAL_ADDRESSABLE, + protocol=PROTOCOL_LEDENET_ADDRESSABLE_A1, mode_to_color_mode={}, color_modes=COLOR_MODES_ADDRESSABLE, channel_map={}, @@ -495,7 +496,7 @@ models=["AK001-ZJ2104"], description="RGB Symphony 2", always_writes_white_and_colors=False, - protocol=PROTOCOL_LEDENET_ADDRESSABLE, + protocol=PROTOCOL_LEDENET_ADDRESSABLE_A2, mode_to_color_mode={}, color_modes=COLOR_MODES_ADDRESSABLE, channel_map={}, @@ -506,7 +507,7 @@ models=["K001-ZJ2148"], description="RGB Symphony 3", always_writes_white_and_colors=False, - protocol=PROTOCOL_LEDENET_ADDRESSABLE, + protocol=PROTOCOL_LEDENET_ADDRESSABLE_A3, mode_to_color_mode={}, color_modes=COLOR_MODES_ADDRESSABLE, channel_map={}, @@ -550,16 +551,6 @@ MODEL_MAP = {model.model_num: model for model in MODELS} MODEL_DESCRIPTIONS = {model.model_num: model.description for model in MODELS} CHANNEL_REMAP = {model.model_num: model.channel_map for model in MODELS} -ORIGINAL_ADDRESSABLE_MODELS = { - model.model_num - for model in MODELS - if model.protocol == PROTOCOL_LEDENET_ORIGINAL_ADDRESSABLE -} -ADDRESSABLE_MODELS = { - model.model_num - for model in MODELS - if model.protocol == PROTOCOL_LEDENET_ADDRESSABLE -} RGBW_PROTOCOL_MODELS = { model.model_num for model in MODELS if model.always_writes_white_and_colors } diff --git a/flux_led/protocol.py b/flux_led/protocol.py index b6a7e8af..5b7cebe5 100755 --- a/flux_led/protocol.py +++ b/flux_led/protocol.py @@ -16,8 +16,9 @@ PROTOCOL_LEDENET_9BYTE_DIMMABLE_EFFECTS = "LEDENET_DIMMABLE_EFFECTS" PROTOCOL_LEDENET_8BYTE = "LEDENET_8BYTE" # Previously was called None PROTOCOL_LEDENET_8BYTE_DIMMABLE_EFFECTS = "LEDENET_8BYTE_DIMMABLE_EFFECTS" -PROTOCOL_LEDENET_ADDRESSABLE = "LEDENET_ADDRESSABLE" -PROTOCOL_LEDENET_ORIGINAL_ADDRESSABLE = "LEDENET_ORIGINAL_ADDRESSABLE" +PROTOCOL_LEDENET_ADDRESSABLE_A1 = "LEDENET_ADDRESSABLE_A1" +PROTOCOL_LEDENET_ADDRESSABLE_A2 = "LEDENET_ADDRESSABLE_A2" +PROTOCOL_LEDENET_ADDRESSABLE_A3 = "LEDENET_ADDRESSABLE_A3" TRANSITION_BYTES = { TRANSITION_JUMP: 0x3B, @@ -135,6 +136,14 @@ class ProtocolBase: power_state_response_length = MSG_LENGTHS[MSG_POWER_STATE] + def is_start_of_addressable_response(self, data): + """Check if a message is the start of an addressable state response.""" + return False + + def is_valid_addressable_response(self, data): + """Check if a message is a valid addressable state response.""" + return False + def expected_response_length(self, data): """Return the number of bytes expected in the response. @@ -537,11 +546,11 @@ def construct_preset_pattern(self, pattern, speed, brightness): return self.construct_message(bytearray([0x38, pattern, delay, brightness])) -class ProtocolLEDENETOriginalAddressable(ProtocolLEDENET9Byte): +class ProtocolLEDENETAddressableA1(ProtocolLEDENET9Byte): @property def name(self): """The name of the protocol.""" - return PROTOCOL_LEDENET_ORIGINAL_ADDRESSABLE + return PROTOCOL_LEDENET_ADDRESSABLE_A1 @property def dimmable_effects(self): @@ -556,7 +565,50 @@ def construct_preset_pattern(self, pattern, speed, brightness): ) -class ProtocolLEDENETAddressable(ProtocolLEDENET9Byte): +class ProtocolLEDENETAddressableA2(ProtocolLEDENET9Byte): + @property + def name(self): + """The name of the protocol.""" + return PROTOCOL_LEDENET_ADDRESSABLE_A2 + + @property + def dimmable_effects(self): + """Protocol supports dimmable effects.""" + return True + + def construct_preset_pattern(self, pattern, speed, brightness): + """The bytes to send for a preset pattern.""" + return self.construct_message(bytearray([0x42, pattern, speed, brightness])) + + def construct_levels_change( + self, persist, red, green, blue, warm_white, cool_white, write_mode + ): + """The bytes to send for a level change request. + + white 41 01 ff ff ff 00 00 00 60 ff 00 00 9e + """ + preset_number = 0x01 # aka fixed color + return self.construct_message( + bytearray( + [ + 0x41, + preset_number, + red, + green, + blue, + 0x00, + 0x00, + 0x00, + 0x60, + 0xFF, + 0x00, + 0x00, + ] + ) + ) + + +class ProtocolLEDENETAddressableA3(ProtocolLEDENET9Byte): ADDRESSABLE_HEADER = [0xB0, 0xB1, 0xB2, 0xB3, 0x00, 0x01, 0x01] addressable_response_length = MSG_LENGTHS[MSG_ADDRESSABLE_STATE] @@ -565,6 +617,11 @@ def __init__(self): self._counter = 0 super().__init__() + @property + def name(self): + """The name of the protocol.""" + return PROTOCOL_LEDENET_ADDRESSABLE_A3 + @property def dimmable_effects(self): """Protocol supports dimmable effects.""" @@ -582,11 +639,6 @@ def is_valid_addressable_response(self, data): return False return self.is_checksum_correct(data) - @property - def name(self): - """The name of the protocol.""" - return PROTOCOL_LEDENET_ADDRESSABLE - def _increment_counter(self): """Increment the counter byte.""" self._counter += 1 diff --git a/tests.py b/tests.py index 33ec947a..a7afc95b 100644 --- a/tests.py +++ b/tests.py @@ -20,9 +20,10 @@ PROTOCOL_LEDENET_8BYTE, PROTOCOL_LEDENET_9BYTE, PROTOCOL_LEDENET_9BYTE_DIMMABLE_EFFECTS, - PROTOCOL_LEDENET_ADDRESSABLE, + PROTOCOL_LEDENET_ADDRESSABLE_A1, + PROTOCOL_LEDENET_ADDRESSABLE_A2, + PROTOCOL_LEDENET_ADDRESSABLE_A3, PROTOCOL_LEDENET_ORIGINAL, - PROTOCOL_LEDENET_ORIGINAL_ADDRESSABLE, ) from flux_led.utils import rgbw_brightness, rgbww_brightness @@ -1112,7 +1113,7 @@ def read_data(expected): @patch("flux_led.WifiLedBulb._send_msg") @patch("flux_led.WifiLedBulb._read_msg") @patch("flux_led.WifiLedBulb.connect") - def test_addressable_strip_effects(self, mock_connect, mock_read, mock_send): + def test_addressable_strip_effects_a2(self, mock_connect, mock_read, mock_send): calls = 0 def read_data(expected): @@ -1133,7 +1134,6 @@ def read_data(expected): mock_read.side_effect = read_data light = flux_led.WifiLedBulb("192.168.1.164") - self.assertEqual(light.addressable, True) self.assertEqual(light.model_num, 0xA2) self.assertEqual(light.microphone, True) self.assertEqual(light.model, "RGB Symphony 2 (0xA2)") @@ -1148,7 +1148,78 @@ def read_data(expected): light.__str__(), "ON [Color: (255, 0, 0) Brightness: 100% raw state: 129,162,35,97,65,16,255,0,0,0,4,0,240,235,]", ) - self.assertEqual(light.protocol, PROTOCOL_LEDENET_ADDRESSABLE) + self.assertEqual(light.protocol, PROTOCOL_LEDENET_ADDRESSABLE_A2) + self.assertEqual(light.is_on, True) + self.assertEqual(light.mode, "color") + self.assertEqual(light.warm_white, 0) + self.assertEqual(light.brightness, 255) + self.assertEqual(light.rgbwcapable, False) + self.assertEqual(light.device_type, flux_led.DeviceType.Bulb) + self.assertEqual(light.dimmable_effects, True) + + light.setRgbw(0, 255, 0) + self.assertEqual(mock_read.call_count, 2) + self.assertEqual(mock_send.call_count, 2) + self.assertEqual( + mock_send.call_args, + mock.call(bytearray(b"A\x01\x00\xff\x00\x00\x00\x00`\xff\x00\x00\xa0")), + ) + + light.set_effect("RBM 1", 50) + self.assertEqual(mock_read.call_count, 2) + self.assertEqual(mock_send.call_count, 3) + self.assertEqual( + mock_send.call_args, + mock.call(bytearray(b'B\x012d\xd9')), + ) + light._transition_complete_time = 0 + light.update_state() + self.assertEqual( + light.__str__(), + "ON [Pattern: RBM 1 (Speed 16%) raw state: 129,162,35,37,1,16,100,0,0,0,4,0,240,212,]", + ) + assert light.effect == "RBM 1" + assert light.getSpeed() == 16 + + @patch("flux_led.WifiLedBulb._send_msg") + @patch("flux_led.WifiLedBulb._read_msg") + @patch("flux_led.WifiLedBulb.connect") + def test_addressable_strip_effects_a3(self, mock_connect, mock_read, mock_send): + calls = 0 + + def read_data(expected): + nonlocal calls + calls += 1 + if calls == 1: + self.assertEqual(expected, 2) + return bytearray(b"\x81\xA3") + if calls == 2: + self.assertEqual(expected, 12) + return bytearray(b"#a\x41\x10\xff\x00\x00\x00\x04\x00\xf0\xec") + if calls == 3: + self.assertEqual(expected, 14) + return bytearray( + b"\x81\xA3#\x25\x01\x10\x64\x00\x00\x00\x04\x00\xf0\xd5" + ) + raise ValueError("Too many calls") + + mock_read.side_effect = read_data + light = flux_led.WifiLedBulb("192.168.1.164") + self.assertEqual(light.model_num, 0xA3) + self.assertEqual(light.microphone, True) + self.assertEqual(light.model, "RGB Symphony 3 (0xA3)") + assert len(light.effect_list) == 104 + assert light.color_modes == {COLOR_MODE_RGB} + + self.assertEqual(mock_read.call_count, 2) + self.assertEqual(mock_send.call_count, 1) + self.assertEqual(mock_send.call_args, mock.call(bytearray(LEDENET_STATE_QUERY))) + + self.assertEqual( + light.__str__(), + "ON [Color: (255, 0, 0) Brightness: 100% raw state: 129,163,35,97,65,16,255,0,0,0,4,0,240,236,]", + ) + self.assertEqual(light.protocol, PROTOCOL_LEDENET_ADDRESSABLE_A3) self.assertEqual(light.is_on, True) self.assertEqual(light.mode, "color") self.assertEqual(light.warm_white, 0) @@ -1182,7 +1253,7 @@ def read_data(expected): light.update_state() self.assertEqual( light.__str__(), - "ON [Pattern: RBM 1 (Speed 16%) raw state: 129,162,35,37,1,16,100,0,0,0,4,0,240,212,]", + "ON [Pattern: RBM 1 (Speed 16%) raw state: 129,163,35,37,1,16,100,0,0,0,4,0,240,213,]", ) assert light.effect == "RBM 1" assert light.getSpeed() == 16 @@ -1213,7 +1284,6 @@ def read_data(expected): mock_read.side_effect = read_data light = flux_led.WifiLedBulb("192.168.1.164") - self.assertEqual(light.original_addressable, True) self.assertEqual(light.dimmable_effects, False) self.assertEqual(light.model_num, 0xA1) self.assertEqual(light.model, "RGB Symphony Original (0xA1)") @@ -1228,7 +1298,7 @@ def read_data(expected): light.__str__(), "ON [Color: (255, 0, 0) Brightness: 100% raw state: 129,161,35,97,65,16,255,0,0,0,4,0,240,234,]", ) - self.assertEqual(light.protocol, PROTOCOL_LEDENET_ORIGINAL_ADDRESSABLE) + self.assertEqual(light.protocol, PROTOCOL_LEDENET_ADDRESSABLE_A1) self.assertEqual(light.is_on, True) self.assertEqual(light.mode, "color") self.assertEqual(light.warm_white, 0) diff --git a/tests_aio.py b/tests_aio.py index 8ac694a6..983503db 100644 --- a/tests_aio.py +++ b/tests_aio.py @@ -188,7 +188,7 @@ def _updated_callback(*args, **kwargs): task = asyncio.create_task(light.async_setup(_updated_callback)) await mock_aio_protocol() light._aio_protocol.data_received( - b"\x81\xA2#\x25\x01\x10\x64\x00\x00\x00\x04\x00\xf0\xd4" + b"\x81\xA3#\x25\x01\x10\x64\x00\x00\x00\x04\x00\xf0\xd5" ) await task @@ -274,10 +274,10 @@ def _updated_callback(*args, **kwargs): task = asyncio.create_task(light.async_setup(_updated_callback)) transport, protocol = await mock_aio_protocol() light._aio_protocol.data_received( - b"\x81\xA2#\x25\x01\x10\x64\x00\x00\x00\x04\x00\xf0\xd4" + b"\x81\xA3#\x25\x01\x10\x64\x00\x00\x00\x04\x00\xf0\xd5" ) await task - assert light.model_num == 0xA2 + assert light.model_num == 0xA3 transport.reset_mock() await light.async_set_effect("random", 50)