ruuvitag_ble: add new sensors (#150435)

Co-authored-by: Joostlek <joostlek@outlook.com>
This commit is contained in:
Aarni Koskela
2025-08-12 15:19:15 +03:00
committed by GitHub
parent 2b70639b11
commit 072ae2b955
6 changed files with 1167 additions and 83 deletions

View File

@@ -4,7 +4,6 @@ from __future__ import annotations
from sensor_state_data import ( from sensor_state_data import (
DeviceKey, DeviceKey,
SensorDescription,
SensorDeviceClass as SSDSensorDeviceClass, SensorDeviceClass as SSDSensorDeviceClass,
SensorUpdate, SensorUpdate,
Units, Units,
@@ -32,53 +31,108 @@ from homeassistant.const import (
UnitOfTemperature, UnitOfTemperature,
) )
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import EntityDescription
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.sensor import sensor_device_info_to_hass_device_info from homeassistant.helpers.sensor import sensor_device_info_to_hass_device_info
from .const import DOMAIN from .const import DOMAIN
SENSOR_DESCRIPTIONS = { SENSOR_DESCRIPTIONS = {
(SSDSensorDeviceClass.TEMPERATURE, Units.TEMP_CELSIUS): SensorEntityDescription( "temperature": SensorEntityDescription(
key=f"{SSDSensorDeviceClass.TEMPERATURE}_{Units.TEMP_CELSIUS}", key=f"{SSDSensorDeviceClass.TEMPERATURE}_{Units.TEMP_CELSIUS}",
device_class=SensorDeviceClass.TEMPERATURE, device_class=SensorDeviceClass.TEMPERATURE,
native_unit_of_measurement=UnitOfTemperature.CELSIUS, native_unit_of_measurement=UnitOfTemperature.CELSIUS,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
), ),
(SSDSensorDeviceClass.HUMIDITY, Units.PERCENTAGE): SensorEntityDescription( "humidity": SensorEntityDescription(
key=f"{SSDSensorDeviceClass.HUMIDITY}_{Units.PERCENTAGE}", key=f"{SSDSensorDeviceClass.HUMIDITY}_{Units.PERCENTAGE}",
device_class=SensorDeviceClass.HUMIDITY, device_class=SensorDeviceClass.HUMIDITY,
native_unit_of_measurement=PERCENTAGE, native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
), ),
(SSDSensorDeviceClass.PRESSURE, Units.PRESSURE_HPA): SensorEntityDescription( "pressure": SensorEntityDescription(
key=f"{SSDSensorDeviceClass.PRESSURE}_{Units.PRESSURE_HPA}", key=f"{SSDSensorDeviceClass.PRESSURE}_{Units.PRESSURE_HPA}",
device_class=SensorDeviceClass.PRESSURE, device_class=SensorDeviceClass.PRESSURE,
native_unit_of_measurement=UnitOfPressure.HPA, native_unit_of_measurement=UnitOfPressure.HPA,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
), ),
( "voltage": SensorEntityDescription(
SSDSensorDeviceClass.VOLTAGE,
Units.ELECTRIC_POTENTIAL_MILLIVOLT,
): SensorEntityDescription(
key=f"{SSDSensorDeviceClass.VOLTAGE}_{Units.ELECTRIC_POTENTIAL_MILLIVOLT}", key=f"{SSDSensorDeviceClass.VOLTAGE}_{Units.ELECTRIC_POTENTIAL_MILLIVOLT}",
native_unit_of_measurement=UnitOfElectricPotential.MILLIVOLT, native_unit_of_measurement=UnitOfElectricPotential.MILLIVOLT,
device_class=SensorDeviceClass.VOLTAGE,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
), ),
( "signal_strength": SensorEntityDescription(
SSDSensorDeviceClass.SIGNAL_STRENGTH,
Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
): SensorEntityDescription(
key=f"{SSDSensorDeviceClass.SIGNAL_STRENGTH}_{Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT}", key=f"{SSDSensorDeviceClass.SIGNAL_STRENGTH}_{Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT}",
device_class=SensorDeviceClass.SIGNAL_STRENGTH, device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS_MILLIWATT, native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
entity_registry_enabled_default=False, entity_registry_enabled_default=False,
), ),
(SSDSensorDeviceClass.COUNT, None): SensorEntityDescription( "movement_counter": SensorEntityDescription(
key="movement_counter", key="movement_counter",
translation_key="movement_counter",
state_class=SensorStateClass.TOTAL_INCREASING, state_class=SensorStateClass.TOTAL_INCREASING,
entity_registry_enabled_default=False, entity_registry_enabled_default=False,
), ),
# Acceleration keys exported in newer versions of ruuvitag-ble
"acceleration_x": SensorEntityDescription(
key=f"acceleration_x_{Units.ACCELERATION_METERS_PER_SQUARE_SECOND}",
translation_key="acceleration_x",
native_unit_of_measurement=Units.ACCELERATION_METERS_PER_SQUARE_SECOND,
state_class=SensorStateClass.MEASUREMENT,
entity_registry_enabled_default=False,
),
"acceleration_y": SensorEntityDescription(
key=f"acceleration_y_{Units.ACCELERATION_METERS_PER_SQUARE_SECOND}",
translation_key="acceleration_y",
native_unit_of_measurement=Units.ACCELERATION_METERS_PER_SQUARE_SECOND,
state_class=SensorStateClass.MEASUREMENT,
entity_registry_enabled_default=False,
),
"acceleration_z": SensorEntityDescription(
key=f"acceleration_z_{Units.ACCELERATION_METERS_PER_SQUARE_SECOND}",
translation_key="acceleration_z",
native_unit_of_measurement=Units.ACCELERATION_METERS_PER_SQUARE_SECOND,
state_class=SensorStateClass.MEASUREMENT,
entity_registry_enabled_default=False,
),
"acceleration_total": SensorEntityDescription(
key=f"acceleration_total_{Units.ACCELERATION_METERS_PER_SQUARE_SECOND}",
translation_key="acceleration_total",
native_unit_of_measurement=Units.ACCELERATION_METERS_PER_SQUARE_SECOND,
state_class=SensorStateClass.MEASUREMENT,
entity_registry_enabled_default=False,
),
# Keys exported for dataformat 06 sensors in newer versions of ruuvitag-ble
"pm25": SensorEntityDescription(
key=f"{SSDSensorDeviceClass.PM25}_{Units.CONCENTRATION_MICROGRAMS_PER_CUBIC_METER}",
device_class=SensorDeviceClass.PM25,
native_unit_of_measurement=Units.CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
),
"carbon_dioxide": SensorEntityDescription(
key=f"{SSDSensorDeviceClass.CO2}_{Units.CONCENTRATION_PARTS_PER_MILLION}",
device_class=SensorDeviceClass.CO2,
native_unit_of_measurement=Units.CONCENTRATION_PARTS_PER_MILLION,
state_class=SensorStateClass.MEASUREMENT,
),
"illuminance": SensorEntityDescription(
key=f"{SSDSensorDeviceClass.ILLUMINANCE}_{Units.LIGHT_LUX}",
device_class=SensorDeviceClass.ILLUMINANCE,
native_unit_of_measurement=Units.LIGHT_LUX,
state_class=SensorStateClass.MEASUREMENT,
),
"voc_index": SensorEntityDescription(
key="voc_index",
translation_key="voc_index",
state_class=SensorStateClass.MEASUREMENT,
),
"nox_index": SensorEntityDescription(
key="nox_index",
translation_key="nox_index",
state_class=SensorStateClass.MEASUREMENT,
),
} }
@@ -89,37 +143,28 @@ def _device_key_to_bluetooth_entity_key(
return PassiveBluetoothEntityKey(device_key.key, device_key.device_id) return PassiveBluetoothEntityKey(device_key.key, device_key.device_id)
def _to_sensor_key(
description: SensorDescription,
) -> tuple[SSDSensorDeviceClass, Units | None]:
assert description.device_class is not None
return (description.device_class, description.native_unit_of_measurement)
def sensor_update_to_bluetooth_data_update( def sensor_update_to_bluetooth_data_update(
sensor_update: SensorUpdate, sensor_update: SensorUpdate,
) -> PassiveBluetoothDataUpdate: ) -> PassiveBluetoothDataUpdate:
"""Convert a sensor update to a bluetooth data update.""" """Convert a sensor update to a bluetooth data update."""
entity_descriptions: dict[PassiveBluetoothEntityKey, EntityDescription] = {}
entity_data = {}
for device_key, sensor_values in sensor_update.entity_values.items():
bek = _device_key_to_bluetooth_entity_key(device_key)
entity_data[bek] = sensor_values.native_value
for device_key in sensor_update.entity_descriptions:
bek = _device_key_to_bluetooth_entity_key(device_key)
if sk_description := SENSOR_DESCRIPTIONS.get(device_key.key):
entity_descriptions[bek] = sk_description
return PassiveBluetoothDataUpdate( return PassiveBluetoothDataUpdate(
devices={ devices={
device_id: sensor_device_info_to_hass_device_info(device_info) device_id: sensor_device_info_to_hass_device_info(device_info)
for device_id, device_info in sensor_update.devices.items() for device_id, device_info in sensor_update.devices.items()
}, },
entity_descriptions={ entity_descriptions=entity_descriptions,
_device_key_to_bluetooth_entity_key(device_key): SENSOR_DESCRIPTIONS[ entity_data=entity_data,
_to_sensor_key(description) entity_names={},
]
for device_key, description in sensor_update.entity_descriptions.items()
if _to_sensor_key(description) in SENSOR_DESCRIPTIONS
},
entity_data={
_device_key_to_bluetooth_entity_key(device_key): sensor_values.native_value
for device_key, sensor_values in sensor_update.entity_values.items()
},
entity_names={
_device_key_to_bluetooth_entity_key(device_key): sensor_values.name
for device_key, sensor_values in sensor_update.entity_values.items()
},
) )

View File

@@ -18,5 +18,30 @@
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]", "already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]",
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]" "already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
} }
},
"entity": {
"sensor": {
"acceleration_total": {
"name": "Acceleration total"
},
"acceleration_x": {
"name": "Acceleration X"
},
"acceleration_y": {
"name": "Acceleration Y"
},
"acceleration_z": {
"name": "Acceleration Z"
},
"movement_counter": {
"name": "Movement counter"
},
"nox_index": {
"name": "NOx index"
},
"voc_index": {
"name": "VOC index"
}
}
} }
} }

View File

@@ -12,7 +12,7 @@ NOT_RUUVITAG_SERVICE_INFO = BluetoothServiceInfo(
source="local", source="local",
) )
RUUVITAG_SERVICE_INFO = BluetoothServiceInfo( RUUVI_V5_SERVICE_INFO = BluetoothServiceInfo(
name="RuuviTag 0911", name="RuuviTag 0911",
address="01:03:05:07:09:11", # Ignored (the payload encodes the correct MAC) address="01:03:05:07:09:11", # Ignored (the payload encodes the correct MAC)
rssi=-60, rssi=-60,
@@ -23,5 +23,16 @@ RUUVITAG_SERVICE_INFO = BluetoothServiceInfo(
service_uuids=[], service_uuids=[],
source="local", source="local",
) )
RUUVI_V6_SERVICE_INFO = BluetoothServiceInfo(
name="Ruuvi 1234",
address="01:03:05:07:12:34", # Ignored (the payload encodes the correct MAC)
rssi=-60,
manufacturer_data={
1177: b"\x06\x17\x0cVh\xc7\x9e\x00p\x00\xc9\x05\x01\xd9J\xcd\x00L\x88O",
},
service_data={},
service_uuids=[],
source="local",
)
CONFIGURED_NAME = "RuuviTag EFAF" CONFIGURED_NAME = "RuuviTag EFAF"
CONFIGURED_PREFIX = "ruuvitag_efaf" CONFIGURED_PREFIX = "ruuvitag_efaf"

File diff suppressed because it is too large Load Diff

View File

@@ -9,7 +9,7 @@ from homeassistant.components.ruuvitag_ble.const import DOMAIN
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType from homeassistant.data_entry_flow import FlowResultType
from .fixtures import CONFIGURED_NAME, NOT_RUUVITAG_SERVICE_INFO, RUUVITAG_SERVICE_INFO from .fixtures import CONFIGURED_NAME, NOT_RUUVITAG_SERVICE_INFO, RUUVI_V5_SERVICE_INFO
from tests.common import MockConfigEntry from tests.common import MockConfigEntry
@@ -24,7 +24,7 @@ async def test_async_step_bluetooth_valid_device(hass: HomeAssistant) -> None:
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, DOMAIN,
context={"source": config_entries.SOURCE_BLUETOOTH}, context={"source": config_entries.SOURCE_BLUETOOTH},
data=RUUVITAG_SERVICE_INFO, data=RUUVI_V5_SERVICE_INFO,
) )
assert result["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "bluetooth_confirm" assert result["step_id"] == "bluetooth_confirm"
@@ -36,7 +36,7 @@ async def test_async_step_bluetooth_valid_device(hass: HomeAssistant) -> None:
) )
assert result2["type"] is FlowResultType.CREATE_ENTRY assert result2["type"] is FlowResultType.CREATE_ENTRY
assert result2["title"] == CONFIGURED_NAME assert result2["title"] == CONFIGURED_NAME
assert result2["result"].unique_id == RUUVITAG_SERVICE_INFO.address assert result2["result"].unique_id == RUUVI_V5_SERVICE_INFO.address
async def test_async_step_bluetooth_not_ruuvitag(hass: HomeAssistant) -> None: async def test_async_step_bluetooth_not_ruuvitag(hass: HomeAssistant) -> None:
@@ -64,7 +64,7 @@ async def test_async_step_user_with_found_devices(hass: HomeAssistant) -> None:
"""Test setup from service info cache with devices found.""" """Test setup from service info cache with devices found."""
with patch( with patch(
"homeassistant.components.ruuvitag_ble.config_flow.async_discovered_service_info", "homeassistant.components.ruuvitag_ble.config_flow.async_discovered_service_info",
return_value=[RUUVITAG_SERVICE_INFO], return_value=[RUUVI_V5_SERVICE_INFO],
): ):
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, DOMAIN,
@@ -77,18 +77,18 @@ async def test_async_step_user_with_found_devices(hass: HomeAssistant) -> None:
): ):
result2 = await hass.config_entries.flow.async_configure( result2 = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
user_input={"address": RUUVITAG_SERVICE_INFO.address}, user_input={"address": RUUVI_V5_SERVICE_INFO.address},
) )
assert result2["type"] is FlowResultType.CREATE_ENTRY assert result2["type"] is FlowResultType.CREATE_ENTRY
assert result2["title"] == CONFIGURED_NAME assert result2["title"] == CONFIGURED_NAME
assert result2["result"].unique_id == RUUVITAG_SERVICE_INFO.address assert result2["result"].unique_id == RUUVI_V5_SERVICE_INFO.address
async def test_async_step_user_device_added_between_steps(hass: HomeAssistant) -> None: async def test_async_step_user_device_added_between_steps(hass: HomeAssistant) -> None:
"""Test the device gets added via another flow between steps.""" """Test the device gets added via another flow between steps."""
with patch( with patch(
"homeassistant.components.ruuvitag_ble.config_flow.async_discovered_service_info", "homeassistant.components.ruuvitag_ble.config_flow.async_discovered_service_info",
return_value=[RUUVITAG_SERVICE_INFO], return_value=[RUUVI_V5_SERVICE_INFO],
): ):
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, DOMAIN,
@@ -99,7 +99,7 @@ async def test_async_step_user_device_added_between_steps(hass: HomeAssistant) -
entry = MockConfigEntry( entry = MockConfigEntry(
domain=DOMAIN, domain=DOMAIN,
unique_id=RUUVITAG_SERVICE_INFO.address, unique_id=RUUVI_V5_SERVICE_INFO.address,
) )
entry.add_to_hass(hass) entry.add_to_hass(hass)
@@ -108,7 +108,7 @@ async def test_async_step_user_device_added_between_steps(hass: HomeAssistant) -
): ):
result2 = await hass.config_entries.flow.async_configure( result2 = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
user_input={"address": RUUVITAG_SERVICE_INFO.address}, user_input={"address": RUUVI_V5_SERVICE_INFO.address},
) )
assert result2["type"] is FlowResultType.ABORT assert result2["type"] is FlowResultType.ABORT
assert result2["reason"] == "already_configured" assert result2["reason"] == "already_configured"
@@ -120,13 +120,13 @@ async def test_async_step_user_with_found_devices_already_setup(
"""Test setup from service info cache with devices found.""" """Test setup from service info cache with devices found."""
entry = MockConfigEntry( entry = MockConfigEntry(
domain=DOMAIN, domain=DOMAIN,
unique_id=RUUVITAG_SERVICE_INFO.address, unique_id=RUUVI_V5_SERVICE_INFO.address,
) )
entry.add_to_hass(hass) entry.add_to_hass(hass)
with patch( with patch(
"homeassistant.components.ruuvitag_ble.config_flow.async_discovered_service_info", "homeassistant.components.ruuvitag_ble.config_flow.async_discovered_service_info",
return_value=[RUUVITAG_SERVICE_INFO], return_value=[RUUVI_V5_SERVICE_INFO],
): ):
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, DOMAIN,
@@ -140,14 +140,14 @@ async def test_async_step_bluetooth_devices_already_setup(hass: HomeAssistant) -
"""Test we can't start a flow if there is already a config entry.""" """Test we can't start a flow if there is already a config entry."""
entry = MockConfigEntry( entry = MockConfigEntry(
domain=DOMAIN, domain=DOMAIN,
unique_id=RUUVITAG_SERVICE_INFO.address, unique_id=RUUVI_V5_SERVICE_INFO.address,
) )
entry.add_to_hass(hass) entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, DOMAIN,
context={"source": config_entries.SOURCE_BLUETOOTH}, context={"source": config_entries.SOURCE_BLUETOOTH},
data=RUUVITAG_SERVICE_INFO, data=RUUVI_V5_SERVICE_INFO,
) )
assert result["type"] is FlowResultType.ABORT assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "already_configured" assert result["reason"] == "already_configured"
@@ -158,7 +158,7 @@ async def test_async_step_bluetooth_already_in_progress(hass: HomeAssistant) ->
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, DOMAIN,
context={"source": config_entries.SOURCE_BLUETOOTH}, context={"source": config_entries.SOURCE_BLUETOOTH},
data=RUUVITAG_SERVICE_INFO, data=RUUVI_V5_SERVICE_INFO,
) )
assert result["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "bluetooth_confirm" assert result["step_id"] == "bluetooth_confirm"
@@ -166,7 +166,7 @@ async def test_async_step_bluetooth_already_in_progress(hass: HomeAssistant) ->
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, DOMAIN,
context={"source": config_entries.SOURCE_BLUETOOTH}, context={"source": config_entries.SOURCE_BLUETOOTH},
data=RUUVITAG_SERVICE_INFO, data=RUUVI_V5_SERVICE_INFO,
) )
assert result["type"] is FlowResultType.ABORT assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "already_in_progress" assert result["reason"] == "already_in_progress"
@@ -179,14 +179,14 @@ async def test_async_step_user_takes_precedence_over_discovery(
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, DOMAIN,
context={"source": config_entries.SOURCE_BLUETOOTH}, context={"source": config_entries.SOURCE_BLUETOOTH},
data=RUUVITAG_SERVICE_INFO, data=RUUVI_V5_SERVICE_INFO,
) )
assert result["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "bluetooth_confirm" assert result["step_id"] == "bluetooth_confirm"
with patch( with patch(
"homeassistant.components.ruuvitag_ble.config_flow.async_discovered_service_info", "homeassistant.components.ruuvitag_ble.config_flow.async_discovered_service_info",
return_value=[RUUVITAG_SERVICE_INFO], return_value=[RUUVI_V5_SERVICE_INFO],
): ):
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, DOMAIN,
@@ -199,9 +199,9 @@ async def test_async_step_user_takes_precedence_over_discovery(
): ):
result2 = await hass.config_entries.flow.async_configure( result2 = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
user_input={"address": RUUVITAG_SERVICE_INFO.address}, user_input={"address": RUUVI_V5_SERVICE_INFO.address},
) )
assert result2["type"] is FlowResultType.CREATE_ENTRY assert result2["type"] is FlowResultType.CREATE_ENTRY
assert result2["title"] == CONFIGURED_NAME assert result2["title"] == CONFIGURED_NAME
assert result2["data"] == {} assert result2["data"] == {}
assert result2["result"].unique_id == RUUVITAG_SERVICE_INFO.address assert result2["result"].unique_id == RUUVI_V5_SERVICE_INFO.address

View File

@@ -3,47 +3,37 @@
from __future__ import annotations from __future__ import annotations
import pytest import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.ruuvitag_ble.const import DOMAIN from homeassistant.components.ruuvitag_ble.const import 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 homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_registry import EntityRegistry
from homeassistant.helpers.service_info.bluetooth import BluetoothServiceInfo
from .fixtures import CONFIGURED_NAME, CONFIGURED_PREFIX, RUUVITAG_SERVICE_INFO from .fixtures import RUUVI_V5_SERVICE_INFO, RUUVI_V6_SERVICE_INFO
from tests.common import MockConfigEntry from tests.common import MockConfigEntry, snapshot_platform
from tests.components.bluetooth import inject_bluetooth_service_info from tests.components.bluetooth import inject_bluetooth_service_info
@pytest.mark.usefixtures("enable_bluetooth") @pytest.mark.usefixtures("enable_bluetooth", "entity_registry_enabled_by_default")
async def test_sensors(hass: HomeAssistant) -> None: @pytest.mark.parametrize(
"service_info", [RUUVI_V5_SERVICE_INFO, RUUVI_V6_SERVICE_INFO], ids=("v5", "v6")
)
async def test_sensors(
hass: HomeAssistant,
entity_registry: EntityRegistry,
snapshot: SnapshotAssertion,
service_info: BluetoothServiceInfo,
) -> None:
"""Test the RuuviTag BLE sensors.""" """Test the RuuviTag BLE sensors."""
entry = MockConfigEntry(domain=DOMAIN, unique_id=RUUVITAG_SERVICE_INFO.address) entry = MockConfigEntry(domain=DOMAIN, unique_id=service_info.address)
entry.add_to_hass(hass) entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(entry.entry_id) assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
inject_bluetooth_service_info(hass, service_info)
assert len(hass.states.async_all()) == 0
inject_bluetooth_service_info(
hass,
RUUVITAG_SERVICE_INFO,
)
await hass.async_block_till_done() await hass.async_block_till_done()
assert len(hass.states.async_all()) >= 4 await snapshot_platform(hass, entity_registry, snapshot, entry.entry_id)
for sensor, value, unit, state_class in (
("temperature", "7.2", "°C", "measurement"),
("humidity", "61.84", "%", "measurement"),
("pressure", "1013.54", "hPa", "measurement"),
("voltage", "2395", "mV", "measurement"),
):
state = hass.states.get(f"sensor.{CONFIGURED_PREFIX}_{sensor}")
assert state is not None
assert state.state == value
name_lower = state.attributes[ATTR_FRIENDLY_NAME].lower()
assert name_lower == f"{CONFIGURED_NAME} {sensor}".lower()
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == unit
assert state.attributes[ATTR_STATE_CLASS] == state_class
assert await hass.config_entries.async_unload(entry.entry_id) assert await hass.config_entries.async_unload(entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()