Skip to content

Commit 9c571c6

Browse files
authored
Merge pull request #220 from plugwise/loria
Implement support for Anna+Loria
2 parents 0e97afe + da6597a commit 9c571c6

22 files changed

+5558
-5370
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# Changelog
22

3+
# v0.24.0: Improve support for Anna-Loria combination
4+
- Replace mode heat_cool by cool (available modes are: auto, heat, cool)
5+
- Add cooling_enabled switch
6+
- Add dhw_mode/dhw_modes (for selector in HA)
7+
- Add dhw_temperature sensor
8+
- Show Plugwise notifications for non-legacy Smile P1
9+
310
# v0.23.0: Add device availability for non-legacy Smiles
411
- Add back Adam vacation preset, fixing reopened issue #185
512

plugwise/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Plugwise module."""
22

3-
__version__ = "0.23.0"
3+
__version__ = "0.24.0"
44

55
from plugwise.smile import Smile
66
from plugwise.stick import Stick

plugwise/constants.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -467,8 +467,10 @@
467467
# Heater Central related measurements
468468
HEATER_CENTRAL_MEASUREMENTS: Final[dict[str, DATA | UOM]] = {
469469
"boiler_temperature": DATA("water_temperature", TEMP_CELSIUS),
470+
"domestic_hot_water_mode": DATA("dhw_mode", NONE),
470471
"domestic_hot_water_comfort_mode": DATA("dhw_cm_switch", NONE),
471472
"domestic_hot_water_state": DATA("dhw_state", TEMP_CELSIUS),
473+
"domestic_hot_water_temperature": DATA("dhw_temperature", TEMP_CELSIUS),
472474
"elga_status_code": UOM(NONE),
473475
"intended_boiler_temperature": UOM(
474476
TEMP_CELSIUS
@@ -485,6 +487,8 @@
485487
# Used with the Elga heatpump - marcelveldt
486488
"compressor_state": UOM(NONE),
487489
"cooling_state": UOM(NONE),
490+
# Available with the Loria and Elga (newer Anna firmware) heatpumps
491+
"cooling_enabled": DATA("cooling_ena_switch", TEMP_CELSIUS),
488492
# Next 2 keys are used to show the state of the gas-heater used next to the Elga heatpump - marcelveldt
489493
"slave_boiler_state": UOM(NONE),
490494
"flame_state": UOM(NONE), # Also present when there is a single gas-heater
@@ -528,6 +532,7 @@
528532
"battery",
529533
"cooling_activation_outdoor_temperature",
530534
"cooling_deactivation_threshold",
535+
"dhw_temperature",
531536
"temperature",
532537
"electricity_consumed",
533538
"electricity_consumed_interval",
@@ -566,6 +571,7 @@
566571
)
567572

568573
SWITCHES: Final[tuple[str, ...]] = (
574+
"cooling_ena_switch",
569575
"dhw_cm_switch",
570576
"lock",
571577
"relay",
@@ -627,6 +633,7 @@ class SmileSensors(TypedDict, total=False):
627633
battery: float
628634
cooling_activation_outdoor_temperature: float
629635
cooling_deactivation_threshold: float
636+
dhw_temperature: float
630637
temperature: float
631638
electricity_consumed: float
632639
electricity_consumed_interval: float
@@ -668,6 +675,7 @@ class SmileSensors(TypedDict, total=False):
668675
class SmileSwitches(TypedDict, total=False):
669676
"""Smile Switches class."""
670677

678+
cooling_ena_switch: bool
671679
dhw_cm_switch: bool
672680
lock: bool
673681
relay: bool
@@ -696,6 +704,10 @@ class DeviceDataPoints(
696704
):
697705
"""The class covering all possible collected data points."""
698706

707+
# Loria
708+
dhw_mode: str
709+
dhw_modes: list[str]
710+
699711
# Gateway
700712
regulation_mode: str
701713
regulation_modes: list[str]
@@ -724,11 +736,9 @@ class DeviceDataPoints(
724736
class DeviceData(ApplianceData, DeviceDataPoints, TypedDict, total=False):
725737
"""The Device Data class, covering the collected and ordere output-data per device."""
726738

727-
adam_cooling_enabled: bool
739+
cooling_enabled: bool
728740
binary_sensors: SmileBinarySensors
729741
domestic_hot_water_setpoint: ActuatorData
730-
elga_cooling_enabled: bool
731-
lortherm_cooling_enabled: bool
732742
sensors: SmileSensors
733743
switches: SmileSwitches
734744
thermostat: ActuatorData

plugwise/helper.py

Lines changed: 42 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -310,14 +310,12 @@ class SmileHelper:
310310

311311
def __init__(self) -> None:
312312
"""Set the constructor for this class."""
313-
self._adam_cooling_enabled = False
314-
self._allowed_modes: list[str] = []
315-
self._anna_cooling_present = False
316313
self._appliances: etree
317314
self._appl_data: dict[str, ApplianceData] = {}
318315
self._cooling_activation_outdoor_temp: float
319316
self._cooling_deactivation_threshold: float
320317
self._cooling_present = False
318+
self._dhw_allowed_modes: list[str] = []
321319
self._domain_objects: etree
322320
self._heater_id: str | None = None
323321
self._home_location: str
@@ -331,6 +329,7 @@ def __init__(self) -> None:
331329
self._on_off_device = False
332330
self._opentherm_device = False
333331
self._outdoor_temp: float
332+
self._reg_allowed_modes: list[str] = []
334333
self._schedule_old_states: dict[str, dict[str, str]] = {}
335334
self._sched_setpoints: list[float] | None = None
336335
self._smile_legacy = False
@@ -340,21 +339,19 @@ def __init__(self) -> None:
340339
self._system: etree
341340
self._thermo_locs: dict[str, ThermoLoc] = {}
342341
###################################################################
343-
# '_elga_cooling_enabled' refers to the state of the Elga heatpump
342+
# '_cooling_enabled' can refer to the state of the Elga heatpump
344343
# connected to an Anna. For Elga, 'elga_status_code' in [8, 9]
345344
# means cooling mode is available, next to heating mode.
346345
# 'elga_status_code' = 8 means cooling is active, 9 means idle.
347346
#
348-
# '_lortherm_cooling_enabled' refers to the state of the Loria or
347+
# '_cooling_enabled' cam refer to the state of the Loria or
349348
# Thermastage heatpump connected to an Anna. For these,
350-
# 'cooling_state' = on means set to cooling mode, instead of to
349+
# 'cooling_enabled' = on means set to cooling mode, instead of to
351350
# heating mode.
352-
# 'modulation_level' = 100 means cooling is active, 0.0 means idle.
351+
# 'cooling_state' = on means cooling is active.
353352
###################################################################
354-
self._elga_cooling_active = False
355-
self._elga_cooling_enabled = False
356-
self._lortherm_cooling_active = False
357-
self._lortherm_cooling_enabled = False
353+
self._cooling_active = False
354+
self._cooling_enabled = False
358355

359356
self.gateway_id: str | None = None
360357
self.gw_data: GatewayData = {}
@@ -528,15 +525,15 @@ def _appliance_info_finder(self, appliance: etree, appl: Munch) -> Munch:
528525
):
529526
appl.zigbee_mac = found.find("mac_address").text
530527

531-
# Adam: check for active heating/cooling operation-mode
532-
mode_list: list[str] = []
528+
# Adam: check for active heating/cooling operation-mode and collect modes
529+
reg_mode_list: list[str] = []
533530
locator = "./actuator_functionalities/regulation_mode_control_functionality"
534531
if (search := appliance.find(locator)) is not None:
535-
self._adam_cooling_enabled = search.find("mode").text == "cooling"
532+
self._cooling_enabled = search.find("mode").text == "cooling"
536533
if search.find("allowed_modes") is not None:
537534
for mode in search.find("allowed_modes"):
538-
mode_list.append(mode.text)
539-
self._allowed_modes = mode_list
535+
reg_mode_list.append(mode.text)
536+
self._reg_allowed_modes = reg_mode_list
540537

541538
return appl
542539

@@ -584,6 +581,16 @@ def _appliance_info_finder(self, appliance: etree, appl: Munch) -> Munch:
584581
if self._cooling_present
585582
else "Generic heater"
586583
)
584+
585+
# Anna + Loria: collect dhw control operation modes
586+
dhw_mode_list: list[str] = []
587+
locator = "./actuator_functionalities/domestic_hot_water_mode_control_functionality"
588+
if (search := appliance.find(locator)) is not None:
589+
if search.find("allowed_modes") is not None:
590+
for mode in search.find("allowed_modes"):
591+
dhw_mode_list.append(mode.text)
592+
self._dhw_allowed_modes = dhw_mode_list
593+
587594
return appl
588595

589596
# Collect info from Stretches
@@ -863,7 +870,7 @@ def _appliance_measurements(
863870

864871
data[measurement] = appl_p_loc.text # type: ignore [literal-required]
865872
# measurements with states "on" or "off" that need to be passed directly
866-
if measurement not in ["regulation_mode"]:
873+
if measurement not in ["dhw_mode", "regulation_mode"]:
867874
data[measurement] = format_measure(appl_p_loc.text, getattr(attrs, ATTR_UNIT_OF_MEASUREMENT)) # type: ignore [literal-required]
868875

869876
# Anna: save cooling-related measurements for later use
@@ -937,7 +944,7 @@ def _get_appliance_data(self, d_id: str) -> DeviceData:
937944
if "c_heating_state" in data:
938945
# Anna + Elga and Adam + OnOff heater/cooler don't use intended_cental_heating_state
939946
# to show the generic heating state
940-
if (self._anna_cooling_present and "heating_state" in data) or (
947+
if (self._cooling_present and "heating_state" in data) or (
941948
self.smile_name == "Adam" and self._on_off_device
942949
):
943950
if data.get("c_heating_state") and not data.get("heating_state"):
@@ -950,24 +957,25 @@ def _get_appliance_data(self, d_id: str) -> DeviceData:
950957
data.pop("heating_state", None)
951958

952959
if d_id == self._heater_id:
953-
if self._adam_cooling_enabled:
954-
data["adam_cooling_enabled"] = self._adam_cooling_enabled
960+
# Adam
961+
if self._cooling_enabled:
962+
data["cooling_enabled"] = self._cooling_enabled
955963
if self.smile_name == "Smile Anna":
956-
# Use elga_status_code or cooling_state to set the relevant *_cooling_enabled to True
957-
if not self._anna_cooling_present:
964+
# Use elga_status_code or cooling_enabled to set _cooling_enabled to True
965+
if not self._cooling_present:
958966
pass
959967

960968
# Elga:
961969
if "elga_status_code" in data:
962-
self._elga_cooling_enabled = data["elga_status_code"] in [8, 9]
963-
data["elga_cooling_enabled"] = self._elga_cooling_enabled
964-
self._elga_cooling_active = data["elga_status_code"] == 8
970+
self._cooling_enabled = data["elga_status_code"] in [8, 9]
971+
data["cooling_enabled"] = self._cooling_enabled
972+
self._cooling_active = data["elga_status_code"] == 8
965973
data.pop("elga_status_code", None)
966974
# Loria/Thermastate: look at cooling_state, not at cooling_enabled, not available on R32!
967-
elif "cooling_state" in data:
968-
self._lortherm_cooling_enabled = data["cooling_state"]
969-
data["lortherm_cooling_enabled"] = self._lortherm_cooling_enabled
970-
self._lortherm_cooling_active = data["modulation_level"] == 100
975+
elif "cooling_ena_switch" in data:
976+
self._cooling_enabled = data["cooling_ena_switch"]
977+
data["cooling_enabled"] = self._cooling_enabled
978+
self._cooling_active = data["cooling_state"]
971979

972980
# Don't show cooling_state when no cooling present
973981
if not self._cooling_present and "cooling_state" in data:
@@ -1243,7 +1251,7 @@ def _schedules(
12431251
name = self._domain_objects.find(f'./rule[@id="{rule_id}"]/name').text
12441252
schedule: dict[str, list[float]] = {}
12451253
# Only process the active schedule in detail for Anna with cooling
1246-
if self._anna_cooling_present and loc_id != NONE:
1254+
if self._cooling_present and loc_id != NONE:
12471255
locator = f'./rule[@id="{rule_id}"]/directives'
12481256
directives = self._domain_objects.find(locator)
12491257
for directive in directives:
@@ -1269,7 +1277,7 @@ def _schedules(
12691277
if schedules:
12701278
available.remove(NONE)
12711279
last_used = self._last_used_schedule(location, schedules)
1272-
if self._anna_cooling_present and last_used in schedules:
1280+
if self._cooling_present and last_used in schedules:
12731281
schedule_temperatures = schedules_temps(schedules, last_used)
12741282

12751283
return available, selected, schedule_temperatures, last_used
@@ -1367,7 +1375,9 @@ def _update_device_with_dicts(
13671375

13681376
# Add plugwise notification binary_sensor to the relevant gateway
13691377
if d_id == self.gateway_id:
1370-
if self._is_thermostat:
1378+
if self._is_thermostat or (
1379+
not self._smile_legacy and self.smile_type == "power"
1380+
):
13711381
bs_dict["plugwise_notification"] = False
13721382

13731383
device_out.update(data)

0 commit comments

Comments
 (0)