Skip to content

Commit 1432005

Browse files
committed
Further progress
1 parent ff036ef commit 1432005

File tree

4 files changed

+164
-62
lines changed

4 files changed

+164
-62
lines changed

plugwise/common.py

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -197,22 +197,21 @@ def _get_groups(self) -> None:
197197
if self.smile.type == "power" or self.check_name(ANNA):
198198
return
199199

200-
for group in self._domain_objects.findall("./group"):
200+
for group in self._domain_objects.group:
201201
members: list[str] = []
202-
group_id = group.get("id")
203-
group_name = group.find("name").text
204-
group_type = group.find("type").text
205-
group_appliances = group.findall("appliances/appliance")
206-
for item in group_appliances:
202+
if not group.appliances:
203+
continue
204+
205+
for item in group.appliances.appliance:
207206
# Check if members are not orphaned - stretch
208-
if item.get("id") in self.gw_entities:
209-
members.append(item.get("id"))
207+
if item.id in self.gw_entities:
208+
members.append(item.id)
210209

211-
if group_type in GROUP_TYPES and members and group_id:
212-
self.gw_entities[group_id] = {
213-
"dev_class": group_type,
210+
if group.type in GROUP_TYPES and members and group.id:
211+
self.gw_entities[group.id] = {
212+
"dev_class": group.type,
214213
"model": "Group",
215-
"name": group_name,
214+
"name": group.name,
216215
"members": members,
217216
"vendor": "Plugwise",
218217
}

plugwise/helper.py

Lines changed: 43 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
ThermoLoc,
4444
ToggleNameType,
4545
)
46+
from plugwise.model import Appliance, ApplianceType, OffsetFunctionality
4647
from plugwise.util import (
4748
check_model,
4849
collect_power_values,
@@ -57,15 +58,6 @@
5758
from packaging import version
5859

5960

60-
def extend_plug_device_class(appl: Munch, appliance: etree.Element) -> None:
61-
"""Extend device_class name of Plugs (Plugwise and Aqara) - Pw-Beta Issue #739."""
62-
63-
if (description := appliance.description) is not None and (
64-
"ZigBee protocol" in description or "smart plug" in description
65-
):
66-
appl.pwclass = f"{appl.pwclass}_plug"
67-
68-
6961
def search_actuator_functionalities(
7062
appliance: etree.Element, actuator: str
7163
) -> etree.Element | None:
@@ -130,28 +122,34 @@ def _get_appliances(self) -> None:
130122

131123
# Don't collect data for the OpenThermGateway appliance, skip thermostat(s)
132124
# without actuator_functionalities, should be an orphaned device(s) (Core #81712)
133-
if appl.pwclass == "open_therm_gateway" or (
134-
appl.pwclass == "thermostat"
135-
and appliance.find("actuator_functionalities/") is None
125+
if appliance.type == ApplianceType.OPENTHERMGW or (
126+
appliance.type == ApplianceType.THERMOSTAT
127+
and appliance.actuator_functionalities is None
136128
):
137129
continue
138130

139-
if (appl_loc := appliance.location) is not None:
140-
appl.location = appl_loc.get("id")
131+
if appliance.location is not None:
132+
appl.fixed_location = appliance.id
141133
# Set location to the _home_loc_id when the appliance-location is not found,
142134
# except for thermostat-devices without a location, they are not active
143-
elif appl.pwclass not in THERMOSTAT_CLASSES:
144-
appl.location = self._home_loc_id
135+
elif appliance.type not in THERMOSTAT_CLASSES:
136+
appliance.fixed_location = self._home_loc_id
145137

146138
# Don't show orphaned thermostat-types
147-
if appl.pwclass in THERMOSTAT_CLASSES and appl.location is None:
139+
if appliance.type in THERMOSTAT_CLASSES and appliance.location is None:
148140
continue
149141

150-
extend_plug_device_class(appl, appliance)
142+
# Extend device_class name of Plugs (Plugwise and Aqara) - Pw-Beta Issue #739
143+
if appliance.description is not None and (
144+
"ZigBee protocol" in appliance.description
145+
or "smart plug" in appliance.description
146+
):
147+
appliance.type = f"{appliance.type}_plug"
151148

152-
# Collect appliance info, skip orphaned/removed devices
153-
if not (appl := self._appliance_info_finder(appl, appliance)):
154-
continue
149+
# TODO: recreate functionality
150+
# # Collect appliance info, skip orphaned/removed devices
151+
# if not (appl := self._appliance_info_finder(appl, appliance)):
152+
# continue
155153

156154
self._create_gw_entities(appl)
157155

@@ -230,12 +228,13 @@ def _get_locations(self) -> None:
230228
"Error, location Home (building) not found!"
231229
) # pragma: no cover
232230

233-
def _appliance_info_finder(self, appl: Munch, appliance: etree.Element) -> Munch:
231+
def _appliance_info_finder(self, appliance: Appliance) -> Appliance:
234232
"""Collect info for all appliances found."""
235-
match appl.pwclass:
236-
case "gateway":
237-
# Collect gateway entity info
238-
return self._appl_gateway_info(appl, appliance)
233+
match application.type:
234+
# No longer needed since we have a Gateway
235+
# case "gateway":
236+
# # Collect gateway entity info
237+
# return self._appl_gateway_info(appl, appliance)
239238
case _ as dev_class if dev_class in THERMOSTAT_CLASSES:
240239
# Collect thermostat entity info
241240
return self._appl_thermostat_info(appl, appliance)
@@ -269,24 +268,14 @@ def _appliance_info_finder(self, appl: Munch, appliance: etree.Element) -> Munch
269268
case _: # pragma: no cover
270269
return Munch()
271270

272-
def _appl_gateway_info(self, appl: Munch, appliance: etree.Element) -> Munch:
271+
def _appl_gateway_info(self, appliance: Appliance) -> Appliance:
273272
"""Helper-function for _appliance_info_finder()."""
274-
self._gateway_id = appl.entity_id
275-
locator = "./gateway/firmware_version"
276-
appl.firmware = self._domain_objects.find(locator).text
277-
appl.hardware = self.smile.hw_version
278-
appl.mac = self.smile.mac_address
279-
appl.model = self.smile.model
280-
appl.model_id = self.smile.model_id
281-
appl.name = self.smile.name
282-
appl.vendor_name = "Plugwise"
273+
self._gateway_id = application.id
283274

284275
# Adam: collect the ZigBee MAC address of the Smile
285-
if self.check_name(ADAM):
286-
if (
287-
found := self._domain_objects.find(".//protocols/zig_bee_coordinator")
288-
) is not None:
289-
appl.zigbee_mac = found.find("mac_address").text
276+
if ADAM in appliance.name:
277+
if (found := appliance.protocols.zig_bee_coordinator) is not None:
278+
application.zigbee_mac = found.mac_address
290279

291280
# Also, collect regulation_modes and check for cooling, indicating cooling-mode is present
292281
self._reg_allowed_modes = self._get_appl_actuator_modes(
@@ -317,12 +306,19 @@ def _get_appl_actuator_modes(
317306

318307
def _get_appliances_with_offset_functionality(self) -> list[str]:
319308
"""Helper-function collecting all appliance that have offset_functionality."""
320-
therm_list: list[str] = []
321-
offset_appls = self._domain_objects.findall(
322-
'.//actuator_functionalities/offset_functionality[type="temperature_offset"]/offset/../../..'
323-
)
324-
for item in offset_appls:
325-
therm_list.append(item.get("id"))
309+
therm_list = []
310+
for appl in self._domain_objects.appliance:
311+
af = appl.actuator_functionalities
312+
if not af or not isinstance(af, OffsetFunctionality):
313+
continue
314+
315+
print(f"HOI6 {af}")
316+
ofs = af.offset_functionality
317+
if isinstance(ofs, OffsetFunctionality):
318+
ofs = [ofs]
319+
320+
if any(o.type == "temperature_offset" for o in ofs):
321+
therm_list.append(appl.id)
326322

327323
return therm_list
328324

plugwise/model.py

Lines changed: 108 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Plugwise models."""
22

3+
from enum import Enum
34
from typing import Any
45

56
from pydantic import BaseModel, ConfigDict, Field
@@ -167,12 +168,31 @@ class ZigBeeNode(WithID):
167168

168169

169170
# Appliance
171+
class ApplianceType(str, Enum):
172+
"""Define application types."""
173+
174+
GATEWAY = "gateway"
175+
OPENTHERMGW = "open_therm_gateway"
176+
THERMOSTAT = "thermostat"
177+
CHP = "central_heating_pump"
178+
CD = "computer_desktop"
179+
HC = "heater_central"
180+
HT = "hometheater"
181+
THERMO_RV = "thermostatic_radiator_valve"
182+
VA = "valve_actuator"
183+
WHV = "water_heater_vessel"
184+
ZONETHERMOMETER = "zone_thermometer"
185+
ZONETHERMOSTAT = "zone_thermostat"
186+
187+
# TODO we still need all the '{}_plug' things here eventually
188+
189+
170190
class Appliance(WithID):
171191
"""Plugwise Appliance."""
172192

173193
name: str
174194
description: str | None = None
175-
type: str
195+
type: ApplianceType
176196
created_date: str
177197
modified_date: str | list[str] | None = None
178198
deleted_date: str | None = None
@@ -184,6 +204,9 @@ class Appliance(WithID):
184204
dict[str, BaseFunctionality | list[BaseFunctionality]] | None
185205
) = None
186206

207+
# Internal processing
208+
fixed_location: str | None = None
209+
187210

188211
# Module
189212
class Module(WithID):
@@ -204,6 +227,54 @@ class Module(WithID):
204227
protocols: dict[str, Any] | None = None # ZigBeeNode, WLAN, LAN
205228

206229

230+
# Gateway
231+
class Gateway(Module):
232+
"""Plugwise Gateway."""
233+
234+
last_reset_date: str | list[str] | None = None
235+
last_boot_date: str | list[str] | None = None
236+
237+
project: dict[str, Any] | None = None
238+
gateway_environment: dict[str, Any] | None = None
239+
features: dict[str, Any] | None = None
240+
241+
242+
# Group
243+
class ApplianceRef(WithID):
244+
"""Group appliance reference."""
245+
246+
pass
247+
248+
249+
class AppliancesContainer(PWBase):
250+
"""Group container containing appliance IDs."""
251+
252+
appliance: list[ApplianceRef] | ApplianceRef
253+
254+
255+
class GroupType(str, Enum):
256+
"""Define group types."""
257+
258+
PUMPING = "pumping"
259+
SWITCHING = "switching"
260+
261+
262+
class Group(WithID):
263+
"""Group of appliances."""
264+
265+
name: str
266+
description: str | None = None
267+
type: GroupType | None = None
268+
269+
created_date: str
270+
modified_date: str | list[str] | None = None
271+
deleted_date: str | None = None
272+
273+
logs: dict[str, BaseLog | list[BaseLog]] | list[BaseLog] | None
274+
appliances: AppliancesContainer | None = None
275+
actuator_functionalities: dict[str, BaseFunctionality] | None = None
276+
277+
207278
# Location
208279
class Location(WithID):
209280
"""Plugwise Location."""
@@ -226,6 +297,8 @@ class DomainObjects(PWBase):
226297
"""Plugwise Domain Objects."""
227298

228299
appliance: list[Appliance] = []
300+
gateway: Gateway | list[Gateway] | None = None
301+
group: Group | list[Group] | None = None
229302
module: list[Module] = []
230303
location: list[Location] = []
231304
notification: Notification | list[Notification] | None = None
@@ -237,3 +310,37 @@ class Root(PWBase):
237310
"""Main XML definition."""
238311

239312
domain_objects: DomainObjects
313+
314+
315+
# Mappings
316+
317+
318+
class SwitchDeviceType(str, Enum):
319+
"""Define switch device types."""
320+
321+
TOGGLE = "toggle"
322+
LOCK = "lock"
323+
324+
325+
class SwitchFunctionType(str, Enum):
326+
"""Define switch function types."""
327+
328+
TOGGLE = "toggle_functionality"
329+
LOCK = "lock"
330+
NONE = None
331+
332+
333+
class SwitchActuatorType(str, Enum):
334+
"""Define switch actuator types."""
335+
336+
DHWCM = "domestic_hot_water_comfort_mode"
337+
CE = "cooling_enabled"
338+
339+
340+
class Switch(BaseModel):
341+
"""Switch/relay definition."""
342+
343+
device: SwitchDeviceType = SwitchDeviceType.TOGGLE
344+
func_type: SwitchFunctionType = SwitchFunctionType.TOGGLE
345+
act_type: SwitchActuatorType = SwitchActuatorType.CE
346+
func: SwitchFunctionType = SwitchFunctionType.NONE

plugwise/smile.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,10 @@
3838
from munch import Munch
3939
import xmltodict
4040

41-
from .model import Appliance, Root
41+
from .model import Appliance, Root, Switch
4242

4343

44-
def model_to_switch_items(model: str, state: str, switch: Munch) -> tuple[str, Munch]:
44+
def model_to_switch_items(model: str, state: str, switch: Switch) -> tuple[str, Switch]:
4545
"""Translate state and switch attributes based on model name.
4646
4747
Helper function for set_switch_state().

0 commit comments

Comments
 (0)