Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions homeassistant/components/saunum/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
PLATFORMS: list[Platform] = [
Platform.CLIMATE,
Platform.LIGHT,
Platform.SENSOR,
]

type LeilSaunaConfigEntry = ConfigEntry[LeilSaunaCoordinator]
Expand Down
9 changes: 9 additions & 0 deletions homeassistant/components/saunum/icons.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"entity": {
"sensor": {
"heater_elements_active": {
"default": "mdi:radiator"
}
}
}
}
8 changes: 5 additions & 3 deletions homeassistant/components/saunum/quality_scale.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,10 @@ rules:
comment: Integration controls a single device; no dynamic device discovery needed.
entity-category: done
entity-device-class: done
entity-disabled-by-default: todo
entity-disabled-by-default: done
entity-translations: done
exception-translations: done
icon-translations: todo
icon-translations: done
reconfiguration-flow: done
repair-issues:
status: exempt
Expand All @@ -74,5 +74,7 @@ rules:

# Platinum
async-dependency: todo
inject-websession: todo
inject-websession:
status: exempt
comment: Integration uses Modbus TCP protocol and does not make HTTP requests.
strict-typing: todo
100 changes: 100 additions & 0 deletions homeassistant/components/saunum/sensor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
"""Sensor platform for Saunum Leil Sauna Control Unit integration."""

from __future__ import annotations

from collections.abc import Callable
from dataclasses import dataclass
from typing import TYPE_CHECKING

from pysaunum import SaunumData

from homeassistant.components.sensor import (
SensorDeviceClass,
SensorEntity,
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.const import EntityCategory, UnitOfTemperature, UnitOfTime
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback

from . import LeilSaunaConfigEntry
from .entity import LeilSaunaEntity

if TYPE_CHECKING:
from .coordinator import LeilSaunaCoordinator

PARALLEL_UPDATES = 0


@dataclass(frozen=True, kw_only=True)
class LeilSaunaSensorEntityDescription(SensorEntityDescription):
"""Describes Leil Sauna sensor entity."""

value_fn: Callable[[SaunumData], float | int | None]


SENSORS: tuple[LeilSaunaSensorEntityDescription, ...] = (
LeilSaunaSensorEntityDescription(
key="current_temperature",
translation_key="current_temperature",
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda data: data.current_temperature,
),
LeilSaunaSensorEntityDescription(
key="heater_elements_active",
translation_key="heater_elements_active",
native_unit_of_measurement="heater elements",
Comment thread
joostlek marked this conversation as resolved.
Outdated
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda data: data.heater_elements_active,
),
LeilSaunaSensorEntityDescription(
key="on_time",
translation_key="on_time",
native_unit_of_measurement=UnitOfTime.SECONDS,
suggested_unit_of_measurement=UnitOfTime.HOURS,
device_class=SensorDeviceClass.DURATION,
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False,
state_class=SensorStateClass.TOTAL_INCREASING,
value_fn=lambda data: data.on_time,
),
Comment thread
mettolen marked this conversation as resolved.
)


async def async_setup_entry(
hass: HomeAssistant,
entry: LeilSaunaConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up Saunum Leil Sauna sensors from a config entry."""
coordinator = entry.runtime_data

async_add_entities(
LeilSaunaSensorEntity(coordinator, description)
for description in SENSORS
if description.value_fn(coordinator.data) is not None
)


class LeilSaunaSensorEntity(LeilSaunaEntity, SensorEntity):
"""Representation of a Saunum Leil Sauna sensor."""

entity_description: LeilSaunaSensorEntityDescription

def __init__(
self,
coordinator: LeilSaunaCoordinator,
description: LeilSaunaSensorEntityDescription,
) -> None:
"""Initialize the sensor."""
super().__init__(coordinator)
self._attr_unique_id = f"{coordinator.config_entry.entry_id}-{description.key}"
self.entity_description = description

@property
def native_value(self) -> float | int | None:
Comment thread
mettolen marked this conversation as resolved.
"""Return the value reported by the sensor."""
return self.entity_description.value_fn(self.coordinator.data)
8 changes: 8 additions & 0 deletions homeassistant/components/saunum/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@
"light": {
"name": "[%key:component::light::title%]"
}
},
"sensor": {
"heater_elements_active": {
"name": "Heater elements active"
Comment thread
joostlek marked this conversation as resolved.
Outdated
},
"on_time": {
"name": "Total time turned on"
}
}
},
"exceptions": {
Expand Down
168 changes: 168 additions & 0 deletions tests/components/saunum/snapshots/test_sensor.ambr
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
# serializer version: 1
# name: test_entities[sensor.saunum_leil_duration-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.saunum_leil_duration',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
'sensor': dict({
'suggested_display_precision': 2,
}),
'sensor.private': dict({
'suggested_unit_of_measurement': <UnitOfTime.HOURS: 'h'>,
}),
}),
'original_device_class': <SensorDeviceClass.DURATION: 'duration'>,
'original_icon': None,
'original_name': 'Duration',
'platform': 'saunum',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'on_time',
'unique_id': '01K98T2T85R5GN0ZHYV25VFMMA-on_time',
'unit_of_measurement': <UnitOfTime.HOURS: 'h'>,
})
# ---
# name: test_entities[sensor.saunum_leil_duration-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'duration',
'friendly_name': 'Saunum Leil Duration',
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
'unit_of_measurement': <UnitOfTime.HOURS: 'h'>,
}),
'context': <ANY>,
'entity_id': 'sensor.saunum_leil_duration',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '1.0',
})
# ---
# name: test_entities[sensor.saunum_leil_none-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.saunum_leil_none',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': None,
'platform': 'saunum',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'heater_elements_active',
'unique_id': '01K98T2T85R5GN0ZHYV25VFMMA-heater_elements_active',
'unit_of_measurement': 'heater elements',
})
# ---
# name: test_entities[sensor.saunum_leil_none-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Saunum Leil None',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': 'heater elements',
}),
'context': <ANY>,
'entity_id': 'sensor.saunum_leil_none',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '0',
})
# ---
# name: test_entities[sensor.saunum_leil_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.saunum_leil_temperature',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
'sensor': dict({
'suggested_display_precision': 1,
}),
}),
'original_device_class': <SensorDeviceClass.TEMPERATURE: 'temperature'>,
'original_icon': None,
'original_name': 'Temperature',
'platform': 'saunum',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'current_temperature',
'unique_id': '01K98T2T85R5GN0ZHYV25VFMMA-current_temperature',
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
})
# ---
# name: test_entities[sensor.saunum_leil_temperature-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'temperature',
'friendly_name': 'Saunum Leil Temperature',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
}),
'context': <ANY>,
'entity_id': 'sensor.saunum_leil_temperature',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '75.0',
})
# ---
Loading
Loading