def _device_key_to_bluetooth_entity_key( device_key: DeviceKey, ) -> PassiveBluetoothEntityKey: """Convert a device key to an entity key.""" return PassiveBluetoothEntityKey(device_key.key, device_key.device_id)
async def test_integration_with_entity_without_a_device(hass, mock_bleak_scanner_start): """Test integration with PassiveBluetoothCoordinatorEntity with no device.""" await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) @callback def _mock_update_method( service_info: BluetoothServiceInfo, ) -> dict[str, str]: return {"test": "data"} @callback def _async_generate_mock_data( data: dict[str, str], ) -> PassiveBluetoothDataUpdate: """Generate mock data.""" return NO_DEVICES_PASSIVE_BLUETOOTH_DATA_UPDATE coordinator = PassiveBluetoothProcessorCoordinator( hass, _LOGGER, "aa:bb:cc:dd:ee:ff", BluetoothScanningMode.ACTIVE, _mock_update_method, ) assert coordinator.available is False # no data yet saved_callback = None def _async_register_callback(_hass, _callback, _matcher, _mode): nonlocal saved_callback saved_callback = _callback return lambda: None processor = PassiveBluetoothDataProcessor(_async_generate_mock_data) with patch( "homeassistant.components.bluetooth.update_coordinator.async_register_callback", _async_register_callback, ): coordinator.async_register_processor(processor) cancel_coordinator = coordinator.async_start() mock_add_entities = MagicMock() processor.async_add_entities_listener( PassiveBluetoothProcessorEntity, mock_add_entities, ) saved_callback(NO_DEVICES_BLUETOOTH_SERVICE_INFO, BluetoothChange.ADVERTISEMENT) # First call with just the remote sensor entities results in them being added assert len(mock_add_entities.mock_calls) == 1 saved_callback(NO_DEVICES_BLUETOOTH_SERVICE_INFO, BluetoothChange.ADVERTISEMENT) # Second call with just the remote sensor entities does not add them again assert len(mock_add_entities.mock_calls) == 1 entities = mock_add_entities.mock_calls[0][1][0] entity_one: PassiveBluetoothProcessorEntity = entities[0] entity_one.hass = hass assert entity_one.available is True assert entity_one.unique_id == "aa:bb:cc:dd:ee:ff-temperature" assert entity_one.device_info == { "identifiers": {("bluetooth", "aa:bb:cc:dd:ee:ff")}, "name": "Generic", } assert entity_one.entity_key == PassiveBluetoothEntityKey( key="temperature", device_id=None ) cancel_coordinator()
async def test_basic_usage(hass, mock_bleak_scanner_start): """Test basic usage of the PassiveBluetoothProcessorCoordinator.""" await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) @callback def _mock_update_method( service_info: BluetoothServiceInfo, ) -> dict[str, str]: return {"test": "data"} @callback def _async_generate_mock_data( data: dict[str, str], ) -> PassiveBluetoothDataUpdate: """Generate mock data.""" assert data == {"test": "data"} return GENERIC_PASSIVE_BLUETOOTH_DATA_UPDATE coordinator = PassiveBluetoothProcessorCoordinator( hass, _LOGGER, "aa:bb:cc:dd:ee:ff", BluetoothScanningMode.ACTIVE, _mock_update_method, ) assert coordinator.available is False # no data yet saved_callback = None def _async_register_callback(_hass, _callback, _matcher, _mode): nonlocal saved_callback saved_callback = _callback return lambda: None processor = PassiveBluetoothDataProcessor(_async_generate_mock_data) with patch( "homeassistant.components.bluetooth.update_coordinator.async_register_callback", _async_register_callback, ): unregister_processor = coordinator.async_register_processor(processor) cancel_coordinator = coordinator.async_start() entity_key = PassiveBluetoothEntityKey("temperature", None) entity_key_events = [] all_events = [] mock_entity = MagicMock() mock_add_entities = MagicMock() def _async_entity_key_listener(data: PassiveBluetoothDataUpdate | None) -> None: """Mock entity key listener.""" entity_key_events.append(data) cancel_async_add_entity_key_listener = processor.async_add_entity_key_listener( _async_entity_key_listener, entity_key, ) def _all_listener(data: PassiveBluetoothDataUpdate | None) -> None: """Mock an all listener.""" all_events.append(data) cancel_listener = processor.async_add_listener( _all_listener, ) cancel_async_add_entities_listener = processor.async_add_entities_listener( mock_entity, mock_add_entities, ) saved_callback(GENERIC_BLUETOOTH_SERVICE_INFO, BluetoothChange.ADVERTISEMENT) # Each listener should receive the same data # since both match assert len(entity_key_events) == 1 assert len(all_events) == 1 # There should be 4 calls to create entities assert len(mock_entity.mock_calls) == 2 saved_callback(GENERIC_BLUETOOTH_SERVICE_INFO, BluetoothChange.ADVERTISEMENT) # Each listener should receive the same data # since both match assert len(entity_key_events) == 2 assert len(all_events) == 2 # On the second, the entities should already be created # so the mock should not be called again assert len(mock_entity.mock_calls) == 2 cancel_async_add_entity_key_listener() cancel_listener() cancel_async_add_entities_listener() saved_callback(GENERIC_BLUETOOTH_SERVICE_INFO, BluetoothChange.ADVERTISEMENT) # Each listener should not trigger any more now # that they were cancelled assert len(entity_key_events) == 2 assert len(all_events) == 2 assert len(mock_entity.mock_calls) == 2 assert coordinator.available is True unregister_processor() cancel_coordinator()
async def test_integration_with_entity(hass, mock_bleak_scanner_start): """Test integration of PassiveBluetoothProcessorCoordinator with PassiveBluetoothCoordinatorEntity.""" await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) update_count = 0 @callback def _mock_update_method( service_info: BluetoothServiceInfo, ) -> dict[str, str]: return {"test": "data"} @callback def _async_generate_mock_data( data: dict[str, str], ) -> PassiveBluetoothDataUpdate: """Generate mock data.""" nonlocal update_count update_count += 1 if update_count > 2: return GOVEE_B5178_PRIMARY_AND_REMOTE_PASSIVE_BLUETOOTH_DATA_UPDATE return GOVEE_B5178_REMOTE_PASSIVE_BLUETOOTH_DATA_UPDATE coordinator = PassiveBluetoothProcessorCoordinator( hass, _LOGGER, "aa:bb:cc:dd:ee:ff", BluetoothScanningMode.ACTIVE, _mock_update_method, ) assert coordinator.available is False # no data yet saved_callback = None def _async_register_callback(_hass, _callback, _matcher, _mode): nonlocal saved_callback saved_callback = _callback return lambda: None processor = PassiveBluetoothDataProcessor(_async_generate_mock_data) with patch( "homeassistant.components.bluetooth.update_coordinator.async_register_callback", _async_register_callback, ): coordinator.async_register_processor(processor) cancel_coordinator = coordinator.async_start() processor.async_add_listener(MagicMock()) mock_add_entities = MagicMock() processor.async_add_entities_listener( PassiveBluetoothProcessorEntity, mock_add_entities, ) saved_callback(GENERIC_BLUETOOTH_SERVICE_INFO, BluetoothChange.ADVERTISEMENT) # First call with just the remote sensor entities results in them being added assert len(mock_add_entities.mock_calls) == 1 saved_callback(GENERIC_BLUETOOTH_SERVICE_INFO, BluetoothChange.ADVERTISEMENT) # Second call with just the remote sensor entities does not add them again assert len(mock_add_entities.mock_calls) == 1 saved_callback(GENERIC_BLUETOOTH_SERVICE_INFO, BluetoothChange.ADVERTISEMENT) # Third call with primary and remote sensor entities adds the primary sensor entities assert len(mock_add_entities.mock_calls) == 2 saved_callback(GENERIC_BLUETOOTH_SERVICE_INFO, BluetoothChange.ADVERTISEMENT) # Forth call with both primary and remote sensor entities does not add them again assert len(mock_add_entities.mock_calls) == 2 entities = [ *mock_add_entities.mock_calls[0][1][0], *mock_add_entities.mock_calls[1][1][0], ] entity_one: PassiveBluetoothProcessorEntity = entities[0] entity_one.hass = hass assert entity_one.available is True assert entity_one.unique_id == "aa:bb:cc:dd:ee:ff-temperature-remote" assert entity_one.device_info == { "identifiers": {("bluetooth", "aa:bb:cc:dd:ee:ff-remote")}, "manufacturer": "Govee", "model": "H5178-REMOTE", "name": "B5178D6FB Remote", } assert entity_one.entity_key == PassiveBluetoothEntityKey( key="temperature", device_id="remote" ) cancel_coordinator()
async def test_integration_multiple_entity_platforms(hass, mock_bleak_scanner_start): """Test integration of PassiveBluetoothProcessorCoordinator with multiple platforms.""" await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) @callback def _mock_update_method( service_info: BluetoothServiceInfo, ) -> dict[str, str]: return {"test": "data"} coordinator = PassiveBluetoothProcessorCoordinator( hass, _LOGGER, "aa:bb:cc:dd:ee:ff", BluetoothScanningMode.ACTIVE, _mock_update_method, ) assert coordinator.available is False # no data yet saved_callback = None def _async_register_callback(_hass, _callback, _matcher, _mode): nonlocal saved_callback saved_callback = _callback return lambda: None binary_sensor_processor = PassiveBluetoothDataProcessor( lambda service_info: BINARY_SENSOR_PASSIVE_BLUETOOTH_DATA_UPDATE ) sesnor_processor = PassiveBluetoothDataProcessor( lambda service_info: SENSOR_PASSIVE_BLUETOOTH_DATA_UPDATE ) with patch( "homeassistant.components.bluetooth.update_coordinator.async_register_callback", _async_register_callback, ): coordinator.async_register_processor(binary_sensor_processor) coordinator.async_register_processor(sesnor_processor) cancel_coordinator = coordinator.async_start() binary_sensor_processor.async_add_listener(MagicMock()) sesnor_processor.async_add_listener(MagicMock()) mock_add_sensor_entities = MagicMock() mock_add_binary_sensor_entities = MagicMock() sesnor_processor.async_add_entities_listener( PassiveBluetoothProcessorEntity, mock_add_sensor_entities, ) binary_sensor_processor.async_add_entities_listener( PassiveBluetoothProcessorEntity, mock_add_binary_sensor_entities, ) saved_callback(GENERIC_BLUETOOTH_SERVICE_INFO, BluetoothChange.ADVERTISEMENT) # First call with just the remote sensor entities results in them being added assert len(mock_add_binary_sensor_entities.mock_calls) == 1 assert len(mock_add_sensor_entities.mock_calls) == 1 binary_sesnor_entities = [ *mock_add_binary_sensor_entities.mock_calls[0][1][0], ] sesnor_entities = [ *mock_add_sensor_entities.mock_calls[0][1][0], ] sensor_entity_one: PassiveBluetoothProcessorEntity = sesnor_entities[0] sensor_entity_one.hass = hass assert sensor_entity_one.available is True assert sensor_entity_one.unique_id == "aa:bb:cc:dd:ee:ff-pressure" assert sensor_entity_one.device_info == { "identifiers": {("bluetooth", "aa:bb:cc:dd:ee:ff")}, "manufacturer": "Test Manufacturer", "model": "Test Model", "name": "Test Device", } assert sensor_entity_one.entity_key == PassiveBluetoothEntityKey( key="pressure", device_id=None ) binary_sensor_entity_one: PassiveBluetoothProcessorEntity = binary_sesnor_entities[ 0 ] binary_sensor_entity_one.hass = hass assert binary_sensor_entity_one.available is True assert binary_sensor_entity_one.unique_id == "aa:bb:cc:dd:ee:ff-motion" assert binary_sensor_entity_one.device_info == { "identifiers": {("bluetooth", "aa:bb:cc:dd:ee:ff")}, "manufacturer": "Test Manufacturer", "model": "Test Model", "name": "Test Device", } assert binary_sensor_entity_one.entity_key == PassiveBluetoothEntityKey( key="motion", device_id=None ) cancel_coordinator()
rssi=-95, manufacturer_data={ 1: b"\x01\x01\x01\x01\x01\x01\x01\x01", }, service_data={}, service_uuids=[], source="local", ) GENERIC_PASSIVE_BLUETOOTH_DATA_UPDATE = PassiveBluetoothDataUpdate( devices={ None: DeviceInfo( name="Test Device", model="Test Model", manufacturer="Test Manufacturer" ), }, entity_data={ PassiveBluetoothEntityKey("temperature", None): 14.5, PassiveBluetoothEntityKey("pressure", None): 1234, }, entity_names={ PassiveBluetoothEntityKey("temperature", None): "Temperature", PassiveBluetoothEntityKey("pressure", None): "Pressure", }, entity_descriptions={ PassiveBluetoothEntityKey("temperature", None): SensorEntityDescription( key="temperature", native_unit_of_measurement=TEMP_CELSIUS, device_class=SensorDeviceClass.TEMPERATURE, ), PassiveBluetoothEntityKey("pressure", None): SensorEntityDescription( key="pressure", native_unit_of_measurement="hPa",