Cancel otp sensor update timer when the entity is removed (#183320)

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Franck Nijhof
2026-09-27 17:17:06 +02:00
committed by GitHub
co-authored by Claude
parent 0c05cf4822
commit 742e1b6f07
2 changed files with 37 additions and 2 deletions
+12 -1
View File
@@ -1,5 +1,6 @@
"""Support for One-Time Password (OTP)."""
import asyncio
import time
from typing import override
@@ -39,6 +40,7 @@ class TOTPSensor(SensorEntity):
_attr_should_poll = False
_attr_native_value: StateType = None
_next_expiration: float | None = None
_update_timer: asyncio.TimerHandle | None = None
_attr_has_entity_name = True
_attr_name = None
@@ -58,6 +60,13 @@ class TOTPSensor(SensorEntity):
"""Handle when an entity is about to be added to Home Assistant."""
self._call_loop()
@override
async def async_will_remove_from_hass(self) -> None:
"""Cancel the update timer when the entity is removed."""
if self._update_timer:
self._update_timer.cancel()
self._update_timer = None
@callback
def _call_loop(self) -> None:
self._attr_native_value = self._otp.now()
@@ -66,4 +75,6 @@ class TOTPSensor(SensorEntity):
# Update must occur at even TIME_STEP, e.g. 12:00:00, 12:00:30,
# 12:01:00, etc. in order to have synced time (see RFC6238)
self._next_expiration = TIME_STEP - (time.time() % TIME_STEP)
self.hass.loop.call_later(self._next_expiration, self._call_loop)
self._update_timer = self.hass.loop.call_later(
self._next_expiration, self._call_loop
)
+25 -1
View File
@@ -1,9 +1,13 @@
"""Test the One-Time Password (OTP) init."""
from datetime import timedelta
from unittest.mock import MagicMock
from homeassistant.config_entries import ConfigEntryState
from homeassistant.core import HomeAssistant
from homeassistant.util import dt as dt_util
from tests.common import MockConfigEntry
from tests.common import MockConfigEntry, async_fire_time_changed
async def test_entry_setup_unload(
@@ -21,3 +25,23 @@ async def test_entry_setup_unload(
await hass.async_block_till_done()
assert otp_config_entry.state is ConfigEntryState.NOT_LOADED
async def test_update_timer_cancelled_on_unload(
hass: HomeAssistant,
otp_config_entry: MockConfigEntry,
mock_pyotp: MagicMock,
) -> None:
"""Test the sensor stops updating after the entry is unloaded."""
otp_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(otp_config_entry.entry_id)
await hass.async_block_till_done()
await hass.config_entries.async_unload(otp_config_entry.entry_id)
await hass.async_block_till_done()
mock_pyotp.TOTP().now.reset_mock()
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=1))
await hass.async_block_till_done()
mock_pyotp.TOTP().now.assert_not_called()