Skip to content

Commit baf3419

Browse files
CoMPaTechbouwew
authored andcommitted
Gateway progress
1 parent 826c387 commit baf3419

5 files changed

Lines changed: 106 additions & 69 deletions

File tree

plugwise/common.py

Lines changed: 41 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -23,24 +23,34 @@
2323
from defusedxml import ElementTree as etree
2424
from munch import Munch
2525

26-
from .model import ModuleData
26+
from .model import Module, ModuleData
2727

2828

29-
def get_zigbee_data(
30-
module: etree.Element, module_data: ModuleData, legacy: bool
31-
) -> None:
29+
def get_zigbee_data(module: Module, module_data: ModuleData, legacy: bool) -> None:
3230
"""Helper-function for _get_module_data()."""
31+
if not module.protocols:
32+
return
33+
3334
if legacy:
3435
# Stretches
3536
if (router := module.find("./protocols/network_router")) is not None:
3637
module_data["zigbee_mac_address"] = router.find("mac_address").text
3738
# Also look for the Circle+/Stealth M+
3839
if (coord := module.find("./protocols/network_coordinator")) is not None:
3940
module_data["zigbee_mac_address"] = coord.find("mac_address").text
41+
if legacy:
42+
if module.protocols.network_router:
43+
module_data.zigbee_mac_address = module.protocols.network_router.mac_address
44+
if module.protocols.network_coordinator:
45+
module_data.zigbee_mac_address = (
46+
module.protocols.network_coordinator.mac_address
47+
)
48+
return
4049
# Adam
41-
elif (zb_node := module.find("./protocols/zig_bee_node")) is not None:
42-
module_data["zigbee_mac_address"] = zb_node.find("mac_address").text
43-
module_data["reachable"] = zb_node.find("reachable").text == "true"
50+
if module.protocols.zig_bee_node:
51+
zb = module.protocols.zig_bee_node
52+
module_data.zigbee_mac_address = zb.mac_address
53+
module_data.reachable = zb.reachable
4454

4555

4656
class SmileCommon:
@@ -194,12 +204,8 @@ def _get_groups(self) -> None:
194204
if self.smile.type == "power" or self.check_name(ANNA):
195205
return
196206

197-
for group in self._domain_objects.group:
198-
group_id = group.get("id")
199-
if group_id is None:
200-
continue # pragma: no cover
201-
202-
if not (members := self._collect_members(group)):
207+
for group in self.data.group:
208+
if not group.appliances:
203209
continue
204210

205211
group_name = group.find("name").text
@@ -251,26 +257,32 @@ def _get_module_data(
251257
252258
Collect requested info from MODULES.
253259
"""
254-
module = self.data.get_module(link_id)
260+
module_data = ModuleData()
261+
if "services" not in self.data.appliance or not self.data.appliance.services:
262+
return module_data
255263

256-
for service_type, services in appliance.services.iter_services():
264+
for service_type, services in self.data.appliance.services.iter_services():
257265
if key and key not in service_type:
258266
continue
259-
for service in services:
260-
module = self.data.get_module(service.id)
261-
if not module:
262-
continue
263267

264-
return ModuleData(
265-
contents=True,
266-
firmware_version=None,
267-
hardware_version=None,
268-
reachable=None,
269-
vendor_name=None,
270-
vendor_model=None,
271-
zigbee_mac_address=None,
272-
)
273-
return ModuleData()
268+
# NOW correctly nested
269+
for service in services:
270+
module = self.data.get_module(service.id)
271+
if not module:
272+
continue
273+
274+
module_data = ModuleData(
275+
content=True,
276+
firmware_version=module.firmware_version,
277+
hardware_version=module.hardware_version,
278+
reachable=module.reachable,
279+
vendor_name=module.vendor_name,
280+
vendor_model=module.vendor_model,
281+
zigbee_mac_address=module.zigbee_mac_address,
282+
)
283+
get_zigbee_data(module, module_data, legacy)
284+
285+
return module_data
274286

275287
# TODO legacy
276288
"""

plugwise/data.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
ANNA,
1313
MAX_SETPOINT,
1414
MIN_SETPOINT,
15-
NONE,
1615
OFF,
1716
ActuatorData,
1817
GwEntityData,
@@ -272,14 +271,14 @@ def _climate_data(self, location_id: str, entity: GwEntityData) -> None:
272271
entity["select_schedule"] = None
273272
self._count += 2
274273
avail_schedules, sel_schedule = self._schedules(loc_id)
275-
if avail_schedules != [NONE]:
274+
if avail_schedules != [None]:
276275
entity["available_schedules"] = avail_schedules
277276
entity["select_schedule"] = sel_schedule
278277

279278
# Set HA climate HVACMode: auto, heat, heat_cool, cool and off
280279
entity["climate_mode"] = "auto"
281280
self._count += 1
282-
if sel_schedule in (NONE, OFF):
281+
if sel_schedule in (None, OFF):
283282
entity["climate_mode"] = "heat"
284283
if self._cooling_present:
285284
entity["climate_mode"] = (
@@ -289,7 +288,7 @@ def _climate_data(self, location_id: str, entity: GwEntityData) -> None:
289288
if self.check_reg_mode("off"):
290289
entity["climate_mode"] = "off"
291290

292-
if NONE not in avail_schedules:
291+
if None not in avail_schedules:
293292
self._get_schedule_states_with_off(
294293
loc_id, avail_schedules, sel_schedule, entity
295294
)
@@ -320,7 +319,7 @@ def _get_schedule_states_with_off(
320319
) -> None:
321320
"""Collect schedules with states for each thermostat.
322321
323-
Also, replace NONE by OFF when none of the schedules are active.
322+
Also, replace None by OFF when none of the schedules are active.
324323
"""
325324
all_off = True
326325
self._schedule_old_states[location] = {}

plugwise/helper.py

Lines changed: 35 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
LOCATIONS,
2727
LOGGER,
2828
MODULE_LOCATOR,
29-
NONE,
3029
OFF,
3130
P1_MEASUREMENTS,
3231
TEMP_CELSIUS,
@@ -80,7 +79,7 @@ def __init__(self) -> None:
8079
self._is_thermostat: bool
8180
self._loc_data: dict[str, ThermoLoc]
8281
self._schedule_old_states: dict[str, dict[str, str]]
83-
self._gateway_id: str = NONE
82+
self._gateway_id: str = None
8483
self._zones: dict[str, GwEntityData]
8584
self.gw_entities: dict[str, GwEntityData]
8685
self.smile: Munch = Munch()
@@ -131,10 +130,9 @@ def _get_appliances(self) -> None:
131130
):
132131
appliance.type = f"{appliance.type}_plug"
133132

134-
# TODO: recreate functionality
135-
# # Collect appliance info, skip orphaned/removed devices
136-
# if not (appl := self._appliance_info_finder(appl, appliance)):
137-
# continue
133+
# Collect appliance info, skip orphaned/removed devices
134+
if not self._appliance_info_finder(appliance):
135+
continue
138136

139137
# A smartmeter is not present as an appliance, add it specifically
140138
if self.smile.type == "power" or self.smile.anna_p1:
@@ -182,6 +180,7 @@ def _get_locations(self) -> None:
182180
"""Collect all locations."""
183181
counter = 0
184182
loc = Munch()
183+
print(f"HOI15 {self.data}")
185184
print(f"HOI15 {self.data.location}")
186185
locations = self.data.location
187186
if not locations:
@@ -206,48 +205,51 @@ def _get_locations(self) -> None:
206205
"Error, location Home (building) not found!"
207206
) # pragma: no cover
208207

209-
def _appliance_info_finder(self, appliance: Appliance) -> Appliance:
208+
def _appliance_info_finder(self, appliance: Appliance) -> Appliance | None:
210209
"""Collect info for all appliances found."""
210+
print(f"HOI22 appliance type {appliance.type}!")
211211
match appliance.type:
212-
# No longer needed since we have a Gateway
213-
# case "gateway":
214-
# # Collect gateway entity info
215-
# return self._appl_gateway_info(appl, appliance)
212+
case "gateway":
213+
# Collect gateway entity info
214+
print("HOI22 gateway!")
215+
return self._appl_gateway_info(appliance)
216216
case _ as dev_class if dev_class in THERMOSTAT_CLASSES:
217217
# Collect thermostat entity info
218-
return self._appl_thermostat_info(appl, appliance)
218+
return self._appl_thermostat_info(appliance)
219219
case "heater_central":
220220
# Collect heater_central entity info
221221
# 251016: the added guarding below also solves Core Issue #104433
222222
if not (
223-
appl := self._appl_heater_central_info(appl, appliance, False)
223+
appliance := self._appl_heater_central_info(appliance, False)
224224
): # False means non-legacy entity
225225
return Munch()
226226
self._dhw_allowed_modes = self._get_appl_actuator_modes(
227227
appliance, "domestic_hot_water_mode_control_functionality"
228228
)
229-
return appl
229+
return appliance
230230
case _ as s if s.endswith("_plug"):
231231
# Collect info from plug-types (Plug, Aqara Smart Plug)
232232
locator = MODULE_LOCATOR
233233
module_data = self._get_module_data(appliance, locator)
234234
# A plug without module-data is orphaned/ no present
235235
if not module_data["contents"]:
236-
return Munch()
237-
238-
appl.available = module_data["reachable"]
239-
appl.firmware = module_data["firmware_version"]
240-
appl.hardware = module_data["hardware_version"]
241-
appl.model_id = module_data["vendor_model"]
242-
appl.vendor_name = module_data["vendor_name"]
243-
appl.model = check_model(appl.model_id, appl.vendor_name)
244-
appl.zigbee_mac = module_data["zigbee_mac_address"]
245-
return appl
236+
return None
237+
238+
print(f"HOI24 {module_data}")
239+
appliance.available = module_data["reachable"]
240+
appliance.firmware_version = module_data["firmware_version"]
241+
appliance.hardware_version = module_data["hardware_version"]
242+
appliance.model_id = module_data["vendor_model"]
243+
appliance.vendor_name = module_data["vendor_name"]
244+
appliance.model = check_model(appl.model_id, appl.vendor_name)
245+
appliance.zigbee_mac_address = module_data["zigbee_mac_address"]
246+
return appliance
246247
case _: # pragma: no cover
247-
return Munch()
248+
return None
248249

249250
def _appl_gateway_info(self, appliance: Appliance) -> Appliance:
250251
"""Helper-function for _appliance_info_finder()."""
252+
print(f"HOI19 {appliance.id}")
251253
self._gateway_id = appliance.id
252254

253255
# Adam: collect the ZigBee MAC address of the Smile
@@ -267,7 +269,7 @@ def _appl_gateway_info(self, appliance: Appliance) -> Appliance:
267269
# Limit the possible gateway-modes
268270
self._gw_allowed_modes = ["away", "full", "vacation"]
269271

270-
return appl
272+
return appliance
271273

272274
def _get_appl_actuator_modes(
273275
self, appliance: etree.Element, actuator_type: str
@@ -601,7 +603,7 @@ def _get_gateway_outdoor_temp(self, entity_id: str, data: GwEntityData) -> None:
601603
if self._is_thermostat and entity_id == self._gateway_id:
602604
locator = "./logs/point_log[type='outdoor_temperature']/period/measurement"
603605
if (found := self._home_location.find(locator)) is not None:
604-
value = format_measure(found.text, NONE)
606+
value = format_measure(found.text, None)
605607
data.update({"sensors": {"outdoor_temperature": value}})
606608
self._count += 1
607609

@@ -905,7 +907,7 @@ def _rule_ids_by_name(self, name: str, loc_id: str) -> dict[str, dict[str, str]]
905907
}
906908
else:
907909
schedule_ids[rule.get("id")] = {
908-
"location": NONE,
910+
"location": None,
909911
"name": name,
910912
"active": active,
911913
}
@@ -932,7 +934,7 @@ def _rule_ids_by_tag(self, tag: str, loc_id: str) -> dict[str, dict[str, str]]:
932934
}
933935
else:
934936
schedule_ids[rule.get("id")] = {
935-
"location": NONE,
937+
"location": None,
936938
"name": name,
937939
"active": active,
938940
}
@@ -945,9 +947,9 @@ def _schedules(self, location: str) -> tuple[list[str], str]:
945947
Obtain the available schedules/schedules. Adam: a schedule can be connected to more than one location.
946948
NEW: when a location_id is present then the schedule is active. Valid for both Adam and non-legacy Anna.
947949
"""
948-
available: list[str] = [NONE]
950+
available: list[str] = [None]
949951
rule_ids: dict[str, dict[str, str]] = {}
950-
selected = NONE
952+
selected = None
951953
tag = "zone_preset_based_on_time_and_presence_with_override"
952954
if not (rule_ids := self._rule_ids_by_tag(tag, location)):
953955
return available, selected
@@ -967,9 +969,9 @@ def _schedules(self, location: str) -> tuple[list[str], str]:
967969
schedules.append(name)
968970

969971
if schedules:
970-
available.remove(NONE)
972+
available.remove(None)
971973
available.append(OFF)
972-
if selected == NONE:
974+
if selected == None:
973975
selected = OFF
974976

975977
return available, selected

plugwise/model.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ class Neighbor(PWBase):
157157
class ZigBeeNode(WithID):
158158
"""ZigBee node definition."""
159159

160+
reachable: bool | None = None
160161
mac_address: str
161162
type: str
162163
reachable: bool
@@ -168,6 +169,26 @@ class ZigBeeNode(WithID):
168169
neighbor_table_support: bool | None = None
169170

170171

172+
class NetworkRouter(BaseModel):
173+
"""Network router."""
174+
175+
mac_address: str | None = None
176+
177+
178+
class NetworkCoordinator(BaseModel):
179+
"""Network coordinator."""
180+
181+
mac_address: str | None = None
182+
183+
184+
class Protocols(BaseModel):
185+
"""Protocol definition."""
186+
187+
network_router: NetworkRouter | None = None
188+
network_coordinator: NetworkCoordinator | None = None
189+
zig_bee_node: ZigBeeNode | None = None
190+
191+
171192
# Appliance
172193
class ApplianceType(str, Enum):
173194
"""Define application types."""
@@ -182,6 +203,7 @@ class ApplianceType(str, Enum):
182203
STRETCH = "stretch"
183204
THERMO_RV = "thermostatic_radiator_valve"
184205
VA = "valve_actuator"
206+
VA_plug = "valve_actuator_plug"
185207
WHV = "water_heater_vessel"
186208
ZONETHERMOMETER = "zone_thermometer"
187209
ZONETHERMOSTAT = "zone_thermostat"
@@ -192,6 +214,7 @@ class ApplianceType(str, Enum):
192214
class Appliance(WithID):
193215
"""Plugwise Appliance."""
194216

217+
available: bool = False
195218
name: str
196219
description: str | None = None
197220
type: ApplianceType
@@ -226,7 +249,8 @@ class Module(WithID):
226249
# services: dict[str, ServiceBase | list[ServiceBase]] | list[dict[str, Any]] | None = None
227250
services: dict[str, Any] | list[Any] | None = None
228251

229-
protocols: dict[str, Any] | None = None # ZigBeeNode, WLAN, LAN
252+
# protocols: dict[str, Any] | None = None # ZigBeeNode, WLAN, LAN
253+
protocols: dict[str, Protocols] | list[Protocols] | None = None
230254

231255

232256
# Gateway

plugwise/smile.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ def cooling_present(self) -> bool:
101101

102102
async def full_xml_update(self) -> None:
103103
"""Perform a first fetch of the Plugwise server XML data."""
104-
self.data = await self._request(DOMAIN_OBJECTS, new=True)
104+
await self._request(DOMAIN_OBJECTS, new=True)
105105
print(f"HOI3a {self.data}")
106106
if "notification" in self.data and self.data.notification is not None:
107107
print(f"HOI3b {self.data.notification}")

0 commit comments

Comments
 (0)