Skip to content

Commit cb55acc

Browse files
authored
Use latest service info for INKBIRD fallback poll recency check (home-assistant#172041)
1 parent d21c227 commit cb55acc

2 files changed

Lines changed: 49 additions & 3 deletions

File tree

homeassistant/components/inkbird/coordinator.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,18 @@ def _async_on_update(self, service_info: BluetoothServiceInfo) -> SensorUpdate:
128128
@callback
129129
def _async_schedule_poll(self, _: datetime) -> None:
130130
"""Schedule a poll of the device."""
131-
if self._last_service_info and self._async_needs_poll(
132-
self._last_service_info, self._last_poll
133-
):
131+
# ``self._last_service_info`` only tracks dispatched events, so when
132+
# the device keeps broadcasting the same payload (HA dedupes the
133+
# repeats before dispatch) its timestamp stops advancing. Pull the
134+
# latest service info from the bluetooth manager instead so the
135+
# recency check in ``poll_needed`` sees every observed advertisement.
136+
service_info = (
137+
async_last_service_info(self.hass, self.address, connectable=False)
138+
or self._last_service_info
139+
)
140+
if service_info and self.needs_poll(service_info):
141+
# Seed ``_last_service_info`` so the debounced poll has a service
142+
# info to hand to ``_async_poll_data``; the base ``_async_poll``
143+
# asserts on it.
144+
self._last_service_info = service_info
134145
self._debounced_poll.async_schedule_call()

tests/components/inkbird/test_sensor.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from inkbird_ble.parser import Model
1717
from sensor_state_data import SensorDeviceClass
1818

19+
from homeassistant.components.bluetooth import async_last_service_info
1920
from homeassistant.components.inkbird.const import (
2021
CONF_DEVICE_DATA,
2122
CONF_DEVICE_TYPE,
@@ -175,6 +176,40 @@ async def test_polling_sensor(hass: HomeAssistant) -> None:
175176
await hass.async_block_till_done()
176177

177178

179+
async def test_fallback_poll_queries_latest_service_info(hass: HomeAssistant) -> None:
180+
"""Fallback timer queries the bluetooth manager for the latest service info.
181+
182+
Regression test for the case where HA dedupes repeat advertisements with the
183+
same payload; ``_last_service_info`` then goes stale even though the device
184+
is still broadcasting, so the fallback recency check must consult the
185+
bluetooth manager's freshest observation instead of the dispatched event.
186+
"""
187+
entry = MockConfigEntry(
188+
domain=DOMAIN,
189+
unique_id="AA:BB:CC:DD:EE:FF",
190+
data={CONF_DEVICE_TYPE: "IBS-TH"},
191+
)
192+
entry.add_to_hass(hass)
193+
assert await hass.config_entries.async_setup(entry.entry_id)
194+
await hass.async_block_till_done()
195+
inject_bluetooth_service_info(hass, SPS_PASSIVE_SERVICE_INFO)
196+
await hass.async_block_till_done()
197+
198+
with patch(
199+
"homeassistant.components.inkbird.coordinator.async_last_service_info",
200+
wraps=async_last_service_info,
201+
) as mock_async_last_service_info:
202+
async_fire_time_changed(hass, dt_util.utcnow() + FALLBACK_POLL_INTERVAL)
203+
await hass.async_block_till_done()
204+
205+
mock_async_last_service_info.assert_called_once_with(
206+
hass, "AA:BB:CC:DD:EE:FF", connectable=False
207+
)
208+
209+
assert await hass.config_entries.async_unload(entry.entry_id)
210+
await hass.async_block_till_done()
211+
212+
178213
async def test_notify_sensor_no_advertisement(hass: HomeAssistant) -> None:
179214
"""Test setting up a notify sensor that has no advertisement."""
180215
entry = MockConfigEntry(

0 commit comments

Comments
 (0)