Skip to content

Commit 72457d9

Browse files
committed
+ Added missed sensors/switches and selects
1 parent 6859827 commit 72457d9

File tree

6 files changed

+236
-9
lines changed

6 files changed

+236
-9
lines changed

custom_components/systemair/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
Platform.BINARY_SENSOR,
2424
Platform.SWITCH,
2525
Platform.NUMBER,
26+
Platform.SELECT,
2627
]
2728

2829

@@ -61,4 +62,4 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
6162
async def async_reload_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
6263
"""Reload config entry."""
6364
await async_unload_entry(hass, entry)
64-
await async_setup_entry(hass, entry)
65+
await async_setup_entry(hass, entry)

custom_components/systemair/api.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,12 @@ async def get_all_data(self) -> dict[str, Any]:
8484
read_blocks = [
8585
(1001, 62),
8686
(1101, 80),
87+
(1271, 4),
88+
(1353, 1),
8789
(2001, 50),
8890
(2505, 1),
8991
(3002, 116),
92+
(4001, 12),
9093
(4100, 1),
9194
(7005, 2),
9295
(12102, 40),
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
"""Select platform for Systemair."""
2+
3+
from __future__ import annotations
4+
5+
import asyncio
6+
import asyncio.exceptions
7+
from dataclasses import dataclass
8+
from typing import TYPE_CHECKING, Any
9+
10+
from homeassistant.components.select import SelectEntity, SelectEntityDescription
11+
from homeassistant.const import EntityCategory
12+
from homeassistant.exceptions import HomeAssistantError
13+
14+
from .entity import SystemairEntity
15+
from .modbus import ModbusParameter, parameter_map
16+
17+
if TYPE_CHECKING:
18+
from homeassistant.core import HomeAssistant
19+
from homeassistant.helpers.entity_platform import AddEntitiesCallback
20+
21+
from .coordinator import SystemairDataUpdateCoordinator
22+
from .data import SystemairConfigEntry
23+
24+
25+
@dataclass(kw_only=True, frozen=True)
26+
class SystemairSelectEntityDescription(SelectEntityDescription):
27+
"""Describes a Systemair select entity."""
28+
29+
registry: ModbusParameter
30+
options_map: dict[int, str]
31+
32+
33+
ENTITY_DESCRIPTIONS = (
34+
SystemairSelectEntityDescription(
35+
key="temperature_control_mode",
36+
translation_key="temperature_control_mode",
37+
icon="mdi:thermostat",
38+
entity_category=EntityCategory.CONFIG,
39+
registry=parameter_map["REG_TC_CONTROL_MODE"],
40+
options_map={
41+
0: "supply_air",
42+
1: "room_air",
43+
2: "extract_air",
44+
},
45+
),
46+
SystemairSelectEntityDescription(
47+
key="fan_regulation_unit",
48+
translation_key="fan_regulation_unit",
49+
icon="mdi:fan-speed-1",
50+
entity_category=EntityCategory.CONFIG,
51+
registry=parameter_map["REG_FAN_REGULATION_UNIT"],
52+
options_map={
53+
0: "manual_percent",
54+
1: "manual_rpm",
55+
2: "pressure",
56+
3: "flow",
57+
4: "external",
58+
},
59+
),
60+
SystemairSelectEntityDescription(
61+
key="defrosting_mode",
62+
translation_key="defrosting_mode",
63+
icon="mdi:snowflake-melt",
64+
entity_category=EntityCategory.CONFIG,
65+
registry=parameter_map["REG_DEFROSTING_MODE"],
66+
options_map={
67+
0: "soft",
68+
1: "normal",
69+
2: "hard",
70+
},
71+
),
72+
)
73+
74+
75+
async def async_setup_entry(
76+
hass: HomeAssistant, # noqa: ARG001
77+
entry: SystemairConfigEntry,
78+
async_add_entities: AddEntitiesCallback,
79+
) -> None:
80+
"""Set up the select platform."""
81+
async_add_entities(
82+
SystemairSelect(
83+
coordinator=entry.runtime_data.coordinator,
84+
entity_description=entity_description,
85+
)
86+
for entity_description in ENTITY_DESCRIPTIONS
87+
)
88+
89+
90+
class SystemairSelect(SystemairEntity, SelectEntity):
91+
"""Systemair select class."""
92+
93+
_attr_has_entity_name = True
94+
entity_description: SystemairSelectEntityDescription
95+
96+
def __init__(
97+
self,
98+
coordinator: SystemairDataUpdateCoordinator,
99+
entity_description: SystemairSelectEntityDescription,
100+
) -> None:
101+
"""Initialize the select class."""
102+
super().__init__(coordinator)
103+
self.entity_description = entity_description
104+
self._attr_unique_id = f"{coordinator.config_entry.entry_id}-{entity_description.key}"
105+
self._attr_options = list(self.entity_description.options_map.values())
106+
self._option_to_value_map = {v: k for k, v in self.entity_description.options_map.items()}
107+
108+
@property
109+
def current_option(self) -> str | None:
110+
"""Return the selected entity option to represent the entity state."""
111+
value = self.coordinator.get_modbus_data(self.entity_description.registry)
112+
return self.entity_description.options_map.get(int(value))
113+
114+
async def async_select_option(self, option: str) -> None:
115+
"""Change the selected option."""
116+
value = self._option_to_value_map.get(option)
117+
if value is None:
118+
return
119+
120+
try:
121+
await self.coordinator.set_modbus_data(self.entity_description.registry, value)
122+
await asyncio.sleep(1)
123+
except (asyncio.exceptions.TimeoutError, ConnectionError) as exc:
124+
raise HomeAssistantError from exc
125+
finally:
126+
await self.coordinator.async_refresh()

custom_components/systemair/sensor.py

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,16 @@
3131

3232
VALUE_MAP_TO_ALARM_STATE = {value: key for key, value in ALARM_STATE_TO_VALUE_MAP.items()}
3333

34+
IAQ_LEVEL_MAP = {0: "Perfect", 1: "Good", 2: "Improving"}
35+
DEMAND_CONTROLLER_MAP = {0: "CO2", 1: "RH"}
36+
DEFROSTING_STATE_MAP = {
37+
0: "Normal",
38+
1: "Bypass",
39+
2: "Stop",
40+
3: "Secondary air",
41+
4: "Error",
42+
}
43+
3444

3545
@dataclass(kw_only=True, frozen=True)
3646
class SystemairSensorEntityDescription(SensorEntityDescription):
@@ -119,6 +129,32 @@ class SystemairSensorEntityDescription(SensorEntityDescription):
119129
registry=parameter_map["REG_FILTER_REMAINING_TIME_L"],
120130
entity_category=EntityCategory.DIAGNOSTIC,
121131
),
132+
SystemairSensorEntityDescription(
133+
key="iaq_level",
134+
translation_key="iaq_level",
135+
icon="mdi:air-filter",
136+
device_class=SensorDeviceClass.ENUM,
137+
options=["Perfect", "Good", "Improving"],
138+
registry=parameter_map["REG_IAQ_LEVEL"],
139+
),
140+
SystemairSensorEntityDescription(
141+
key="demand_active_controller",
142+
translation_key="demand_active_controller",
143+
icon="mdi:tune-variant",
144+
device_class=SensorDeviceClass.ENUM,
145+
options=["CO2", "RH"],
146+
registry=parameter_map["REG_DEMC_ACTIVE_CONTROLLER"],
147+
entity_category=EntityCategory.DIAGNOSTIC,
148+
),
149+
SystemairSensorEntityDescription(
150+
key="defrosting_state",
151+
translation_key="defrosting_state",
152+
icon="mdi:snowflake-alert",
153+
device_class=SensorDeviceClass.ENUM,
154+
options=["Normal", "Bypass", "Stop", "Secondary air", "Error"],
155+
registry=parameter_map["REG_DEFROSTING_STATE"],
156+
entity_category=EntityCategory.DIAGNOSTIC,
157+
),
122158
*(
123159
SystemairSensorEntityDescription(
124160
key=f"alarm_{param.short.lower()}",
@@ -169,9 +205,17 @@ def __init__(
169205
def native_value(self) -> str | None:
170206
"""Return the native value of the sensor."""
171207
value = self.coordinator.get_modbus_data(self.entity_description.registry)
208+
value = int(value)
209+
210+
key = self.entity_description.key
211+
if key == "iaq_level":
212+
return IAQ_LEVEL_MAP.get(value)
213+
if key == "demand_active_controller":
214+
return DEMAND_CONTROLLER_MAP.get(value)
215+
if key == "defrosting_state":
216+
return DEFROSTING_STATE_MAP.get(value)
172217

173218
if self.device_class == SensorDeviceClass.ENUM:
174-
value = int(value)
175219
return VALUE_MAP_TO_ALARM_STATE.get(value, "Inactive")
176220

177-
return str(value)
221+
return str(value)

custom_components/systemair/switch.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22

33
from __future__ import annotations
44

5+
import asyncio
56
from dataclasses import dataclass
67
from typing import TYPE_CHECKING, Any
78

89
from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription
10+
from homeassistant.const import EntityCategory
911

1012
from .entity import SystemairEntity
1113
from .modbus import ModbusParameter, parameter_map
@@ -38,6 +40,13 @@ class SystemairSwitchEntityDescription(SwitchEntityDescription):
3840
icon="mdi:snowflake",
3941
registry=parameter_map["REG_FREE_COOLING_ON_OFF"],
4042
),
43+
SystemairSwitchEntityDescription(
44+
key="manual_fan_stop_allowed",
45+
translation_key="manual_fan_stop_allowed",
46+
icon="mdi:fan-off",
47+
registry=parameter_map["REG_FAN_MANUAL_STOP_ALLOWED"],
48+
entity_category=EntityCategory.CONFIG,
49+
),
4150
)
4251

4352

@@ -78,12 +87,16 @@ def is_on(self) -> bool:
7887
"""Return true if the switch is on."""
7988
return self.coordinator.get_modbus_data(self.entity_description.registry) != 0
8089

90+
async def _async_set_state(self, value: bool) -> None:
91+
"""Turn on or off the switch."""
92+
await self.coordinator.set_modbus_data(self.entity_description.registry, value=value)
93+
await asyncio.sleep(1)
94+
await self.coordinator.async_request_refresh()
95+
8196
async def async_turn_on(self, **_: Any) -> None:
8297
"""Turn on the switch."""
83-
await self.coordinator.set_modbus_data(self.entity_description.registry, value=True)
84-
await self.coordinator.async_request_refresh()
98+
await self._async_set_state(True)
8599

86100
async def async_turn_off(self, **_: Any) -> None:
87101
"""Turn off the switch."""
88-
await self.coordinator.set_modbus_data(self.entity_description.registry, value=False)
89-
await self.coordinator.async_request_refresh()
102+
await self._async_set_state(False)

custom_components/systemair/translations/en.json

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@
7272
},
7373
"extract_air_relative_humidity": {
7474
"name": "Extract air relative humidity"
75-
},
75+
},
7676
"meter_saf_rpm": {
7777
"name": "Supply air fan RPM"
7878
},
@@ -90,6 +90,15 @@
9090
},
9191
"filter_remaining_time": {
9292
"name": "Filter remaining time"
93+
},
94+
"iaq_level": {
95+
"name": "Indoor Air Quality"
96+
},
97+
"demand_active_controller": {
98+
"name": "Demand Active Controller"
99+
},
100+
"defrosting_state": {
101+
"name": "Defrosting State"
93102
}
94103
},
95104
"switch": {
@@ -98,7 +107,38 @@
98107
},
99108
"free_cooling": {
100109
"name": "Free Cooling"
110+
},
111+
"manual_fan_stop_allowed": {
112+
"name": "Allow manual fan stop"
113+
}
114+
},
115+
"select": {
116+
"temperature_control_mode": {
117+
"name": "Temperature Control Mode",
118+
"state": {
119+
"supply_air": "Supply air",
120+
"room_air": "Room air",
121+
"extract_air": "Extract air"
122+
}
123+
},
124+
"fan_regulation_unit": {
125+
"name": "Fan Regulation Unit",
126+
"state": {
127+
"manual_percent": "Manual, %",
128+
"manual_rpm": "Manual, RPM",
129+
"pressure": "Pressure",
130+
"flow": "Flow",
131+
"external": "External"
132+
}
133+
},
134+
"defrosting_mode": {
135+
"name": "Defrosting Mode",
136+
"state": {
137+
"soft": "Soft",
138+
"normal": "Normal",
139+
"hard": "Hard"
140+
}
101141
}
102142
}
103143
}
104-
}
144+
}

0 commit comments

Comments
 (0)