diff --git a/homeassistant/components/inkbird/__init__.py b/homeassistant/components/inkbird/__init__.py index c715c64599a13e0177e6c348b09fbeab2949ec95..9dd058e841ac43b4551e70fc889a2852d75287b6 100644 --- a/homeassistant/components/inkbird/__init__.py +++ b/homeassistant/components/inkbird/__init__.py @@ -4,17 +4,20 @@ from __future__ import annotations import logging -from inkbird_ble import INKBIRDBluetoothDeviceData +from inkbird_ble import INKBIRDBluetoothDeviceData, SensorUpdate -from homeassistant.components.bluetooth import BluetoothScanningMode +from homeassistant.components.bluetooth import ( + BluetoothScanningMode, + BluetoothServiceInfo, +) from homeassistant.components.bluetooth.passive_update_processor import ( PassiveBluetoothProcessorCoordinator, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform -from homeassistant.core import HomeAssistant +from homeassistant.core import HomeAssistant, callback -from .const import DOMAIN +from .const import CONF_DEVICE_TYPE, DOMAIN PLATFORMS: list[Platform] = [Platform.SENSOR] @@ -25,20 +28,33 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up INKBIRD BLE device from a config entry.""" address = entry.unique_id assert address is not None - data = INKBIRDBluetoothDeviceData() - coordinator = hass.data.setdefault(DOMAIN, {})[entry.entry_id] = ( - PassiveBluetoothProcessorCoordinator( - hass, - _LOGGER, - address=address, - mode=BluetoothScanningMode.ACTIVE, - update_method=data.update, - ) + device_type: str | None = entry.data.get(CONF_DEVICE_TYPE) + data = INKBIRDBluetoothDeviceData(device_type) + + @callback + def _async_on_update(service_info: BluetoothServiceInfo) -> SensorUpdate: + """Handle update callback from the passive BLE processor.""" + nonlocal device_type + update = data.update(service_info) + if device_type is None and data.device_type is not None: + device_type_str = str(data.device_type) + hass.config_entries.async_update_entry( + entry, data={**entry.data, CONF_DEVICE_TYPE: device_type_str} + ) + device_type = device_type_str + return update + + coordinator = PassiveBluetoothProcessorCoordinator( + hass, + _LOGGER, + address=address, + mode=BluetoothScanningMode.ACTIVE, + update_method=_async_on_update, ) + hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) - entry.async_on_unload( - coordinator.async_start() - ) # only start after all platforms have had a chance to subscribe + # only start after all platforms have had a chance to subscribe + entry.async_on_unload(coordinator.async_start()) return True diff --git a/homeassistant/components/inkbird/const.py b/homeassistant/components/inkbird/const.py index 9d0e16389584fcf227d41ac9fa7d802bfb2054fa..93fdcc7519c4afb5db831e0984173655aaa17943 100644 --- a/homeassistant/components/inkbird/const.py +++ b/homeassistant/components/inkbird/const.py @@ -1,3 +1,5 @@ """Constants for the INKBIRD Bluetooth integration.""" DOMAIN = "inkbird" + +CONF_DEVICE_TYPE = "device_type" diff --git a/tests/components/inkbird/__init__.py b/tests/components/inkbird/__init__.py index 30ca369672c06f3b7084a1c8faa28179c141d528..01ae0bf8efcb67ecf7330f2af592c381e978b33b 100644 --- a/tests/components/inkbird/__init__.py +++ b/tests/components/inkbird/__init__.py @@ -22,6 +22,17 @@ SPS_SERVICE_INFO = BluetoothServiceInfo( source="local", ) +SPS_WITH_CORRUPT_NAME_SERVICE_INFO = BluetoothServiceInfo( + name="XXXXcorruptXXXX", + address="AA:BB:CC:DD:EE:FF", + rssi=-63, + service_data={}, + manufacturer_data={2096: b"\x0f\x12\x00Z\xc7W\x06"}, + service_uuids=["0000fff0-0000-1000-8000-00805f9b34fb"], + source="local", +) + + IBBQ_SERVICE_INFO = BluetoothServiceInfo( name="iBBQ", address="4125DDBA-2774-4851-9889-6AADDD4CAC3D", diff --git a/tests/components/inkbird/test_sensor.py b/tests/components/inkbird/test_sensor.py index 822136b90211f0fac64abc2c924b54cfd53aa199..0f3d6497c2b67a5a61882f76680212c33e333930 100644 --- a/tests/components/inkbird/test_sensor.py +++ b/tests/components/inkbird/test_sensor.py @@ -1,11 +1,11 @@ """Test the INKBIRD config flow.""" -from homeassistant.components.inkbird.const import DOMAIN +from homeassistant.components.inkbird.const import CONF_DEVICE_TYPE, DOMAIN from homeassistant.components.sensor import ATTR_STATE_CLASS from homeassistant.const import ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT from homeassistant.core import HomeAssistant -from . import SPS_SERVICE_INFO +from . import SPS_SERVICE_INFO, SPS_WITH_CORRUPT_NAME_SERVICE_INFO from tests.common import MockConfigEntry from tests.components.bluetooth import inject_bluetooth_service_info @@ -34,5 +34,37 @@ async def test_sensors(hass: HomeAssistant) -> None: assert temp_sensor_attribtes[ATTR_UNIT_OF_MEASUREMENT] == "%" assert temp_sensor_attribtes[ATTR_STATE_CLASS] == "measurement" + # Make sure we remember the device type + # in case the name is corrupted later + assert entry.data[CONF_DEVICE_TYPE] == "IBS-TH" + assert await hass.config_entries.async_unload(entry.entry_id) + await hass.async_block_till_done() + + +async def test_device_with_corrupt_name(hass: HomeAssistant) -> None: + """Test setting up a known device type with a corrupt name.""" + entry = MockConfigEntry( + domain=DOMAIN, + unique_id="AA:BB:CC:DD:EE:FF", + data={CONF_DEVICE_TYPE: "IBS-TH"}, + ) + 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, SPS_WITH_CORRUPT_NAME_SERVICE_INFO) + await hass.async_block_till_done() + assert len(hass.states.async_all()) == 3 + + temp_sensor = hass.states.get("sensor.ibs_th_eeff_battery") + temp_sensor_attribtes = temp_sensor.attributes + assert temp_sensor.state == "87" + assert temp_sensor_attribtes[ATTR_FRIENDLY_NAME] == "IBS-TH EEFF Battery" + assert temp_sensor_attribtes[ATTR_UNIT_OF_MEASUREMENT] == "%" + assert temp_sensor_attribtes[ATTR_STATE_CLASS] == "measurement" + + assert entry.data[CONF_DEVICE_TYPE] == "IBS-TH" assert await hass.config_entries.async_unload(entry.entry_id) await hass.async_block_till_done()