diff --git a/homeassistant/components/airthings_ble/icons.json b/homeassistant/components/airthings_ble/icons.json index 17ad64e8d26..04b951999ba 100644 --- a/homeassistant/components/airthings_ble/icons.json +++ b/homeassistant/components/airthings_ble/icons.json @@ -1,6 +1,14 @@ { "entity": { "sensor": { + "connectivity_mode": { + "default": "mdi:bluetooth-off", + "state": { + "bluetooth": "mdi:bluetooth", + "not_configured": "mdi:alert-circle", + "smartlink": "mdi:hub" + } + }, "radon_1day_avg": { "default": "mdi:radioactive" }, diff --git a/homeassistant/components/airthings_ble/sensor.py b/homeassistant/components/airthings_ble/sensor.py index 49ca7970ae3..eb0d016528e 100644 --- a/homeassistant/components/airthings_ble/sensor.py +++ b/homeassistant/components/airthings_ble/sensor.py @@ -5,7 +5,7 @@ from __future__ import annotations import dataclasses import logging -from airthings_ble import AirthingsDevice +from airthings_ble import AirthingsConnectivityMode, AirthingsDevice from homeassistant.components.sensor import ( SensorDeviceClass, @@ -41,6 +41,12 @@ from .coordinator import AirthingsBLEConfigEntry, AirthingsBLEDataUpdateCoordina _LOGGER = logging.getLogger(__name__) +CONNECTIVITY_MODE_MAP = { + AirthingsConnectivityMode.BLE.value: "bluetooth", + AirthingsConnectivityMode.SMARTLINK.value: "smartlink", + AirthingsConnectivityMode.NOT_CONFIGURED.value: "not_configured", +} + SENSORS_MAPPING_TEMPLATE: dict[str, SensorEntityDescription] = { "radon_1day_avg": SensorEntityDescription( key="radon_1day_avg", @@ -129,6 +135,14 @@ SENSORS_MAPPING_TEMPLATE: dict[str, SensorEntityDescription] = { state_class=SensorStateClass.MEASUREMENT, suggested_display_precision=0, ), + "connectivity_mode": SensorEntityDescription( + key="connectivity_mode", + translation_key="connectivity_mode", + device_class=SensorDeviceClass.ENUM, + options=list(CONNECTIVITY_MODE_MAP.values()), + entity_category=EntityCategory.DIAGNOSTIC, + entity_registry_enabled_default=False, + ), } PARALLEL_UPDATES = 0 @@ -256,4 +270,12 @@ class AirthingsSensor( @property def native_value(self) -> StateType: """Return the value reported by the sensor.""" - return self.coordinator.data.sensors[self.entity_description.key] + value = self.coordinator.data.sensors[self.entity_description.key] + + # Map connectivity mode to enum values + if self.entity_description.key == "connectivity_mode": + if not isinstance(value, str): + return None + return CONNECTIVITY_MODE_MAP.get(value) + + return value diff --git a/homeassistant/components/airthings_ble/strings.json b/homeassistant/components/airthings_ble/strings.json index f1dc92591a8..6e1143fb979 100644 --- a/homeassistant/components/airthings_ble/strings.json +++ b/homeassistant/components/airthings_ble/strings.json @@ -30,6 +30,14 @@ "ambient_noise": { "name": "Ambient noise" }, + "connectivity_mode": { + "name": "Connectivity mode", + "state": { + "bluetooth": "Bluetooth", + "not_configured": "Not configured", + "smartlink": "SmartLink" + } + }, "illuminance": { "name": "[%key:component::sensor::entity_component::illuminance::name%]" }, diff --git a/tests/components/airthings_ble/__init__.py b/tests/components/airthings_ble/__init__.py index 23c66a9c3ec..7c13235e3c6 100644 --- a/tests/components/airthings_ble/__init__.py +++ b/tests/components/airthings_ble/__init__.py @@ -274,6 +274,7 @@ WAVE_ENHANCE_DEVICE_INFO = AirthingsDevice( name="Airthings Wave Enhance", identifier="123456", sensors={ + "connectivity_mode": "Bluetooth", "lux": 25, "battery": 85, "humidity": 60.0, diff --git a/tests/components/airthings_ble/test_sensor.py b/tests/components/airthings_ble/test_sensor.py index e6f029ca23e..a1839dddd57 100644 --- a/tests/components/airthings_ble/test_sensor.py +++ b/tests/components/airthings_ble/test_sensor.py @@ -1,7 +1,9 @@ """Test the Airthings Wave sensor.""" +from copy import deepcopy from datetime import timedelta import logging +from typing import Any from freezegun.api import FrozenDateTimeFactory import pytest @@ -12,7 +14,7 @@ from homeassistant.components.airthings_ble.const import ( DEVICE_SPECIFIC_SCAN_INTERVAL, DOMAIN, ) -from homeassistant.const import Platform +from homeassistant.const import STATE_UNKNOWN, Platform from homeassistant.core import HomeAssistant from homeassistant.helpers import device_registry as dr, entity_registry as er @@ -20,6 +22,7 @@ from . import ( CO2_V1, CO2_V2, CORENTIUM_HOME_2_DEVICE_INFO, + CORENTIUM_HOME_2_SERVICE_INFO, HUMIDITY_V2, TEMPERATURE_V1, VOC_V1, @@ -242,7 +245,7 @@ async def test_migration_with_all_unique_ids( ("noise", "Ambient noise"), ], ) -async def test_translation_keys( +async def test_translation_keys_wave_enhance( hass: HomeAssistant, entity_registry: er.EntityRegistry, device_registry: dr.DeviceRegistry, @@ -250,7 +253,7 @@ async def test_translation_keys( expected_sensor_name: str, ) -> None: """Test that translated sensor names are correct.""" - entry = create_entry(hass, WAVE_ENHANCE_SERVICE_INFO, WAVE_DEVICE_INFO) + entry = create_entry(hass, WAVE_ENHANCE_SERVICE_INFO, WAVE_ENHANCE_DEVICE_INFO) device = create_device( entry, device_registry, WAVE_ENHANCE_SERVICE_INFO, WAVE_ENHANCE_DEVICE_INFO ) @@ -280,6 +283,87 @@ async def test_translation_keys( assert state.attributes.get("friendly_name") == expected_name +async def test_disabled_connectivity_mode_corentium_home_2( + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + device_registry: dr.DeviceRegistry, +) -> None: + """Test that translated sensor names are correct for disabled sensors.""" + entry = create_entry( + hass, + CORENTIUM_HOME_2_SERVICE_INFO, + CORENTIUM_HOME_2_DEVICE_INFO, + ) + device = create_device( + entry, + device_registry, + CORENTIUM_HOME_2_SERVICE_INFO, + CORENTIUM_HOME_2_DEVICE_INFO, + ) + + with ( + patch_async_ble_device_from_address(CORENTIUM_HOME_2_SERVICE_INFO.device), + patch_async_discovered_service_info([CORENTIUM_HOME_2_SERVICE_INFO]), + patch_airthings_ble(CORENTIUM_HOME_2_DEVICE_INFO), + ): + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert device is not None + assert device.name == "Airthings Corentium Home 2 (123456)" + + unique_id = f"{CORENTIUM_HOME_2_DEVICE_INFO.address}_connectivity_mode" + + entity_id = entity_registry.async_get_entity_id(Platform.SENSOR, DOMAIN, unique_id) + assert entity_id is not None + + entity_entry = entity_registry.async_get(entity_id) + assert entity_entry is not None + assert entity_entry.disabled + assert entity_entry.disabled_by is er.RegistryEntryDisabler.INTEGRATION + + +@pytest.mark.usefixtures("entity_registry_enabled_by_default") +@pytest.mark.parametrize( + ("source_value", "expected_state"), + [ + (None, STATE_UNKNOWN), + (123, STATE_UNKNOWN), + (45.6, STATE_UNKNOWN), + ("Bluetooth", "bluetooth"), + ], +) +async def test_connectivity_mode( + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + device_registry: dr.DeviceRegistry, + source_value: Any, + expected_state: str, +) -> None: + """Test that non-string connectivity mode values are handled correctly.""" + test_device = deepcopy(CORENTIUM_HOME_2_DEVICE_INFO) + + # Non-string value, will be mapped to 'unknown' state + test_device.sensors["connectivity_mode"] = source_value + + entry = create_entry(hass, CORENTIUM_HOME_2_SERVICE_INFO, test_device) + create_device(entry, device_registry, CORENTIUM_HOME_2_SERVICE_INFO, test_device) + + with ( + patch_async_ble_device_from_address(CORENTIUM_HOME_2_SERVICE_INFO.device), + patch_async_discovered_service_info([CORENTIUM_HOME_2_SERVICE_INFO]), + patch_airthings_ble(test_device), + ): + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + state = hass.states.get( + "sensor.airthings_corentium_home_2_123456_connectivity_mode" + ) + assert state is not None + assert state.state == expected_state + + async def test_scan_interval_migration_corentium_home_2( hass: HomeAssistant, freezer: FrozenDateTimeFactory,