diff --git a/homeassistant/components/govee_ble/sensor.py b/homeassistant/components/govee_ble/sensor.py index b2da37bdf7e..cbef769bdc9 100644 --- a/homeassistant/components/govee_ble/sensor.py +++ b/homeassistant/components/govee_ble/sensor.py @@ -110,7 +110,9 @@ async def async_setup_entry( GoveeBluetoothSensorEntity, async_add_entities ) ) - entry.async_on_unload(coordinator.async_register_processor(processor)) + entry.async_on_unload( + coordinator.async_register_processor(processor, SensorEntityDescription) + ) class GoveeBluetoothSensorEntity( diff --git a/tests/components/govee_ble/__init__.py b/tests/components/govee_ble/__init__.py index 54e7c1ee777..5dd67adb160 100644 --- a/tests/components/govee_ble/__init__.py +++ b/tests/components/govee_ble/__init__.py @@ -37,6 +37,31 @@ GVH5177_SERVICE_INFO = BluetoothServiceInfo( source="local", ) +GVH5178_REMOTE_SERVICE_INFO = BluetoothServiceInfo( + name="B51782BC8", + address="A4:C1:38:75:2B:C8", + rssi=-66, + manufacturer_data={ + 1: b"\x01\x01\x01\x00\x2a\xf7\x64\x00\x03", + 76: b"\x02\x15INTELLI_ROCKS_HWPu\xf2\xff\xc2", + }, + service_data={}, + service_uuids=["0000ec88-0000-1000-8000-00805f9b34fb"], + source="local", +) +GVH5178_PRIMARY_SERVICE_INFO = BluetoothServiceInfo( + name="B51782BC8", + address="A4:C1:38:75:2B:C8", + rssi=-66, + manufacturer_data={ + 1: b"\x01\x01\x00\x00\x2a\xf7\x64\x00\x03", + 76: b"\x02\x15INTELLI_ROCKS_HWPu\xf2\xff\xc2", + }, + service_data={}, + service_uuids=["0000ec88-0000-1000-8000-00805f9b34fb"], + source="local", +) + GVH5178_SERVICE_INFO_ERROR = BluetoothServiceInfo( name="B51782BC8", address="A4:C1:38:75:2B:C8", diff --git a/tests/components/govee_ble/test_sensor.py b/tests/components/govee_ble/test_sensor.py index 1408a35142a..185ae2404da 100644 --- a/tests/components/govee_ble/test_sensor.py +++ b/tests/components/govee_ble/test_sensor.py @@ -1,4 +1,11 @@ """Test the Govee BLE sensors.""" +from datetime import timedelta +import time +from unittest.mock import patch + +from homeassistant.components.bluetooth import ( + FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS, +) from homeassistant.components.govee_ble.const import DOMAIN from homeassistant.components.sensor import ATTR_STATE_CLASS from homeassistant.const import ( @@ -7,11 +14,20 @@ from homeassistant.const import ( STATE_UNAVAILABLE, ) from homeassistant.core import HomeAssistant +from homeassistant.util import dt as dt_util -from . import GVH5075_SERVICE_INFO, GVH5178_SERVICE_INFO_ERROR +from . import ( + GVH5075_SERVICE_INFO, + GVH5178_PRIMARY_SERVICE_INFO, + GVH5178_REMOTE_SERVICE_INFO, + GVH5178_SERVICE_INFO_ERROR, +) -from tests.common import MockConfigEntry -from tests.components.bluetooth import inject_bluetooth_service_info +from tests.common import MockConfigEntry, async_fire_time_changed +from tests.components.bluetooth import ( + inject_bluetooth_service_info, + patch_all_discovered_devices, +) async def test_sensors(hass: HomeAssistant) -> None: @@ -62,3 +78,80 @@ async def test_gvh5178_error(hass: HomeAssistant) -> None: assert await hass.config_entries.async_unload(entry.entry_id) await hass.async_block_till_done() + + +async def test_gvh5178_multi_sensor(hass: HomeAssistant) -> None: + """Test H5178 with a primary and remote sensor. + + The gateway sensor is responsible for broadcasting the state for + all sensors and it does so in many advertisements. We want + all the connected devices to stay available when the gateway + sensor is available. + """ + start_monotonic = time.monotonic() + entry = MockConfigEntry( + domain=DOMAIN, + unique_id="A4:C1:38:75:2B:C8", + ) + entry.add_to_hass(hass) + + assert await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert len(hass.states.async_all()) == 0 + inject_bluetooth_service_info(hass, GVH5178_REMOTE_SERVICE_INFO) + await hass.async_block_till_done() + assert len(hass.states.async_all()) == 3 + + temp_sensor = hass.states.get("sensor.b51782bc8_remote_temperature") + assert temp_sensor.state == "1.0" + + assert await hass.config_entries.async_unload(entry.entry_id) + await hass.async_block_till_done() + + # Fastforward time without BLE advertisements + monotonic_now = start_monotonic + FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS + 1 + + with patch( + "homeassistant.components.bluetooth.manager.MONOTONIC_TIME", + return_value=monotonic_now, + ), patch_all_discovered_devices([]): + async_fire_time_changed( + hass, + dt_util.utcnow() + + timedelta(seconds=FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS + 1), + ) + await hass.async_block_till_done() + + assert await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + temp_sensor = hass.states.get("sensor.b51782bc8_remote_temperature") + assert temp_sensor.state == STATE_UNAVAILABLE + + inject_bluetooth_service_info(hass, GVH5178_PRIMARY_SERVICE_INFO) + await hass.async_block_till_done() + + temp_sensor = hass.states.get("sensor.b51782bc8_remote_temperature") + assert temp_sensor.state == "1.0" + + primary_temp_sensor = hass.states.get("sensor.b51782bc8_primary_temperature") + assert primary_temp_sensor.state == "1.0" + + # Fastforward time without BLE advertisements + with patch( + "homeassistant.components.bluetooth.manager.MONOTONIC_TIME", + return_value=monotonic_now, + ), patch_all_discovered_devices([]): + async_fire_time_changed( + hass, + dt_util.utcnow() + + timedelta(seconds=FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS + 1), + ) + await hass.async_block_till_done() + + temp_sensor = hass.states.get("sensor.b51782bc8_remote_temperature") + assert temp_sensor.state == STATE_UNAVAILABLE + + primary_temp_sensor = hass.states.get("sensor.b51782bc8_primary_temperature") + assert primary_temp_sensor.state == STATE_UNAVAILABLE