Skip to content

Commit 2b5a3f8

Browse files
committed
Gateway progress
1 parent 62ac03a commit 2b5a3f8

File tree

5 files changed

+105
-64
lines changed

5 files changed

+105
-64
lines changed

plugwise/common.py

Lines changed: 40 additions & 24 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,7 +204,7 @@ 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:
207+
for group in self.data.group:
198208
members: list[str] = []
199209
if not group.appliances:
200210
continue
@@ -241,26 +251,32 @@ def _get_module_data(
241251
242252
Collect requested info from MODULES.
243253
"""
244-
module = self.data.get_module(link_id)
254+
module_data = ModuleData()
255+
if "services" not in self.data.appliance or not self.data.appliance.services:
256+
return module_data
245257

246-
for service_type, services in appliance.services.iter_services():
258+
for service_type, services in self.data.appliance.services.iter_services():
247259
if key and key not in service_type:
248260
continue
249-
for service in services:
250-
module = self.data.get_module(service.id)
251-
if not module:
252-
continue
253261

254-
return ModuleData(
255-
contents=True,
256-
firmware_version=None,
257-
hardware_version=None,
258-
reachable=None,
259-
vendor_name=None,
260-
vendor_model=None,
261-
zigbee_mac_address=None,
262-
)
263-
return ModuleData()
262+
# NOW correctly nested
263+
for service in services:
264+
module = self.data.get_module(service.id)
265+
if not module:
266+
continue
267+
268+
module_data = ModuleData(
269+
content=True,
270+
firmware_version=module.firmware_version,
271+
hardware_version=module.hardware_version,
272+
reachable=module.reachable,
273+
vendor_name=module.vendor_name,
274+
vendor_model=module.vendor_model,
275+
zigbee_mac_address=module.zigbee_mac_address,
276+
)
277+
get_zigbee_data(module, module_data, legacy)
278+
279+
return module_data
264280

265281
# TODO legacy
266282
"""

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,
@@ -274,14 +273,14 @@ def _climate_data(self, location_id: str, entity: GwEntityData) -> None:
274273
entity["select_schedule"] = None
275274
self._count += 2
276275
avail_schedules, sel_schedule = self._schedules(loc_id)
277-
if avail_schedules != [NONE]:
276+
if avail_schedules != [None]:
278277
entity["available_schedules"] = avail_schedules
279278
entity["select_schedule"] = sel_schedule
280279

281280
# Set HA climate HVACMode: auto, heat, heat_cool, cool and off
282281
entity["climate_mode"] = "auto"
283282
self._count += 1
284-
if sel_schedule in (NONE, OFF):
283+
if sel_schedule in (None, OFF):
285284
entity["climate_mode"] = "heat"
286285
if self._cooling_present:
287286
entity["climate_mode"] = (
@@ -291,7 +290,7 @@ def _climate_data(self, location_id: str, entity: GwEntityData) -> None:
291290
if self.check_reg_mode("off"):
292291
entity["climate_mode"] = "off"
293292

294-
if NONE not in avail_schedules:
293+
if None not in avail_schedules:
295294
self._get_schedule_states_with_off(
296295
loc_id, avail_schedules, sel_schedule, entity
297296
)
@@ -322,7 +321,7 @@ def _get_schedule_states_with_off(
322321
) -> None:
323322
"""Collect schedules with states for each thermostat.
324323
325-
Also, replace NONE by OFF when none of the schedules are active.
324+
Also, replace None by OFF when none of the schedules are active.
326325
"""
327326
all_off = True
328327
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:
@@ -211,48 +210,51 @@ def _get_locations(self) -> None:
211210
"Error, location Home (building) not found!"
212211
) # pragma: no cover
213212

214-
def _appliance_info_finder(self, appliance: Appliance) -> Appliance:
213+
def _appliance_info_finder(self, appliance: Appliance) -> Appliance | None:
215214
"""Collect info for all appliances found."""
215+
print(f"HOI22 appliance type {appliance.type}!")
216216
match appliance.type:
217-
# No longer needed since we have a Gateway
218-
# case "gateway":
219-
# # Collect gateway entity info
220-
# return self._appl_gateway_info(appl, appliance)
217+
case "gateway":
218+
# Collect gateway entity info
219+
print("HOI22 gateway!")
220+
return self._appl_gateway_info(appliance)
221221
case _ as dev_class if dev_class in THERMOSTAT_CLASSES:
222222
# Collect thermostat entity info
223-
return self._appl_thermostat_info(appl, appliance)
223+
return self._appl_thermostat_info(appliance)
224224
case "heater_central":
225225
# Collect heater_central entity info
226226
# 251016: the added guarding below also solves Core Issue #104433
227227
if not (
228-
appl := self._appl_heater_central_info(appl, appliance, False)
228+
appliance := self._appl_heater_central_info(appliance, False)
229229
): # False means non-legacy entity
230230
return Munch()
231231
self._dhw_allowed_modes = self._get_appl_actuator_modes(
232232
appliance, "domestic_hot_water_mode_control_functionality"
233233
)
234-
return appl
234+
return appliance
235235
case _ as s if s.endswith("_plug"):
236236
# Collect info from plug-types (Plug, Aqara Smart Plug)
237237
locator = MODULE_LOCATOR
238238
module_data = self._get_module_data(appliance, locator)
239239
# A plug without module-data is orphaned/ no present
240240
if not module_data["contents"]:
241-
return Munch()
242-
243-
appl.available = module_data["reachable"]
244-
appl.firmware = module_data["firmware_version"]
245-
appl.hardware = module_data["hardware_version"]
246-
appl.model_id = module_data["vendor_model"]
247-
appl.vendor_name = module_data["vendor_name"]
248-
appl.model = check_model(appl.model_id, appl.vendor_name)
249-
appl.zigbee_mac = module_data["zigbee_mac_address"]
250-
return appl
241+
return None
242+
243+
print(f"HOI24 {module_data}")
244+
appliance.available = module_data["reachable"]
245+
appliance.firmware_version = module_data["firmware_version"]
246+
appliance.hardware_version = module_data["hardware_version"]
247+
appliance.model_id = module_data["vendor_model"]
248+
appliance.vendor_name = module_data["vendor_name"]
249+
appliance.model = check_model(appl.model_id, appl.vendor_name)
250+
appliance.zigbee_mac_address = module_data["zigbee_mac_address"]
251+
return appliance
251252
case _: # pragma: no cover
252-
return Munch()
253+
return None
253254

254255
def _appl_gateway_info(self, appliance: Appliance) -> Appliance:
255256
"""Helper-function for _appliance_info_finder()."""
257+
print(f"HOI19 {appliance.id}")
256258
self._gateway_id = appliance.id
257259

258260
# Adam: collect the ZigBee MAC address of the Smile
@@ -272,7 +274,7 @@ def _appl_gateway_info(self, appliance: Appliance) -> Appliance:
272274
# Limit the possible gateway-modes
273275
self._gw_allowed_modes = ["away", "full", "vacation"]
274276

275-
return appl
277+
return appliance
276278

277279
def _get_appl_actuator_modes(
278280
self, appliance: etree.Element, actuator_type: str
@@ -624,7 +626,7 @@ def _get_gateway_outdoor_temp(self, entity_id: str, data: GwEntityData) -> None:
624626
if self._is_thermostat and entity_id == self._gateway_id:
625627
locator = "./logs/point_log[type='outdoor_temperature']/period/measurement"
626628
if (found := self._home_location.find(locator)) is not None:
627-
value = format_measure(found.text, NONE)
629+
value = format_measure(found.text, None)
628630
data.update({"sensors": {"outdoor_temperature": value}})
629631
self._count += 1
630632

@@ -918,7 +920,7 @@ def _rule_ids_by_name(self, name: str, loc_id: str) -> dict[str, dict[str, str]]
918920
}
919921
else:
920922
schedule_ids[rule.get("id")] = {
921-
"location": NONE,
923+
"location": None,
922924
"name": name,
923925
"active": active,
924926
}
@@ -945,7 +947,7 @@ def _rule_ids_by_tag(self, tag: str, loc_id: str) -> dict[str, dict[str, str]]:
945947
}
946948
else:
947949
schedule_ids[rule.get("id")] = {
948-
"location": NONE,
950+
"location": None,
949951
"name": name,
950952
"active": active,
951953
}
@@ -958,9 +960,9 @@ def _schedules(self, location: str) -> tuple[list[str], str]:
958960
Obtain the available schedules/schedules. Adam: a schedule can be connected to more than one location.
959961
NEW: when a location_id is present then the schedule is active. Valid for both Adam and non-legacy Anna.
960962
"""
961-
available: list[str] = [NONE]
963+
available: list[str] = [None]
962964
rule_ids: dict[str, dict[str, str]] = {}
963-
selected = NONE
965+
selected = None
964966
tag = "zone_preset_based_on_time_and_presence_with_override"
965967
if not (rule_ids := self._rule_ids_by_tag(tag, location)):
966968
return available, selected
@@ -980,9 +982,9 @@ def _schedules(self, location: str) -> tuple[list[str], str]:
980982
schedules.append(name)
981983

982984
if schedules:
983-
available.remove(NONE)
985+
available.remove(None)
984986
available.append(OFF)
985-
if selected == NONE:
987+
if selected == None:
986988
selected = OFF
987989

988990
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)