From bcacb3e6f2a09a4238a7ba91955721e744e259b5 Mon Sep 17 00:00:00 2001 From: raman325 <7243222+raman325@users.noreply.github.com> Date: Tue, 20 Jan 2026 11:44:32 -0500 Subject: [PATCH] fix: immediately update UI when accesslimit_count changes When accesslimit_count changes (either by code usage or user input), update the UI immediately instead of waiting for coordinator refresh. Changes: - Add async_set_updated_data() calls in _lock_unlocked() after decrementing accesslimit_count (for both parent and child locks) - Call _update_slot() after decrement to check if slot should be deactivated and clear PIN from lock when count reaches 0 - Add async_write_ha_state() in number entity's async_set_native_value() for immediate feedback when user changes the value The UI notification happens before _update_slot() so users see the count change immediately, even before the potentially slow PIN clear operation completes. Co-Authored-By: Claude Opus 4.5 --- custom_components/keymaster/coordinator.py | 10 ++++++++++ custom_components/keymaster/number.py | 1 + tests/test_number.py | 17 ++++++++++++----- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/custom_components/keymaster/coordinator.py b/custom_components/keymaster/coordinator.py index 9c4f70fc..b51531b9 100644 --- a/custom_components/keymaster/coordinator.py +++ b/custom_components/keymaster/coordinator.py @@ -836,10 +836,20 @@ async def _lock_unlocked( parent_kmlock.code_slots[code_slot_num].accesslimit_count = ( accesslimit_count - 1 ) + # Immediately notify entities of the count change + self.async_set_updated_data(dict(self.kmlocks)) + # Check if slot should be deactivated (e.g., count reached 0) + await self._update_slot( + parent_kmlock, parent_kmlock.code_slots[code_slot_num], code_slot_num + ) elif kmlock.code_slots[code_slot_num].accesslimit_count_enabled: accesslimit_count = kmlock.code_slots[code_slot_num].accesslimit_count if isinstance(accesslimit_count, int) and accesslimit_count > 0: kmlock.code_slots[code_slot_num].accesslimit_count = accesslimit_count - 1 + # Immediately notify entities of the count change + self.async_set_updated_data(dict(self.kmlocks)) + # Check if slot should be deactivated (e.g., count reached 0) + await self._update_slot(kmlock, kmlock.code_slots[code_slot_num], code_slot_num) if kmlock.code_slots[code_slot_num].notifications and not kmlock.lock_notifications: if kmlock.code_slots[code_slot_num].name: diff --git a/custom_components/keymaster/number.py b/custom_components/keymaster/number.py index 44ede98d..79875d68 100644 --- a/custom_components/keymaster/number.py +++ b/custom_components/keymaster/number.py @@ -193,4 +193,5 @@ async def async_set_native_value(self, value: float) -> None: value = int(value) if self._set_property_value(value): self._attr_native_value = value + self.async_write_ha_state() # Immediate UI update await self.coordinator.async_refresh() diff --git a/tests/test_number.py b/tests/test_number.py index 3a4cdcef..0d01bab2 100644 --- a/tests/test_number.py +++ b/tests/test_number.py @@ -442,12 +442,16 @@ async def test_number_entity_async_set_value(hass: HomeAssistant, number_config_ entity = KeymasterNumber(entity_description=entity_description) - # Mock coordinator.async_refresh - with patch.object(coordinator, "async_refresh", new=AsyncMock()) as mock_refresh: + # Mock coordinator.async_refresh and async_write_ha_state (entity not registered) + with ( + patch.object(coordinator, "async_refresh", new=AsyncMock()) as mock_refresh, + patch.object(entity, "async_write_ha_state") as mock_write_state, + ): await entity.async_set_native_value(5) - # Should update value and call refresh + # Should update value, write state immediately, and call refresh assert entity._attr_native_value == 5 + mock_write_state.assert_called_once() mock_refresh.assert_called_once() @@ -541,8 +545,11 @@ async def test_number_entity_converts_float_to_int_for_accesslimit_count( entity = KeymasterNumber(entity_description=entity_description) - # Mock coordinator.async_refresh - with patch.object(coordinator, "async_refresh", new=AsyncMock()): + # Mock coordinator.async_refresh and async_write_ha_state (entity not registered) + with ( + patch.object(coordinator, "async_refresh", new=AsyncMock()), + patch.object(entity, "async_write_ha_state"), + ): # Pass a float value (like NumberEntity would from the frontend) await entity.async_set_native_value(5.0)