Skip to content

Commit d4e1f77

Browse files
mettolenjoostlek
andauthored
Add sensor entities to Saunum integration (home-assistant#157342)
Co-authored-by: Joost Lekkerkerker <[email protected]>
1 parent e713632 commit d4e1f77

File tree

7 files changed

+376
-3
lines changed

7 files changed

+376
-3
lines changed

homeassistant/components/saunum/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
PLATFORMS: list[Platform] = [
1515
Platform.CLIMATE,
1616
Platform.LIGHT,
17+
Platform.SENSOR,
1718
]
1819

1920
type LeilSaunaConfigEntry = ConfigEntry[LeilSaunaCoordinator]
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"entity": {
3+
"sensor": {
4+
"heater_elements_active": {
5+
"default": "mdi:radiator"
6+
}
7+
}
8+
}
9+
}

homeassistant/components/saunum/quality_scale.yaml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,10 @@ rules:
6060
comment: Integration controls a single device; no dynamic device discovery needed.
6161
entity-category: done
6262
entity-device-class: done
63-
entity-disabled-by-default: todo
63+
entity-disabled-by-default: done
6464
entity-translations: done
6565
exception-translations: done
66-
icon-translations: todo
66+
icon-translations: done
6767
reconfiguration-flow: done
6868
repair-issues:
6969
status: exempt
@@ -74,5 +74,7 @@ rules:
7474

7575
# Platinum
7676
async-dependency: todo
77-
inject-websession: todo
77+
inject-websession:
78+
status: exempt
79+
comment: Integration uses Modbus TCP protocol and does not make HTTP requests.
7880
strict-typing: todo
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
"""Sensor platform for Saunum Leil Sauna Control Unit integration."""
2+
3+
from __future__ import annotations
4+
5+
from collections.abc import Callable
6+
from dataclasses import dataclass
7+
from typing import TYPE_CHECKING
8+
9+
from pysaunum import SaunumData
10+
11+
from homeassistant.components.sensor import (
12+
SensorDeviceClass,
13+
SensorEntity,
14+
SensorEntityDescription,
15+
SensorStateClass,
16+
)
17+
from homeassistant.const import EntityCategory, UnitOfTemperature, UnitOfTime
18+
from homeassistant.core import HomeAssistant
19+
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
20+
21+
from . import LeilSaunaConfigEntry
22+
from .entity import LeilSaunaEntity
23+
24+
if TYPE_CHECKING:
25+
from .coordinator import LeilSaunaCoordinator
26+
27+
PARALLEL_UPDATES = 0
28+
29+
30+
@dataclass(frozen=True, kw_only=True)
31+
class LeilSaunaSensorEntityDescription(SensorEntityDescription):
32+
"""Describes Leil Sauna sensor entity."""
33+
34+
value_fn: Callable[[SaunumData], float | int | None]
35+
36+
37+
SENSORS: tuple[LeilSaunaSensorEntityDescription, ...] = (
38+
LeilSaunaSensorEntityDescription(
39+
key="current_temperature",
40+
translation_key="current_temperature",
41+
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
42+
device_class=SensorDeviceClass.TEMPERATURE,
43+
state_class=SensorStateClass.MEASUREMENT,
44+
value_fn=lambda data: data.current_temperature,
45+
),
46+
LeilSaunaSensorEntityDescription(
47+
key="heater_elements_active",
48+
translation_key="heater_elements_active",
49+
state_class=SensorStateClass.MEASUREMENT,
50+
value_fn=lambda data: data.heater_elements_active,
51+
),
52+
LeilSaunaSensorEntityDescription(
53+
key="on_time",
54+
translation_key="on_time",
55+
native_unit_of_measurement=UnitOfTime.SECONDS,
56+
suggested_unit_of_measurement=UnitOfTime.HOURS,
57+
device_class=SensorDeviceClass.DURATION,
58+
entity_category=EntityCategory.DIAGNOSTIC,
59+
entity_registry_enabled_default=False,
60+
state_class=SensorStateClass.TOTAL_INCREASING,
61+
value_fn=lambda data: data.on_time,
62+
),
63+
)
64+
65+
66+
async def async_setup_entry(
67+
hass: HomeAssistant,
68+
entry: LeilSaunaConfigEntry,
69+
async_add_entities: AddConfigEntryEntitiesCallback,
70+
) -> None:
71+
"""Set up Saunum Leil Sauna sensors from a config entry."""
72+
coordinator = entry.runtime_data
73+
74+
async_add_entities(
75+
LeilSaunaSensorEntity(coordinator, description)
76+
for description in SENSORS
77+
if description.value_fn(coordinator.data) is not None
78+
)
79+
80+
81+
class LeilSaunaSensorEntity(LeilSaunaEntity, SensorEntity):
82+
"""Representation of a Saunum Leil Sauna sensor."""
83+
84+
entity_description: LeilSaunaSensorEntityDescription
85+
86+
def __init__(
87+
self,
88+
coordinator: LeilSaunaCoordinator,
89+
description: LeilSaunaSensorEntityDescription,
90+
) -> None:
91+
"""Initialize the sensor."""
92+
super().__init__(coordinator)
93+
self._attr_unique_id = f"{coordinator.config_entry.entry_id}-{description.key}"
94+
self.entity_description = description
95+
96+
@property
97+
def native_value(self) -> float | int | None:
98+
"""Return the value reported by the sensor."""
99+
return self.entity_description.value_fn(self.coordinator.data)

homeassistant/components/saunum/strings.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,15 @@
3434
"light": {
3535
"name": "[%key:component::light::title%]"
3636
}
37+
},
38+
"sensor": {
39+
"heater_elements_active": {
40+
"name": "Heater elements active",
41+
"unit_of_measurement": "heater elements"
42+
},
43+
"on_time": {
44+
"name": "Total time turned on"
45+
}
3746
}
3847
},
3948
"exceptions": {
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
# serializer version: 1
2+
# name: test_entities[sensor.saunum_leil_heater_elements_active-entry]
3+
EntityRegistryEntrySnapshot({
4+
'aliases': set({
5+
}),
6+
'area_id': None,
7+
'capabilities': dict({
8+
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
9+
}),
10+
'config_entry_id': <ANY>,
11+
'config_subentry_id': <ANY>,
12+
'device_class': None,
13+
'device_id': <ANY>,
14+
'disabled_by': None,
15+
'domain': 'sensor',
16+
'entity_category': None,
17+
'entity_id': 'sensor.saunum_leil_heater_elements_active',
18+
'has_entity_name': True,
19+
'hidden_by': None,
20+
'icon': None,
21+
'id': <ANY>,
22+
'labels': set({
23+
}),
24+
'name': None,
25+
'options': dict({
26+
}),
27+
'original_device_class': None,
28+
'original_icon': None,
29+
'original_name': 'Heater elements active',
30+
'platform': 'saunum',
31+
'previous_unique_id': None,
32+
'suggested_object_id': None,
33+
'supported_features': 0,
34+
'translation_key': 'heater_elements_active',
35+
'unique_id': '01K98T2T85R5GN0ZHYV25VFMMA-heater_elements_active',
36+
'unit_of_measurement': 'heater elements',
37+
})
38+
# ---
39+
# name: test_entities[sensor.saunum_leil_heater_elements_active-state]
40+
StateSnapshot({
41+
'attributes': ReadOnlyDict({
42+
'friendly_name': 'Saunum Leil Heater elements active',
43+
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
44+
'unit_of_measurement': 'heater elements',
45+
}),
46+
'context': <ANY>,
47+
'entity_id': 'sensor.saunum_leil_heater_elements_active',
48+
'last_changed': <ANY>,
49+
'last_reported': <ANY>,
50+
'last_updated': <ANY>,
51+
'state': '0',
52+
})
53+
# ---
54+
# name: test_entities[sensor.saunum_leil_temperature-entry]
55+
EntityRegistryEntrySnapshot({
56+
'aliases': set({
57+
}),
58+
'area_id': None,
59+
'capabilities': dict({
60+
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
61+
}),
62+
'config_entry_id': <ANY>,
63+
'config_subentry_id': <ANY>,
64+
'device_class': None,
65+
'device_id': <ANY>,
66+
'disabled_by': None,
67+
'domain': 'sensor',
68+
'entity_category': None,
69+
'entity_id': 'sensor.saunum_leil_temperature',
70+
'has_entity_name': True,
71+
'hidden_by': None,
72+
'icon': None,
73+
'id': <ANY>,
74+
'labels': set({
75+
}),
76+
'name': None,
77+
'options': dict({
78+
'sensor': dict({
79+
'suggested_display_precision': 1,
80+
}),
81+
}),
82+
'original_device_class': <SensorDeviceClass.TEMPERATURE: 'temperature'>,
83+
'original_icon': None,
84+
'original_name': 'Temperature',
85+
'platform': 'saunum',
86+
'previous_unique_id': None,
87+
'suggested_object_id': None,
88+
'supported_features': 0,
89+
'translation_key': 'current_temperature',
90+
'unique_id': '01K98T2T85R5GN0ZHYV25VFMMA-current_temperature',
91+
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
92+
})
93+
# ---
94+
# name: test_entities[sensor.saunum_leil_temperature-state]
95+
StateSnapshot({
96+
'attributes': ReadOnlyDict({
97+
'device_class': 'temperature',
98+
'friendly_name': 'Saunum Leil Temperature',
99+
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
100+
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
101+
}),
102+
'context': <ANY>,
103+
'entity_id': 'sensor.saunum_leil_temperature',
104+
'last_changed': <ANY>,
105+
'last_reported': <ANY>,
106+
'last_updated': <ANY>,
107+
'state': '75.0',
108+
})
109+
# ---
110+
# name: test_entities[sensor.saunum_leil_total_time_turned_on-entry]
111+
EntityRegistryEntrySnapshot({
112+
'aliases': set({
113+
}),
114+
'area_id': None,
115+
'capabilities': dict({
116+
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
117+
}),
118+
'config_entry_id': <ANY>,
119+
'config_subentry_id': <ANY>,
120+
'device_class': None,
121+
'device_id': <ANY>,
122+
'disabled_by': None,
123+
'domain': 'sensor',
124+
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
125+
'entity_id': 'sensor.saunum_leil_total_time_turned_on',
126+
'has_entity_name': True,
127+
'hidden_by': None,
128+
'icon': None,
129+
'id': <ANY>,
130+
'labels': set({
131+
}),
132+
'name': None,
133+
'options': dict({
134+
'sensor': dict({
135+
'suggested_display_precision': 2,
136+
}),
137+
'sensor.private': dict({
138+
'suggested_unit_of_measurement': <UnitOfTime.HOURS: 'h'>,
139+
}),
140+
}),
141+
'original_device_class': <SensorDeviceClass.DURATION: 'duration'>,
142+
'original_icon': None,
143+
'original_name': 'Total time turned on',
144+
'platform': 'saunum',
145+
'previous_unique_id': None,
146+
'suggested_object_id': None,
147+
'supported_features': 0,
148+
'translation_key': 'on_time',
149+
'unique_id': '01K98T2T85R5GN0ZHYV25VFMMA-on_time',
150+
'unit_of_measurement': <UnitOfTime.HOURS: 'h'>,
151+
})
152+
# ---
153+
# name: test_entities[sensor.saunum_leil_total_time_turned_on-state]
154+
StateSnapshot({
155+
'attributes': ReadOnlyDict({
156+
'device_class': 'duration',
157+
'friendly_name': 'Saunum Leil Total time turned on',
158+
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
159+
'unit_of_measurement': <UnitOfTime.HOURS: 'h'>,
160+
}),
161+
'context': <ANY>,
162+
'entity_id': 'sensor.saunum_leil_total_time_turned_on',
163+
'last_changed': <ANY>,
164+
'last_reported': <ANY>,
165+
'last_updated': <ANY>,
166+
'state': '1.0',
167+
})
168+
# ---

0 commit comments

Comments
 (0)