1
0
mirror of https://github.com/home-assistant/core.git synced 2026-07-06 22:36:33 +01:00

Add sensor platform to prana (#165632)

This commit is contained in:
prana-dev-official
2026-03-16 21:03:36 +02:00
committed by GitHub
parent de4025634a
commit 254aa30ad8
6 changed files with 314 additions and 1 deletions
+1 -1
View File
@@ -14,7 +14,7 @@ from .coordinator import PranaConfigEntry, PranaCoordinator
_LOGGER = logging.getLogger(__name__)
PLATFORMS = [Platform.FAN, Platform.SWITCH]
PLATFORMS = [Platform.FAN, Platform.SENSOR, Platform.SWITCH]
async def async_setup_entry(hass: HomeAssistant, entry: PranaConfigEntry) -> bool:
+14
View File
@@ -8,6 +8,20 @@
"default": "mdi:arrow-expand-left"
}
},
"sensor": {
"inside_temperature": {
"default": "mdi:home-thermometer"
},
"inside_temperature_2": {
"default": "mdi:home-thermometer"
},
"outside_temperature": {
"default": "mdi:thermometer"
},
"outside_temperature_2": {
"default": "mdi:thermometer"
}
},
"switch": {
"auto": {
"default": "mdi:fan-auto"
+129
View File
@@ -0,0 +1,129 @@
"""Sensor platform for Prana integration."""
from collections.abc import Callable
from dataclasses import dataclass
from enum import StrEnum
from homeassistant.components.sensor import (
SensorDeviceClass,
SensorEntity,
SensorEntityDescription,
SensorStateClass,
StateType,
)
from homeassistant.const import (
CONCENTRATION_PARTS_PER_BILLION,
CONCENTRATION_PARTS_PER_MILLION,
PERCENTAGE,
UnitOfPressure,
UnitOfTemperature,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import PranaConfigEntry
from .entity import PranaBaseEntity, PranaCoordinator
PARALLEL_UPDATES = 1
class PranaSensorType(StrEnum):
"""Enumerates Prana sensor types exposed by the device API."""
HUMIDITY = "humidity"
VOC = "voc"
AIR_PRESSURE = "air_pressure"
CO2 = "co2"
INSIDE_TEMPERATURE = "inside_temperature"
INSIDE_TEMPERATURE_2 = "inside_temperature_2"
OUTSIDE_TEMPERATURE = "outside_temperature"
OUTSIDE_TEMPERATURE_2 = "outside_temperature_2"
@dataclass(frozen=True, kw_only=True)
class PranaSensorEntityDescription(SensorEntityDescription):
"""Description of a Prana sensor entity."""
key: PranaSensorType
state_class: SensorStateClass = SensorStateClass.MEASUREMENT
value_fn: Callable[[PranaCoordinator], StateType | None]
ENTITIES: tuple[PranaSensorEntityDescription, ...] = (
PranaSensorEntityDescription(
key=PranaSensorType.HUMIDITY,
value_fn=lambda coord: coord.data.humidity,
native_unit_of_measurement=PERCENTAGE,
device_class=SensorDeviceClass.HUMIDITY,
),
PranaSensorEntityDescription(
key=PranaSensorType.VOC,
value_fn=lambda coord: coord.data.voc,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
device_class=SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS_PARTS,
),
PranaSensorEntityDescription(
key=PranaSensorType.AIR_PRESSURE,
value_fn=lambda coord: coord.data.air_pressure,
native_unit_of_measurement=UnitOfPressure.MMHG,
device_class=SensorDeviceClass.PRESSURE,
),
PranaSensorEntityDescription(
key=PranaSensorType.CO2,
value_fn=lambda coord: coord.data.co2,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
device_class=SensorDeviceClass.CO2,
),
PranaSensorEntityDescription(
key=PranaSensorType.INSIDE_TEMPERATURE,
translation_key="inside_temperature",
value_fn=lambda coord: coord.data.inside_temperature,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
device_class=SensorDeviceClass.TEMPERATURE,
),
PranaSensorEntityDescription(
key=PranaSensorType.INSIDE_TEMPERATURE_2,
translation_key="inside_temperature_2",
value_fn=lambda coord: coord.data.inside_temperature_2,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
device_class=SensorDeviceClass.TEMPERATURE,
),
PranaSensorEntityDescription(
key=PranaSensorType.OUTSIDE_TEMPERATURE,
translation_key="outside_temperature",
value_fn=lambda coord: coord.data.outside_temperature,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
device_class=SensorDeviceClass.TEMPERATURE,
),
PranaSensorEntityDescription(
key=PranaSensorType.OUTSIDE_TEMPERATURE_2,
translation_key="outside_temperature_2",
value_fn=lambda coord: coord.data.outside_temperature_2,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
device_class=SensorDeviceClass.TEMPERATURE,
),
)
async def async_setup_entry(
hass: HomeAssistant,
entry: PranaConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up Prana sensor entities from a config entry."""
async_add_entities(
PranaSensor(entry.runtime_data, description)
for description in ENTITIES
if description.value_fn(entry.runtime_data) is not None
)
class PranaSensor(PranaBaseEntity, SensorEntity):
"""Representation of a Prana sensor entity."""
entity_description: PranaSensorEntityDescription
@property
def native_value(self) -> StateType | None:
"""Return the state of the sensor."""
return self.entity_description.value_fn(self.coordinator)
@@ -49,6 +49,20 @@
}
}
},
"sensor": {
"inside_temperature": {
"name": "Inside temperature"
},
"inside_temperature_2": {
"name": "Inside temperature 2"
},
"outside_temperature": {
"name": "Outside temperature"
},
"outside_temperature_2": {
"name": "Outside temperature 2"
}
},
"switch": {
"auto": {
"name": "Auto"
@@ -0,0 +1,112 @@
# serializer version: 1
# name: test_sensors[sensor.prana_recuperator_humidity-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.prana_recuperator_humidity',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Humidity',
'options': dict({
}),
'original_device_class': <SensorDeviceClass.HUMIDITY: 'humidity'>,
'original_icon': None,
'original_name': 'Humidity',
'platform': 'prana',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': 'ECC9FFE0E574_humidity',
'unit_of_measurement': '%',
})
# ---
# name: test_sensors[sensor.prana_recuperator_humidity-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'humidity',
'friendly_name': 'PRANA RECUPERATOR Humidity',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': '%',
}),
'context': <ANY>,
'entity_id': 'sensor.prana_recuperator_humidity',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '56',
})
# ---
# name: test_sensors[sensor.prana_recuperator_inside_temperature-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.prana_recuperator_inside_temperature',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Inside temperature',
'options': dict({
'sensor': dict({
'suggested_display_precision': 1,
}),
}),
'original_device_class': <SensorDeviceClass.TEMPERATURE: 'temperature'>,
'original_icon': None,
'original_name': 'Inside temperature',
'platform': 'prana',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'inside_temperature',
'unique_id': 'ECC9FFE0E574_inside_temperature',
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
})
# ---
# name: test_sensors[sensor.prana_recuperator_inside_temperature-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'temperature',
'friendly_name': 'PRANA RECUPERATOR Inside temperature',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
}),
'context': <ANY>,
'entity_id': 'sensor.prana_recuperator_inside_temperature',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '21.7',
})
# ---
+44
View File
@@ -0,0 +1,44 @@
"""Tests for the Prana sensor platform."""
from unittest.mock import MagicMock, patch
from syrupy.assertion import SnapshotAssertion
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from . import async_init_integration
from tests.common import MockConfigEntry, snapshot_platform
async def test_sensors(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
entity_registry: er.EntityRegistry,
mock_prana_api: MagicMock,
snapshot: SnapshotAssertion,
) -> None:
"""Test all Prana sensors using snapshots."""
with patch("homeassistant.components.prana.PLATFORMS", [Platform.SENSOR]):
await async_init_integration(hass, mock_config_entry)
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
async def test_sensors_not_added_if_none(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_prana_api: MagicMock,
) -> None:
"""Test sensors are not added when value_fn returns None."""
mock_prana_api.get_state.return_value.co2 = None
mock_prana_api.get_state.return_value.humidity = 45
with patch("homeassistant.components.prana.PLATFORMS", [Platform.SENSOR]):
await async_init_integration(hass, mock_config_entry)
assert hass.states.get("sensor.prana_recuperator_humidity") is not None
assert hass.states.get("sensor.prana_recuperator_co2") is None