diff --git a/tests/test_ikea.py b/tests/test_ikea.py index 8208d55661..859654a561 100644 --- a/tests/test_ikea.py +++ b/tests/test_ikea.py @@ -132,9 +132,14 @@ def mock_read(attributes, manufacturer=None): @pytest.mark.parametrize( "firmware, pct_device, pct_correct, expected_pct_updates, expect_log_warning", ( - ("2.3.075", 50, 100, 1, False), - ("24.4.5", 50, 50, 2, False), - ("invalid_fw_string", 50, 50, 2, True), + ("1.0.024", 50, 100, 1, False), # old firmware, doubling + ("2.3.075", 50, 100, 1, False), # old firmware, doubling + ("2.4.5", 50, 50, 2, False), # new firmware, no doubling + ("3.0.0", 50, 50, 2, False), # new firmware, no doubling + ("24.4.5", 50, 50, 2, False), # new firmware, no doubling + ("invalid_fw_string_1", 50, 50, 2, False), # treated as new, no doubling + ("invalid.fw.string.2", 50, 50, 2, True), # treated as new, no doubling + log + ("", 50, 100, 1, False), # treated as old fw, doubling ), ) async def test_double_power_config_firmware( diff --git a/zhaquirks/ikea/__init__.py b/zhaquirks/ikea/__init__.py index ccc8be56ee..f64dcc7be4 100644 --- a/zhaquirks/ikea/__init__.py +++ b/zhaquirks/ikea/__init__.py @@ -209,26 +209,36 @@ async def bind(self): await self.endpoint.basic.read_attributes([Basic.AttributeDefs.sw_build_id.id]) return result - def _is_firmware_old(self): - """Checks if firmware is old or unknown.""" + def _is_firmware_new(self): + """Checks if new firmware is installed that does not require battery doubling.""" # get sw_build_id from attribute cache if available sw_build_id = self.endpoint.basic.get(Basic.AttributeDefs.sw_build_id.id, None) - # guard against possible future version formatting which includes more than just numbers - try: - # if first part of sw_build_id is 24 or higher, then firmware is new - if sw_build_id and int(sw_build_id.split(".")[0]) >= 24: - return False - except ValueError: - _LOGGER.warning( - "sw_build_id is not a number: %s for device %s", - sw_build_id, - self.endpoint.device.ieee, - ) - # sw_build_id is not a number, so it must be new firmware + # sw_build_id is not cached or empty, so we consider it old firmware for now + if not sw_build_id: return False - # unknown or old firmware + # split sw_build_id into parts to check for new firmware + split_fw_version = sw_build_id.split(".") + if len(split_fw_version) >= 2: + # guard against possible future version formatting which includes more than just numbers + try: + first_part = int(split_fw_version[0]) + second_part = int(split_fw_version[1]) + + # new firmware is either 24.4.5 or above, or 2.4.5 or above + # old firmware is 2.3.x or below + return first_part >= 3 or (first_part >= 2 and second_part >= 4) + except ValueError: + _LOGGER.warning( + "sw_build_id is not a number: %s for device %s", + sw_build_id, + self.endpoint.device.ieee, + ) + # sw_build_id is not a number, so it must be new firmware + return True + + # unknown formatting of sw_build_id, so it must be new firmware return True async def _read_fw_and_update_battery_pct(self, reported_battery_pct): @@ -238,7 +248,7 @@ async def _read_fw_and_update_battery_pct(self, reported_battery_pct): # check if sw_build_id was read successfully and new firmware is installed # if so, update cache with reported battery percentage (non-doubled) - if not self._is_firmware_old(): + if self._is_firmware_new(): self._update_attribute( PowerConfiguration.AttributeDefs.battery_percentage_remaining.id, reported_battery_pct, @@ -261,7 +271,7 @@ def _update_attribute(self, attrid, value): # double percentage if the firmware is old or unknown # the coroutine above will not have executed yet if the firmware is unknown, # so we double for now in that case too, and it updates again later if our doubling was wrong - if self._is_firmware_old(): + if not self._is_firmware_new(): value = value * 2 super()._update_attribute(attrid, value)