Compare commits

...

2 Commits

Author SHA1 Message Date
J. Nick Koston bda7244e5a Extend INKBIRD fallback poll interval to 330s to match AUTO scan cadence 2026-05-24 08:55:24 -05:00
J. Nick Koston 3dc1b4cabc Request active scan cadence for INKBIRD passive sensors 2026-05-23 23:52:57 -05:00
9 changed files with 100 additions and 14 deletions
@@ -68,9 +68,20 @@ class ActiveBluetoothProcessorCoordinator[_DataT](
| None = None,
poll_debouncer: Debouncer[Coroutine[Any, Any, None]] | None = None,
connectable: bool = True,
scan_interval: float | None = None,
scan_duration: float | None = None,
) -> None:
"""Initialize the processor."""
super().__init__(hass, logger, address, mode, update_method, connectable)
super().__init__(
hass,
logger,
address,
mode,
update_method,
connectable,
scan_interval,
scan_duration,
)
self._needs_poll_method = needs_poll_method
self._poll_method = poll_method
@@ -298,9 +298,13 @@ class PassiveBluetoothProcessorCoordinator[_DataT](BasePassiveBluetoothCoordinat
mode: BluetoothScanningMode,
update_method: Callable[[BluetoothServiceInfoBleak], _DataT],
connectable: bool = False,
scan_interval: float | None = None,
scan_duration: float | None = None,
) -> None:
"""Initialize the coordinator."""
super().__init__(hass, logger, address, mode, connectable)
super().__init__(
hass, logger, address, mode, connectable, scan_interval, scan_duration
)
self._processors: list[PassiveBluetoothDataProcessor[Any, _DataT]] = []
self._update_method = update_method
self.last_update_success = True
@@ -30,6 +30,8 @@ class BasePassiveBluetoothCoordinator(ABC):
address: str,
mode: BluetoothScanningMode,
connectable: bool,
scan_interval: float | None = None,
scan_duration: float | None = None,
) -> None:
"""Initialize the coordinator."""
self.hass = hass
@@ -38,6 +40,8 @@ class BasePassiveBluetoothCoordinator(ABC):
self.connectable = connectable
self._on_stop: list[CALLBACK_TYPE] = []
self.mode = mode
self._scan_interval = scan_interval
self._scan_duration = scan_duration
self._last_unavailable_time = 0.0
self._last_name = address
# Subclasses are responsible for setting _available to True
@@ -92,6 +96,8 @@ class BasePassiveBluetoothCoordinator(ABC):
address=self.address, connectable=self.connectable
),
self.mode,
scan_interval=self._scan_interval,
scan_duration=self._scan_duration,
)
)
self._on_stop.append(
@@ -25,7 +25,16 @@ from .const import CONF_DEVICE_DATA, CONF_DEVICE_TYPE, DOMAIN
_LOGGER = logging.getLogger(__name__)
FALLBACK_POLL_INTERVAL = timedelta(seconds=180)
FALLBACK_POLL_INTERVAL = timedelta(seconds=330)
# INKBIRD devices fall back to a connectable poll once no active
# advertisement has been seen for the library's MIN_POLL_INTERVAL
# (currently 330 seconds, sized to outlast HA's 300 second default
# AUTO scan cadence). Ask AUTO-mode scanners to flip ACTIVE for a
# 10 second window every 165 seconds so we keep receiving
# advertisements and avoid the more expensive connect path.
ACTIVE_SCAN_INTERVAL = 165.0
ACTIVE_SCAN_DURATION = 10.0
class INKBIRDActiveBluetoothProcessorCoordinator(
@@ -57,6 +66,8 @@ class INKBIRDActiveBluetoothProcessorCoordinator(
needs_poll_method=self._async_needs_poll,
poll_method=self._async_poll_data,
connectable=False, # Polling only happens if active scanning is disabled
scan_interval=ACTIVE_SCAN_INTERVAL,
scan_duration=ACTIVE_SCAN_DURATION,
)
async def async_init(self) -> None:
@@ -703,7 +703,9 @@ async def test_exception_from_update_method(
assert coordinator.available is False # no data yet
saved_callback = None
def _async_register_callback(_hass, _callback, _matcher, _mode):
def _async_register_callback(
_hass, _callback, _matcher, _mode, *, scan_interval=None, scan_duration=None
):
nonlocal saved_callback
saved_callback = _callback
return lambda: None
@@ -768,7 +770,9 @@ async def test_bad_data_from_update_method(hass: HomeAssistant) -> None:
assert coordinator.available is False # no data yet
saved_callback = None
def _async_register_callback(_hass, _callback, _matcher, _mode):
def _async_register_callback(
_hass, _callback, _matcher, _mode, *, scan_interval=None, scan_duration=None
):
nonlocal saved_callback
saved_callback = _callback
return lambda: None
+6 -2
View File
@@ -501,7 +501,9 @@ async def test_async_step_reauth(hass: HomeAssistant) -> None:
entry.add_to_hass(hass)
saved_callback = None
def _async_register_callback(_hass, _callback, _matcher, _mode):
def _async_register_callback(
_hass, _callback, _matcher, _mode, *, scan_interval=None, scan_duration=None
):
nonlocal saved_callback
saved_callback = _callback
return lambda: None
@@ -541,7 +543,9 @@ async def test_async_step_reauth_wrong_key(hass: HomeAssistant) -> None:
entry.add_to_hass(hass)
saved_callback = None
def _async_register_callback(_hass, _callback, _matcher, _mode):
def _async_register_callback(
_hass, _callback, _matcher, _mode, *, scan_interval=None, scan_duration=None
):
nonlocal saved_callback
saved_callback = _callback
return lambda: None
+3 -1
View File
@@ -40,7 +40,9 @@ async def _setup_entry(
saved_callback: Callable[[object, BluetoothChange], None] | None = None
def _async_register_callback(_hass, _callback, _matcher, _mode):
def _async_register_callback(
_hass, _callback, _matcher, _mode, *, scan_interval=None, scan_duration=None
):
nonlocal saved_callback
saved_callback = _callback
return lambda: None
+35 -1
View File
@@ -16,12 +16,17 @@ from inkbird_ble import (
from inkbird_ble.parser import Model
from sensor_state_data import SensorDeviceClass
from homeassistant.components.bluetooth.manager import HomeAssistantBluetoothManager
from homeassistant.components.inkbird.const import (
CONF_DEVICE_DATA,
CONF_DEVICE_TYPE,
DOMAIN,
)
from homeassistant.components.inkbird.coordinator import FALLBACK_POLL_INTERVAL
from homeassistant.components.inkbird.coordinator import (
ACTIVE_SCAN_DURATION,
ACTIVE_SCAN_INTERVAL,
FALLBACK_POLL_INTERVAL,
)
from homeassistant.components.sensor import ATTR_STATE_CLASS
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT
@@ -259,6 +264,35 @@ async def test_notify_sensor(hass: HomeAssistant) -> None:
assert entry.data[CONF_DEVICE_DATA] == {"temp_unit": "C"}
async def test_active_scan_cadence(hass: HomeAssistant) -> None:
"""Test the coordinator registers an active scan cadence with the manager."""
entry = MockConfigEntry(
domain=DOMAIN,
unique_id="61DE521B-F0BF-9F44-64D4-75BBE1738105",
)
entry.add_to_hass(hass)
cancel = MagicMock()
with patch.object(
HomeAssistantBluetoothManager,
"async_register_active_scan",
return_value=cancel,
) as mock_register:
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
mock_register.assert_called_once_with(
"61DE521B-F0BF-9F44-64D4-75BBE1738105",
ACTIVE_SCAN_INTERVAL,
ACTIVE_SCAN_DURATION,
)
cancel.assert_not_called()
assert await hass.config_entries.async_unload(entry.entry_id)
await hass.async_block_till_done()
cancel.assert_called_once()
async def test_ibs_p02b_sensors(hass: HomeAssistant) -> None:
"""Test setting up creates the sensors for an IBS-P02B."""
entry = MockConfigEntry(
@@ -1162,7 +1162,9 @@ async def test_async_step_reauth_legacy(hass: HomeAssistant) -> None:
entry.add_to_hass(hass)
saved_callback = None
def _async_register_callback(_hass, _callback, _matcher, _mode):
def _async_register_callback(
_hass, _callback, _matcher, _mode, *, scan_interval=None, scan_duration=None
):
nonlocal saved_callback
saved_callback = _callback
return lambda: None
@@ -1211,7 +1213,9 @@ async def test_async_step_reauth_legacy_wrong_key(hass: HomeAssistant) -> None:
entry.add_to_hass(hass)
saved_callback = None
def _async_register_callback(_hass, _callback, _matcher, _mode):
def _async_register_callback(
_hass, _callback, _matcher, _mode, *, scan_interval=None, scan_duration=None
):
nonlocal saved_callback
saved_callback = _callback
return lambda: None
@@ -1268,7 +1272,9 @@ async def test_async_step_reauth_v4(hass: HomeAssistant) -> None:
entry.add_to_hass(hass)
saved_callback = None
def _async_register_callback(_hass, _callback, _matcher, _mode):
def _async_register_callback(
_hass, _callback, _matcher, _mode, *, scan_interval=None, scan_duration=None
):
nonlocal saved_callback
saved_callback = _callback
return lambda: None
@@ -1322,7 +1328,9 @@ async def test_async_step_reauth_v4_wrong_key(hass: HomeAssistant) -> None:
entry.add_to_hass(hass)
saved_callback = None
def _async_register_callback(_hass, _callback, _matcher, _mode):
def _async_register_callback(
_hass, _callback, _matcher, _mode, *, scan_interval=None, scan_duration=None
):
nonlocal saved_callback
saved_callback = _callback
return lambda: None
@@ -1384,7 +1392,9 @@ async def test_async_step_reauth_v4_from_cloud(hass: HomeAssistant) -> None:
entry.add_to_hass(hass)
saved_callback = None
def _async_register_callback(_hass, _callback, _matcher, _mode):
def _async_register_callback(
_hass, _callback, _matcher, _mode, *, scan_interval=None, scan_duration=None
):
nonlocal saved_callback
saved_callback = _callback
return lambda: None