Use radon device class for Aranet sensors (#182952)

This commit is contained in:
Parker Brown
2026-09-23 11:36:00 +01:00
committed by GitHub
parent b2f1fad07d
commit c8095a55c9
4 changed files with 224 additions and 5 deletions
+14 -1
View File
@@ -25,6 +25,7 @@ from homeassistant.const import (
ATTR_SW_VERSION,
EntityCategory,
UnitOfPressure,
UnitOfRadiationConcentration,
UnitOfRatio,
UnitOfTemperature,
UnitOfTime,
@@ -107,7 +108,8 @@ SENSOR_DESCRIPTIONS = {
key="radon_concentration",
translation_key="radon_concentration",
name="Radon Concentration",
native_unit_of_measurement="Bq/m³",
device_class=SensorDeviceClass.RADON,
native_unit_of_measurement=UnitOfRadiationConcentration.BECQUEREL_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
),
"battery": AranetSensorEntityDescription(
@@ -209,6 +211,17 @@ class Aranet4BluetoothSensorEntity(
):
"""Representation of an Aranet sensor."""
def __init__(
self,
processor: PassiveBluetoothDataProcessor[
float | int | None, Aranet4Advertisement
],
entity_key: PassiveBluetoothEntityKey,
description: EntityDescription,
) -> None:
"""Initialize with current metadata instead of a cached description."""
super().__init__(processor, entity_key, SENSOR_DESCRIPTIONS[entity_key.key])
@property
@override
def available(self) -> bool:
+11
View File
@@ -105,3 +105,14 @@ VALID_ARANET_RADON_DATA_SERVICE_INFO = fake_service_info(
)
},
)
VALID_ARANET_RADON1_DATA_SERVICE_INFO = fake_service_info(
"AranetRn1 12345",
"0000fce0-0000-1000-8000-00805f9b34fb",
{
1794: (
b"\x03!\x04\x06\x01\x00\x00\x00\x07\x00"
b"\x00\x00\xc9'\x00\x00\x00d\x01X\x02\xf6\x01\x08"
)
},
)
+11 -1
View File
@@ -1,8 +1,18 @@
"""Aranet session fixtures."""
from typing import Any
import pytest
@pytest.fixture
def mock_bluetooth_storage(
hass_storage: dict[str, Any], request: pytest.FixtureRequest
) -> None:
"""Load Bluetooth storage before setting up the integration."""
hass_storage.update(getattr(request, "param", {}))
@pytest.fixture(autouse=True)
def mock_bluetooth(enable_bluetooth: None) -> None:
def mock_bluetooth(mock_bluetooth_storage: None, enable_bluetooth: None) -> None:
"""Auto mock bluetooth."""
+188 -3
View File
@@ -3,15 +3,33 @@
import pytest
from homeassistant.components.aranet.const import DOMAIN
from homeassistant.components.sensor import ATTR_OPTIONS, ATTR_STATE_CLASS
from homeassistant.const import ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT
from homeassistant.components.bluetooth import BluetoothServiceInfoBleak
from homeassistant.components.bluetooth.passive_update_processor import STORAGE_KEY
from homeassistant.components.sensor import (
ATTR_OPTIONS,
ATTR_STATE_CLASS,
SensorDeviceClass,
SensorStateClass,
)
from homeassistant.const import (
ATTR_DEVICE_CLASS,
ATTR_FRIENDLY_NAME,
ATTR_UNIT_OF_MEASUREMENT,
UnitOfRadiationConcentration,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.util.unit_system import (
METRIC_SYSTEM,
US_CUSTOMARY_SYSTEM,
UnitSystem,
)
from . import (
DISABLED_INTEGRATIONS_SERVICE_INFO,
VALID_ARANET2_DATA_SERVICE_INFO,
VALID_ARANET_RADIATION_DATA_SERVICE_INFO,
VALID_ARANET_RADON1_DATA_SERVICE_INFO,
VALID_ARANET_RADON_DATA_SERVICE_INFO,
VALID_DATA_SERVICE_INFO,
)
@@ -19,6 +37,9 @@ from . import (
from tests.common import MockConfigEntry
from tests.components.bluetooth import inject_bluetooth_service_info
RADON_ENTITY_ID = "sensor.aranetrn_12345_radon_concentration"
RADON_UNIQUE_ID = "aa:bb:cc:dd:ee:ff-radon_concentration-aa:bb:cc:dd:ee:ff"
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_sensors_aranet_radiation(
@@ -263,7 +284,7 @@ async def test_sensors_aranetrn(
assert batt_sensor_attrs[ATTR_UNIT_OF_MEASUREMENT] == "%"
assert batt_sensor_attrs[ATTR_STATE_CLASS] == "measurement"
co2_sensor = hass.states.get("sensor.aranetrn_12345_radon_concentration")
co2_sensor = hass.states.get(RADON_ENTITY_ID)
co2_sensor_attrs = co2_sensor.attributes
assert co2_sensor.state == "7"
assert co2_sensor_attrs[ATTR_FRIENDLY_NAME] == "AranetRn+ 12345 Radon Concentration"
@@ -319,6 +340,170 @@ async def test_sensors_aranetrn(
await hass.async_block_till_done()
@pytest.mark.parametrize(
("service_info", "entity_id"),
[
pytest.param(
VALID_ARANET_RADON_DATA_SERVICE_INFO,
RADON_ENTITY_ID,
id="aranetrn_plus",
),
pytest.param(
VALID_ARANET_RADON1_DATA_SERVICE_INFO,
"sensor.aranetrn1_12345_radon_concentration",
id="aranetrn1",
),
],
)
@pytest.mark.parametrize(
("unit_system", "expected_unit", "expected_value"),
[
pytest.param(
METRIC_SYSTEM,
UnitOfRadiationConcentration.BECQUEREL_PER_CUBIC_METER,
7,
id="metric",
),
pytest.param(
US_CUSTOMARY_SYSTEM,
UnitOfRadiationConcentration.PICOCURIES_PER_LITER,
7 / 37,
id="us_customary",
),
],
)
async def test_radon_device_class_and_units(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
service_info: BluetoothServiceInfoBleak,
entity_id: str,
unit_system: UnitSystem,
expected_unit: UnitOfRadiationConcentration,
expected_value: float,
) -> None:
"""Test radon metadata and default units."""
hass.config.units = unit_system
entry = MockConfigEntry(domain=DOMAIN, unique_id=service_info.address)
entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(entry.entry_id)
inject_bluetooth_service_info(hass, service_info)
await hass.async_block_till_done()
state = hass.states.get(entity_id)
assert state is not None
assert float(state.state) == pytest.approx(expected_value)
assert state.attributes[ATTR_DEVICE_CLASS] == SensorDeviceClass.RADON
assert state.attributes[ATTR_STATE_CLASS] == SensorStateClass.MEASUREMENT
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == expected_unit
assert (
state.attributes[ATTR_FRIENDLY_NAME]
== f"{service_info.name} Radon Concentration"
)
entity = entity_registry.async_get(entity_id)
assert entity is not None
assert entity.unique_id == RADON_UNIQUE_ID
assert entity.original_device_class == SensorDeviceClass.RADON
@pytest.mark.parametrize(
("mock_bluetooth_storage", "restored_device_classes"),
[
pytest.param({}, {}, id="without_cache"),
pytest.param(
{
STORAGE_KEY: {
"version": 1,
"minor_version": 1,
"key": STORAGE_KEY,
"data": {
"aranet_radon": {
"sensor": {
"devices": {
"aa:bb:cc:dd:ee:ff": {
"name": "AranetRn+ 12345",
"connections": [
["bluetooth", "aa:bb:cc:dd:ee:ff"]
],
}
},
"entity_descriptions": {
"radon_concentration___aa:bb:cc:dd:ee:ff": {
"key": "radon_concentration",
"translation_key": "radon_concentration",
"name": "Radon Concentration",
"native_unit_of_measurement": "Bq/m³",
"state_class": "measurement",
}
},
"entity_names": {
"radon_concentration___aa:bb:cc:dd:ee:ff": (
"Radon Concentration"
)
},
"entity_data": {
"radon_concentration___aa:bb:cc:dd:ee:ff": 7
},
}
}
},
}
},
{RADON_ENTITY_ID: SensorDeviceClass.RADON},
id="cached_without_device_class",
),
],
indirect=["mock_bluetooth_storage"],
)
async def test_existing_radon_sensor(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
restored_device_classes: dict[str, SensorDeviceClass],
) -> None:
"""Test existing radon sensors gain the class without changing identity or units."""
hass.config.units = US_CUSTOMARY_SYSTEM
entry = MockConfigEntry(
domain=DOMAIN,
entry_id="aranet_radon",
unique_id="aa:bb:cc:dd:ee:ff",
)
entry.add_to_hass(hass)
entity = entity_registry.async_get_or_create(
"sensor",
DOMAIN,
RADON_UNIQUE_ID,
config_entry=entry,
suggested_object_id="aranetrn_12345_radon_concentration",
original_name="Radon Concentration",
unit_of_measurement=UnitOfRadiationConcentration.BECQUEREL_PER_CUBIC_METER,
)
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert {
state.entity_id: state.attributes[ATTR_DEVICE_CLASS]
for state in hass.states.async_all("sensor")
} == restored_device_classes
inject_bluetooth_service_info(hass, VALID_ARANET_RADON_DATA_SERVICE_INFO)
await hass.async_block_till_done()
state = hass.states.get(RADON_ENTITY_ID)
assert state is not None
assert state.state == "7"
assert state.attributes[ATTR_DEVICE_CLASS] == SensorDeviceClass.RADON
assert (
state.attributes[ATTR_UNIT_OF_MEASUREMENT]
== UnitOfRadiationConcentration.BECQUEREL_PER_CUBIC_METER
)
assert state.attributes[ATTR_FRIENDLY_NAME] == "AranetRn+ 12345 Radon Concentration"
updated_entity = entity_registry.async_get(RADON_ENTITY_ID)
assert updated_entity is not None
assert updated_entity.id == entity.id
assert updated_entity.unique_id == RADON_UNIQUE_ID
assert updated_entity.original_device_class == SensorDeviceClass.RADON
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_smart_home_integration_disabled(hass: HomeAssistant) -> None:
"""Test disabling smart home integration marks entities as unavailable."""