Skip to content

Commit 0be4fad

Browse files
authored
Merge pull request #517 from plugwise/optimize-4
More improvements
2 parents dd03a01 + fd04279 commit 0be4fad

File tree

3 files changed

+142
-212
lines changed

3 files changed

+142
-212
lines changed

plugwise/common.py

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,13 @@
44
"""
55
from __future__ import annotations
66

7-
from plugwise.constants import ModelData
7+
from plugwise.constants import (
8+
ANNA,
9+
SWITCH_GROUP_TYPES,
10+
DeviceData,
11+
ModelData,
12+
SensorType,
13+
)
814
from plugwise.util import (
915
check_heater_central,
1016
check_model,
@@ -22,12 +28,15 @@ class SmileCommon:
2228
def __init__(self) -> None:
2329
"""Init."""
2430
self._appliances: etree
31+
self._count: int
2532
self._domain_objects: etree
2633
self._cooling_present: bool
2734
self._heater_id: str
2835
self._on_off_device: bool
2936
self._opentherm_device: bool
37+
self.gw_devices: dict[str, DeviceData]
3038
self.smile_name: str
39+
self.smile_type: str
3140

3241
def smile(self, name: str) -> bool:
3342
"""Helper-function checking the smile-name."""
@@ -149,3 +158,69 @@ def _get_zigbee_data(self, module: etree, model_data: ModelData, legacy: bool) -
149158
model_data["zigbee_mac_address"] = zb_node.find("mac_address").text
150159
model_data["reachable"] = zb_node.find("reachable").text == "true"
151160

161+
def _get_group_switches(self) -> dict[str, DeviceData]:
162+
"""Helper-function for smile.py: get_all_devices().
163+
164+
Collect switching- or pump-group info.
165+
"""
166+
switch_groups: dict[str, DeviceData] = {}
167+
# P1 and Anna don't have switchgroups
168+
if self.smile_type == "power" or self.smile(ANNA):
169+
return switch_groups
170+
171+
for group in self._domain_objects.findall("./group"):
172+
members: list[str] = []
173+
group_id = group.attrib["id"]
174+
group_name = group.find("name").text
175+
group_type = group.find("type").text
176+
group_appliances = group.findall("appliances/appliance")
177+
for item in group_appliances:
178+
# Check if members are not orphaned - stretch
179+
if item.attrib["id"] in self.gw_devices:
180+
members.append(item.attrib["id"])
181+
182+
if group_type in SWITCH_GROUP_TYPES and members:
183+
switch_groups.update(
184+
{
185+
group_id: {
186+
"dev_class": group_type,
187+
"model": "Switchgroup",
188+
"name": group_name,
189+
"members": members,
190+
},
191+
},
192+
)
193+
self._count += 4
194+
195+
return switch_groups
196+
197+
def power_data_energy_diff(
198+
self,
199+
measurement: str,
200+
net_string: SensorType,
201+
f_val: float | int,
202+
direct_data: DeviceData,
203+
) -> DeviceData:
204+
"""Calculate differential energy."""
205+
if (
206+
"electricity" in measurement
207+
and "phase" not in measurement
208+
and "interval" not in net_string
209+
):
210+
diff = 1
211+
if "produced" in measurement:
212+
diff = -1
213+
if net_string not in direct_data["sensors"]:
214+
tmp_val: float | int = 0
215+
else:
216+
tmp_val = direct_data["sensors"][net_string]
217+
218+
if isinstance(f_val, int):
219+
tmp_val += f_val * diff
220+
else:
221+
tmp_val += float(f_val * diff)
222+
tmp_val = float(f"{round(tmp_val, 3):.3f}")
223+
224+
direct_data["sensors"][net_string] = tmp_val
225+
226+
return direct_data

plugwise/helper.py

Lines changed: 59 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
SENSORS,
3434
SPECIAL_PLUG_TYPES,
3535
SPECIALS,
36-
SWITCH_GROUP_TYPES,
3736
SWITCHES,
3837
TEMP_CELSIUS,
3938
THERMOSTAT_CLASSES,
@@ -195,8 +194,6 @@ class SmileHelper(SmileCommon):
195194

196195
def __init__(self) -> None:
197196
"""Set the constructor for this class."""
198-
self._cooling_activation_outdoor_temp: float
199-
self._cooling_deactivation_threshold: float
200197
self._cooling_present: bool
201198
self._count: int
202199
self._dhw_allowed_modes: list[str] = []
@@ -212,7 +209,6 @@ def __init__(self) -> None:
212209
self._notifications: dict[str, dict[str, str]] = {}
213210
self._on_off_device: bool
214211
self._opentherm_device: bool
215-
self._outdoor_temp: float
216212
self._reg_allowed_modes: list[str] = []
217213
self._schedule_old_states: dict[str, dict[str, str]]
218214
self._status: etree
@@ -260,7 +256,7 @@ def _all_locations(self) -> None:
260256

261257
self.loc_data[loc.loc_id] = {"name": loc.name}
262258

263-
def _energy_device_info_finder(self, appliance: etree, appl: Munch) -> Munch:
259+
def _energy_device_info_finder(self, appl: Munch, appliance: etree) -> Munch:
264260
"""Helper-function for _appliance_info_finder().
265261
266262
Collect energy device info (Smartmeter, Plug): firmware, model and vendor name.
@@ -296,62 +292,73 @@ def _energy_device_info_finder(self, appliance: etree, appl: Munch) -> Munch:
296292

297293
def _appliance_info_finder(self, appl: Munch, appliance: etree) -> Munch:
298294
"""Collect device info (Smile/Stretch, Thermostats, OpenTherm/On-Off): firmware, model and vendor name."""
299-
# Collect gateway device info
300-
if appl.pwclass == "gateway":
301-
self.gateway_id = appliance.attrib["id"]
302-
appl.firmware = self.smile_fw_version
303-
appl.hardware = self.smile_hw_version
304-
appl.mac = self.smile_mac_address
305-
appl.model = self.smile_model
306-
appl.name = self.smile_name
307-
appl.vendor_name = "Plugwise"
308-
309-
# Adam: look for the ZigBee MAC address of the Smile
310-
if self.smile(ADAM) and (
311-
(found := self._domain_objects.find(".//protocols/zig_bee_coordinator")) is not None
312-
):
295+
match appl.pwclass:
296+
case "gateway":
297+
# Collect gateway device info
298+
return self._appl_gateway_info(appl, appliance)
299+
case _ as dev_class if dev_class in THERMOSTAT_CLASSES:
300+
# Collect thermostat device info
301+
return self._appl_thermostat_info(appl, appliance)
302+
case "heater_central":
303+
# Collect heater_central device info
304+
self._appl_heater_central_info(appl, appliance)
305+
self._appl_dhw_mode_info(appl, appliance)
306+
return appl
307+
case _:
308+
# Collect info from power-related devices (Plug, Aqara Smart Plug)
309+
return self._energy_device_info_finder(appl, appliance)
310+
311+
def _appl_gateway_info(self, appl: Munch, appliance: etree) -> Munch:
312+
"""Helper-function for _appliance_info_finder()."""
313+
self.gateway_id = appliance.attrib["id"]
314+
appl.firmware = self.smile_fw_version
315+
appl.hardware = self.smile_hw_version
316+
appl.mac = self.smile_mac_address
317+
appl.model = self.smile_model
318+
appl.name = self.smile_name
319+
appl.vendor_name = "Plugwise"
320+
321+
# Adam: collect the ZigBee MAC address of the Smile
322+
if self.smile(ADAM):
323+
if (found := self._domain_objects.find(".//protocols/zig_bee_coordinator")) is not None:
313324
appl.zigbee_mac = found.find("mac_address").text
314325

315-
# Adam: collect regulation_modes and check for cooling, indicating cooling-mode is present
316-
reg_mode_list: list[str] = []
317-
locator = "./actuator_functionalities/regulation_mode_control_functionality"
318-
if (search := appliance.find(locator)) is not None:
319-
if search.find("allowed_modes") is not None:
320-
for mode in search.find("allowed_modes"):
321-
reg_mode_list.append(mode.text)
322-
if mode.text == "cooling":
323-
self._cooling_present = True
324-
self._reg_allowed_modes = reg_mode_list
325-
326-
# Adam: check for presence of gateway_modes
326+
# Also, collect regulation_modes and check for cooling, indicating cooling-mode is present
327+
self._appl_regulation_mode_info(appliance)
328+
329+
# Finally, collect the gateway_modes
327330
self._gw_allowed_modes = []
328331
locator = "./actuator_functionalities/gateway_mode_control_functionality[type='gateway_mode']/allowed_modes"
329332
if appliance.find(locator) is not None:
330333
# Limit the possible gateway-modes
331334
self._gw_allowed_modes = ["away", "full", "vacation"]
332335

333-
return appl
334-
335-
# Collect thermostat device info
336-
if appl.pwclass in THERMOSTAT_CLASSES:
337-
return self._appl_thermostat_info(appl, appliance)
338-
339-
# Collect extra heater_central device info
340-
if appl.pwclass == "heater_central":
341-
appl = self._appl_heater_central_info(appl, appliance)
342-
# Anna + Loria: collect dhw control operation modes
343-
dhw_mode_list: list[str] = []
344-
locator = "./actuator_functionalities/domestic_hot_water_mode_control_functionality"
345-
if (search := appliance.find(locator)) is not None:
346-
if search.find("allowed_modes") is not None:
347-
for mode in search.find("allowed_modes"):
348-
dhw_mode_list.append(mode.text)
349-
self._dhw_allowed_modes = dhw_mode_list
336+
return appl
350337

351-
return appl
338+
def _appl_regulation_mode_info(self, appliance: etree) -> None:
339+
"""Helper-function for _appliance_info_finder()."""
340+
reg_mode_list: list[str] = []
341+
locator = "./actuator_functionalities/regulation_mode_control_functionality"
342+
if (search := appliance.find(locator)) is not None:
343+
if search.find("allowed_modes") is not None:
344+
for mode in search.find("allowed_modes"):
345+
reg_mode_list.append(mode.text)
346+
if mode.text == "cooling":
347+
self._cooling_present = True
348+
self._reg_allowed_modes = reg_mode_list
349+
350+
def _appl_dhw_mode_info(self, appl: Munch, appliance: etree) -> Munch:
351+
"""Helper-function for _appliance_info_finder().
352352
353-
# Collect info from power-related devices (Plug, Aqara Smart Plug)
354-
appl = self._energy_device_info_finder(appliance, appl)
353+
Collect dhw control operation modes - Anna + Loria.
354+
"""
355+
dhw_mode_list: list[str] = []
356+
locator = "./actuator_functionalities/domestic_hot_water_mode_control_functionality"
357+
if (search := appliance.find(locator)) is not None:
358+
if search.find("allowed_modes") is not None:
359+
for mode in search.find("allowed_modes"):
360+
dhw_mode_list.append(mode.text)
361+
self._dhw_allowed_modes = dhw_mode_list
355362

356363
return appl
357364

@@ -366,7 +373,7 @@ def _p1_smartmeter_info_finder(self, appl: Munch) -> None:
366373
appl.pwclass = "smartmeter"
367374
appl.zigbee_mac = None
368375
location = self._domain_objects.find(f'./location[@id="{loc_id}"]')
369-
appl = self._energy_device_info_finder(location, appl)
376+
appl = self._energy_device_info_finder(appl, location)
370377

371378
self.gw_devices[appl.dev_id] = {"dev_class": appl.pwclass}
372379
self._count += 1
@@ -587,20 +594,6 @@ def _appliance_measurements(
587594
appl_p_loc.text, getattr(attrs, ATTR_UNIT_OF_MEASUREMENT)
588595
)
589596
data["sensors"][s_key] = s_value
590-
# Anna: save cooling-related measurements for later use
591-
# Use the local outdoor temperature as reference for turning cooling on/off
592-
if measurement == "cooling_activation_outdoor_temperature":
593-
self._cooling_activation_outdoor_temp = data["sensors"][
594-
"cooling_activation_outdoor_temperature"
595-
]
596-
if measurement == "cooling_deactivation_threshold":
597-
self._cooling_deactivation_threshold = data["sensors"][
598-
"cooling_deactivation_threshold"
599-
]
600-
if measurement == "outdoor_air_temperature":
601-
self._outdoor_temp = data["sensors"][
602-
"outdoor_air_temperature"
603-
]
604597
case _ as measurement if measurement in SWITCHES:
605598
sw_key = cast(SwitchType, measurement)
606599
sw_value = appl_p_loc.text in ["on", "true"]
@@ -952,42 +945,6 @@ def _thermostat_uri(self, loc_id: str) -> str:
952945

953946
return f"{LOCATIONS};id={loc_id}/thermostat;id={thermostat_functionality_id}"
954947

955-
def _get_group_switches(self) -> dict[str, DeviceData]:
956-
"""Helper-function for smile.py: get_all_devices().
957-
958-
Collect switching- or pump-group info.
959-
"""
960-
switch_groups: dict[str, DeviceData] = {}
961-
# P1 and Anna don't have switchgroups
962-
if self.smile_type == "power" or self.smile(ANNA):
963-
return switch_groups
964-
965-
for group in self._domain_objects.findall("./group"):
966-
members: list[str] = []
967-
group_id = group.attrib["id"]
968-
group_name = group.find("name").text
969-
group_type = group.find("type").text
970-
group_appliances = group.findall("appliances/appliance")
971-
# Check if members are not orphaned
972-
for item in group_appliances:
973-
if item.attrib["id"] in self.gw_devices:
974-
members.append(item.attrib["id"])
975-
976-
if group_type in SWITCH_GROUP_TYPES and members:
977-
switch_groups.update(
978-
{
979-
group_id: {
980-
"dev_class": group_type,
981-
"model": "Switchgroup",
982-
"name": group_name,
983-
"members": members,
984-
},
985-
},
986-
)
987-
self._count += 4
988-
989-
return switch_groups
990-
991948
def _heating_valves(self) -> int | bool:
992949
"""Helper-function for smile.py: _device_data_adam().
993950
@@ -1005,37 +962,6 @@ def _heating_valves(self) -> int | bool:
1005962

1006963
return False if loc_found == 0 else open_valve_count
1007964

1008-
def power_data_energy_diff(
1009-
self,
1010-
measurement: str,
1011-
net_string: SensorType,
1012-
f_val: float | int,
1013-
direct_data: DeviceData,
1014-
) -> DeviceData:
1015-
"""Calculate differential energy."""
1016-
if (
1017-
"electricity" in measurement
1018-
and "phase" not in measurement
1019-
and "interval" not in net_string
1020-
):
1021-
diff = 1
1022-
if "produced" in measurement:
1023-
diff = -1
1024-
if net_string not in direct_data["sensors"]:
1025-
tmp_val: float | int = 0
1026-
else:
1027-
tmp_val = direct_data["sensors"][net_string]
1028-
1029-
if isinstance(f_val, int):
1030-
tmp_val += f_val * diff
1031-
else:
1032-
tmp_val += float(f_val * diff)
1033-
tmp_val = float(f"{round(tmp_val, 3):.3f}")
1034-
1035-
direct_data["sensors"][net_string] = tmp_val
1036-
1037-
return direct_data
1038-
1039965
def _power_data_peak_value(self, loc: Munch) -> Munch:
1040966
"""Helper-function for _power_data_from_location() and _power_data_from_modules()."""
1041967
loc.found = True

0 commit comments

Comments
 (0)