aioWiserHeatAPI 1.5.19__tar.gz → 1.6.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. {aiowiserheatapi-1.5.19 → aiowiserheatapi-1.6.0}/PKG-INFO +12 -2
  2. {aiowiserheatapi-1.5.19 → aiowiserheatapi-1.6.0}/README.md +11 -1
  3. {aiowiserheatapi-1.5.19 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/__init__.py +1 -1
  4. {aiowiserheatapi-1.5.19 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/binary_sensor.py +40 -0
  5. {aiowiserheatapi-1.5.19 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/const.py +3 -0
  6. {aiowiserheatapi-1.5.19 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/devices.py +236 -68
  7. {aiowiserheatapi-1.5.19 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/heating_actuator.py +18 -0
  8. {aiowiserheatapi-1.5.19 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/helpers/device.py +10 -2
  9. aiowiserheatapi-1.6.0/aioWiserHeatAPI/helpers/threshold.py +93 -0
  10. aiowiserheatapi-1.6.0/aioWiserHeatAPI/helpers/uiconfiguration.py +73 -0
  11. {aiowiserheatapi-1.5.19 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/hot_water.py +114 -35
  12. {aiowiserheatapi-1.5.19 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/light.py +24 -3
  13. aiowiserheatapi-1.6.0/aioWiserHeatAPI/ptc.py +201 -0
  14. {aiowiserheatapi-1.5.19 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI.egg-info/PKG-INFO +12 -2
  15. {aiowiserheatapi-1.5.19 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI.egg-info/SOURCES.txt +3 -0
  16. {aiowiserheatapi-1.5.19 → aiowiserheatapi-1.6.0}/LICENSE +0 -0
  17. {aiowiserheatapi-1.5.19 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/automation.py +0 -0
  18. {aiowiserheatapi-1.5.19 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/boiler_interface.py +0 -0
  19. {aiowiserheatapi-1.5.19 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/button_panel.py +0 -0
  20. {aiowiserheatapi-1.5.19 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/cli.py +0 -0
  21. {aiowiserheatapi-1.5.19 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/discovery.py +0 -0
  22. {aiowiserheatapi-1.5.19 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/exceptions.py +0 -0
  23. {aiowiserheatapi-1.5.19 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/heating.py +0 -0
  24. {aiowiserheatapi-1.5.19 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/helpers/__init__.py +0 -0
  25. {aiowiserheatapi-1.5.19 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/helpers/automations.py +0 -0
  26. {aiowiserheatapi-1.5.19 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/helpers/battery.py +0 -0
  27. {aiowiserheatapi-1.5.19 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/helpers/capabilities.py +0 -0
  28. {aiowiserheatapi-1.5.19 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/helpers/cloud.py +0 -0
  29. {aiowiserheatapi-1.5.19 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/helpers/equipment.py +0 -0
  30. {aiowiserheatapi-1.5.19 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/helpers/extra_config.py +0 -0
  31. {aiowiserheatapi-1.5.19 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/helpers/firmware.py +0 -0
  32. {aiowiserheatapi-1.5.19 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/helpers/gps.py +0 -0
  33. {aiowiserheatapi-1.5.19 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/helpers/misc.py +0 -0
  34. {aiowiserheatapi-1.5.19 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/helpers/network.py +0 -0
  35. {aiowiserheatapi-1.5.19 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/helpers/opentherm.py +0 -0
  36. {aiowiserheatapi-1.5.19 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/helpers/signal.py +0 -0
  37. {aiowiserheatapi-1.5.19 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/helpers/special_times.py +0 -0
  38. {aiowiserheatapi-1.5.19 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/helpers/status.py +0 -0
  39. {aiowiserheatapi-1.5.19 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/helpers/temp.py +0 -0
  40. {aiowiserheatapi-1.5.19 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/helpers/version.py +0 -0
  41. {aiowiserheatapi-1.5.19 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/helpers/zigbee.py +0 -0
  42. {aiowiserheatapi-1.5.19 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/moments.py +0 -0
  43. {aiowiserheatapi-1.5.19 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/pte.py +0 -0
  44. {aiowiserheatapi-1.5.19 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/rest_controller.py +0 -0
  45. {aiowiserheatapi-1.5.19 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/room.py +0 -0
  46. {aiowiserheatapi-1.5.19 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/roomstat.py +0 -0
  47. {aiowiserheatapi-1.5.19 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/schedule.py +0 -0
  48. {aiowiserheatapi-1.5.19 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/shutter.py +0 -0
  49. {aiowiserheatapi-1.5.19 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/smartplug.py +0 -0
  50. {aiowiserheatapi-1.5.19 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/smartvalve.py +0 -0
  51. {aiowiserheatapi-1.5.19 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/smokealarm.py +0 -0
  52. {aiowiserheatapi-1.5.19 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/system.py +0 -0
  53. {aiowiserheatapi-1.5.19 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/ufh.py +0 -0
  54. {aiowiserheatapi-1.5.19 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/wiserhub.py +0 -0
  55. {aiowiserheatapi-1.5.19 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI.egg-info/dependency_links.txt +0 -0
  56. {aiowiserheatapi-1.5.19 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI.egg-info/entry_points.txt +0 -0
  57. {aiowiserheatapi-1.5.19 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI.egg-info/requires.txt +0 -0
  58. {aiowiserheatapi-1.5.19 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI.egg-info/top_level.txt +0 -0
  59. {aiowiserheatapi-1.5.19 → aiowiserheatapi-1.6.0}/setup.cfg +0 -0
  60. {aiowiserheatapi-1.5.19 → aiowiserheatapi-1.6.0}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: aioWiserHeatAPI
3
- Version: 1.5.19
3
+ Version: 1.6.0
4
4
  Summary: An AsyncIO API for controlling the Drayton Wiser Heating system
5
5
  Home-page: https://github.com/msp1974/aioWiserHeatAPI
6
6
  Author: Mark Parker
@@ -18,7 +18,7 @@ Requires-Dist: aiohttp>=3.9.3
18
18
  Requires-Dist: aiofiles>=23.2.1
19
19
  Requires-Dist: pyyaml>=6.0.1
20
20
 
21
- # Drayton Wiser Hub API Async v1.5.19
21
+ # Drayton Wiser Hub API Async v1.6.0
22
22
 
23
23
  This repository contains a simple API which queries the Drayton Wiser Heating sysystem used in the UK.
24
24
 
@@ -72,6 +72,16 @@ Documentation available in [info.md](https://github.com/msp1974/wiserHeatAPIv2/b
72
72
 
73
73
  ## Changelog
74
74
 
75
+ ### v1.6.0
76
+ * Refactored how devices are enumerated
77
+ * BREAKING CHANGE - threshold sensor is now a device attribute and not its own device
78
+ * Added: support for 2 gang light switches
79
+ * Added: support for threshold and UI configuration as device attributes
80
+ * Added: support for PTC devices
81
+ * Added: UUID now exists for all devices (for future use)
82
+ * Added: New attributes and stored extra config for Hot Water to support HW Climate automation in HA integration
83
+ * Added: Control of interacts with room climate for binary sensors
84
+
75
85
  ### v1.5.19
76
86
  * Fixed error when BoilerInterface does not provide DeviceId field
77
87
  * Fixed not supporting non ascii characters in schedule names
@@ -1,4 +1,4 @@
1
- # Drayton Wiser Hub API Async v1.5.19
1
+ # Drayton Wiser Hub API Async v1.6.0
2
2
 
3
3
  This repository contains a simple API which queries the Drayton Wiser Heating sysystem used in the UK.
4
4
 
@@ -52,6 +52,16 @@ Documentation available in [info.md](https://github.com/msp1974/wiserHeatAPIv2/b
52
52
 
53
53
  ## Changelog
54
54
 
55
+ ### v1.6.0
56
+ * Refactored how devices are enumerated
57
+ * BREAKING CHANGE - threshold sensor is now a device attribute and not its own device
58
+ * Added: support for 2 gang light switches
59
+ * Added: support for threshold and UI configuration as device attributes
60
+ * Added: support for PTC devices
61
+ * Added: UUID now exists for all devices (for future use)
62
+ * Added: New attributes and stored extra config for Hot Water to support HW Climate automation in HA integration
63
+ * Added: Control of interacts with room climate for binary sensors
64
+
55
65
  ### v1.5.19
56
66
  * Fixed error when BoilerInterface does not provide DeviceId field
57
67
  * Fixed not supporting non ascii characters in schedule names
@@ -3,6 +3,6 @@ import logging
3
3
  name = "aioWiserHeatAPI"
4
4
  __all__ = ["wiserAPI", "wiserDiscovery"]
5
5
 
6
- __VERSION__ = "1.5.19"
6
+ __VERSION__ = "1.6.0"
7
7
 
8
8
  _LOGGER = logging.getLogger(__name__)
@@ -2,13 +2,43 @@
2
2
  Handles binary_sensor devices
3
3
  """
4
4
 
5
+ import inspect
6
+
7
+ from aioWiserHeatAPI import _LOGGER
8
+
5
9
  from .helpers.battery import _WiserBattery
6
10
  from .helpers.device import _WiserDevice
11
+ from .helpers.threshold import _WiserThresholdSensor
7
12
 
8
13
 
9
14
  class _WiserBinarySensor(_WiserDevice):
10
15
  """Class representing a Wiser Binary Sensor"""
11
16
 
17
+ def __init__(self, *args):
18
+ """Initialise."""
19
+ super().__init__(*args)
20
+ self._threshold_sensors: list[_WiserThresholdSensor] = []
21
+
22
+ async def _send_command(self, cmd: dict):
23
+ """
24
+ Send control command to the Threshold sensor
25
+ param cmd: json command structure
26
+ return: boolen - true = success, false = failed
27
+ """
28
+ result = await self._wiser_rest_controller._send_command(
29
+ self._endpoint.format(self.device_type_id), cmd
30
+ )
31
+ if result:
32
+ self._device_type_data = result
33
+ if result:
34
+ _LOGGER.debug(
35
+ "Wiser light - {} command successful".format(
36
+ inspect.stack()[1].function
37
+ )
38
+ )
39
+ return True
40
+ return False
41
+
12
42
  @property
13
43
  def active(self) -> bool:
14
44
  """Get if is active"""
@@ -19,6 +49,11 @@ class _WiserBinarySensor(_WiserDevice):
19
49
  """Get the if interacts with room climate"""
20
50
  return self._device_type_data.get("InteractsWithRoomClimate")
21
51
 
52
+ async def set_interacts_with_room_climate(self, enabled: bool):
53
+ if await self._send_command({"InteractsWithRoomClimate": str(enabled).lower()}):
54
+ self._interacts_with_room_climate = enabled
55
+ return True
56
+
22
57
  @property
23
58
  def type(self) -> str:
24
59
  """Get the type of device"""
@@ -34,6 +69,11 @@ class _WiserBinarySensor(_WiserDevice):
34
69
  """Get the battery information for the smokealarm"""
35
70
  return _WiserBattery(self._data)
36
71
 
72
+ @property
73
+ def threshold_sensors(self) -> list[_WiserThresholdSensor]:
74
+ """Get threshold sensors."""
75
+ return self._threshold_sensors
76
+
37
77
 
38
78
  class _WiserWindowDoorSensor(_WiserBinarySensor):
39
79
  """Class representing a Wiser WindowDoor Sensor"""
@@ -101,11 +101,14 @@ WISERHEATINGACTUATOR = "HeatingActuator/{}"
101
101
  WISERUFHCONTROLLER = "UnderFloorHeating/{}"
102
102
  WISERSHUTTER = "Shutter/{}"
103
103
  WISERLIGHT = "Light/{}"
104
+ WISERPOWERTAGCONTROL = "PTC/{}"
104
105
  WISERPOWERTAGENERGY = "PTE/{}"
105
106
  WISERSMOKEALARM = "SmokeAlarmDevice/{}"
106
107
  WISERBINARYSENSOR = "BinarySensor/{}"
107
108
  WISERBOILERINTERFACE = "BoilerInterface/{}"
108
109
  WISERBUTTONPANEL = "ButtonPanel/{}"
110
+ WISERTHRESHOLDSENSOR = "ThresholdSensor/{}"
111
+ WISERUICONFIGURATION = "UIConfiguration/{}"
109
112
 
110
113
 
111
114
  # Enums
@@ -3,28 +3,41 @@ Module to manage all devices
3
3
  """
4
4
 
5
5
  import enum
6
+ from dataclasses import dataclass
7
+ from typing import Any
6
8
 
7
- from .binary_sensor import _WiserBinarySensorCollection, _WiserWindowDoorSensor
9
+ from .binary_sensor import (
10
+ _WiserBinarySensor,
11
+ _WiserBinarySensorCollection,
12
+ _WiserWindowDoorSensor,
13
+ )
8
14
  from .boiler_interface import _WiserBoilerInterface, _WiserBoilerInterfaceCollection
9
15
  from .button_panel import _WiserButtonPanel, _WiserButtonPanelCollection
10
16
  from .const import (
11
- TEXT_UNKNOWN,
12
17
  WISERBINARYSENSOR,
13
18
  WISERBOILERINTERFACE,
14
19
  WISERBUTTONPANEL,
15
20
  WISERHEATINGACTUATOR,
16
21
  WISERLIGHT,
22
+ WISERPOWERTAGCONTROL,
17
23
  WISERPOWERTAGENERGY,
18
24
  WISERROOMSTAT,
19
25
  WISERSHUTTER,
20
26
  WISERSMARTPLUG,
21
27
  WISERSMARTVALVE,
22
28
  WISERSMOKEALARM,
29
+ WISERTHRESHOLDSENSOR,
23
30
  WISERUFHCONTROLLER,
31
+ WISERUICONFIGURATION,
24
32
  )
25
33
  from .heating_actuator import _WiserHeatingActuator, _WiserHeatingActuatorCollection
26
34
  from .helpers.device import _WiserDevice
35
+ from .helpers.threshold import (
36
+ _WiserThresholdSensor,
37
+ )
38
+ from .helpers.uiconfiguration import _WiserUIConfigSensor
27
39
  from .light import _WiserDimmableLight, _WiserLight, _WiserLightCollection
40
+ from .ptc import _WiserPowerTagControl, _WiserPowerTagControlCollection
28
41
  from .pte import _WiserPowerTagEnergy, _WiserPowerTagEnergyCollection
29
42
  from .rest_controller import _WiserRestController
30
43
  from .roomstat import _WiserRoomStat, _WiserRoomStatCollection
@@ -36,6 +49,116 @@ from .smokealarm import _WiserSmokeAlarm, _WiserSmokeAlarmCollection
36
49
  from .ufh import _WiserUFHController, _WiserUFHControllerCollection
37
50
 
38
51
 
52
+ @dataclass(frozen=True, kw_only=True)
53
+ class DeviceConfig:
54
+ device_class: Any
55
+ collection: Any
56
+ endpoint: str = None
57
+ heating: bool = False
58
+ schedule_type: WiserScheduleTypeEnum | None = None
59
+ has_v2_equipment: bool = False
60
+
61
+
62
+ @dataclass(frozen=True, kw_only=True)
63
+ class AncillaryDeviceConfig:
64
+ device_class: Any
65
+ attribute: str
66
+ endpoint: str = None
67
+
68
+
69
+ PRODUCT_TYPE_CONFIG = {
70
+ "SmartValve": DeviceConfig(
71
+ device_class=_WiserSmartValve,
72
+ collection=_WiserSmartValveCollection,
73
+ endpoint=WISERSMARTVALVE,
74
+ heating=True,
75
+ ),
76
+ "RoomStat": DeviceConfig(
77
+ device_class=_WiserRoomStat,
78
+ collection=_WiserRoomStatCollection,
79
+ endpoint=WISERROOMSTAT,
80
+ heating=True,
81
+ ),
82
+ "SmartPlug": DeviceConfig(
83
+ device_class=_WiserSmartPlug,
84
+ collection=_WiserSmartPlugCollection,
85
+ endpoint=WISERSMARTPLUG,
86
+ schedule_type=WiserScheduleTypeEnum.onoff,
87
+ has_v2_equipment=True,
88
+ ),
89
+ "HeatingActuator": DeviceConfig(
90
+ device_class=_WiserHeatingActuator,
91
+ collection=_WiserHeatingActuatorCollection,
92
+ endpoint=WISERHEATINGACTUATOR,
93
+ heating=True,
94
+ has_v2_equipment=True,
95
+ ),
96
+ "UnderFloorHeating": DeviceConfig(
97
+ device_class=_WiserUFHController,
98
+ collection=_WiserUFHControllerCollection,
99
+ endpoint=WISERUFHCONTROLLER,
100
+ heating=True,
101
+ ),
102
+ "Light": DeviceConfig(
103
+ device_class=_WiserLight,
104
+ collection=_WiserLightCollection,
105
+ endpoint=WISERLIGHT,
106
+ schedule_type=WiserScheduleTypeEnum.level,
107
+ ),
108
+ "Shutter": DeviceConfig(
109
+ device_class=_WiserShutter,
110
+ collection=_WiserShutterCollection,
111
+ endpoint=WISERSHUTTER,
112
+ schedule_type=WiserScheduleTypeEnum.level,
113
+ ),
114
+ "PTC": DeviceConfig(
115
+ device_class=_WiserPowerTagControl,
116
+ collection=_WiserPowerTagControlCollection,
117
+ endpoint=WISERPOWERTAGCONTROL,
118
+ has_v2_equipment=True,
119
+ ),
120
+ "PTE": DeviceConfig(
121
+ device_class=_WiserPowerTagEnergy,
122
+ collection=_WiserPowerTagEnergyCollection,
123
+ endpoint=WISERPOWERTAGENERGY,
124
+ has_v2_equipment=True,
125
+ ),
126
+ "SmokeAlarmDevice": DeviceConfig(
127
+ device_class=_WiserSmokeAlarm,
128
+ collection=_WiserSmokeAlarmCollection,
129
+ endpoint=WISERSMOKEALARM,
130
+ ),
131
+ "BinarySensor": DeviceConfig(
132
+ device_class=_WiserBinarySensor,
133
+ collection=_WiserBinarySensorCollection,
134
+ endpoint=WISERBINARYSENSOR,
135
+ ),
136
+ "BoilerInterface": DeviceConfig(
137
+ device_class=_WiserBoilerInterface,
138
+ collection=_WiserBoilerInterfaceCollection,
139
+ endpoint=WISERBOILERINTERFACE,
140
+ ),
141
+ "ButtonPanel": DeviceConfig(
142
+ device_class=_WiserButtonPanel,
143
+ collection=_WiserButtonPanelCollection,
144
+ endpoint=WISERBUTTONPANEL,
145
+ ),
146
+ }
147
+
148
+ ANCILLARY_SENSOR_CONFIG = {
149
+ "ThresholdSensor": AncillaryDeviceConfig(
150
+ device_class=_WiserThresholdSensor,
151
+ attribute="_threshold_sensors",
152
+ endpoint=WISERTHRESHOLDSENSOR,
153
+ ),
154
+ "UIConfiguration": AncillaryDeviceConfig(
155
+ device_class=_WiserUIConfigSensor,
156
+ attribute="_uiconfig_sensors",
157
+ endpoint=WISERUICONFIGURATION,
158
+ ),
159
+ }
160
+
161
+
39
162
  class _WiserDeviceTypeEnum(enum.Enum):
40
163
  iTRV = "SmartValve"
41
164
  RoomStat = "RoomStat"
@@ -53,7 +176,7 @@ class _WiserDeviceTypeEnum(enum.Enum):
53
176
  ButtonPanel = "ButtonPanel"
54
177
 
55
178
 
56
- PRODUCT_TYPE_CONFIG = {
179
+ PRODUCT_TYPE_CONFIG_OLD = {
57
180
  "iTRV": {
58
181
  "class": _WiserSmartValve,
59
182
  "collection": _WiserSmartValveCollection,
@@ -167,79 +290,86 @@ class _WiserDeviceCollection:
167
290
  ]
168
291
  return equipment_data[0] if equipment_data else None
169
292
 
170
- def _build(self):
171
- """Build collection of devices by type"""
172
-
173
- # Instantiate collection classes
174
- for device_type in _WiserDeviceTypeEnum:
175
- if (
176
- device_type.value not in self._device_collection
177
- and device_type.name in PRODUCT_TYPE_CONFIG
178
- ):
179
- self._device_collection[device_type.value] = PRODUCT_TYPE_CONFIG[
180
- device_type.name
181
- ].get("collection")()
182
-
183
- # Iterate device data for all known device types
184
- if self._device_data:
185
- for device in self._device_data:
186
- device_type = device.get("ProductType", TEXT_UNKNOWN)
187
- if device_type in PRODUCT_TYPE_CONFIG:
188
- device_config = PRODUCT_TYPE_CONFIG[device_type]
189
- device_info = [
190
- device_info
191
- for device_info in self._domain_data.get(
192
- _WiserDeviceTypeEnum[device_type].value
193
- )
194
- if device_info.get("DeviceId", device_info.get("id"))
195
- == device.get("id")
196
- ]
293
+ def _get_device_info_by_id(self, device_id: int) -> dict[str, Any] | None:
294
+ """Get device entry by id."""
295
+ for device in self._domain_data.get("Device"):
296
+ if device.get("id") == device_id:
297
+ return device
298
+ return None
197
299
 
198
- # Safety net to prevent error if device info not found
199
- if not device_info:
200
- continue
300
+ def _build(self):
301
+ """Updated builf collection of devices.
201
302
 
303
+ Starts with device type key first and then gets matching device data
304
+ """
305
+ # TODO - limit checks based on DeviceCapabilityMatrix
306
+ for device_type in PRODUCT_TYPE_CONFIG:
307
+ # If not yet in the collection, add empty collection to device collections.
308
+ if device_type not in self._device_collection:
309
+ self._device_collection[device_type] = PRODUCT_TYPE_CONFIG[
310
+ device_type
311
+ ].collection()
312
+
313
+ if device_type_data := self._domain_data.get(device_type):
314
+ # We have this device type in data
315
+ device_config = PRODUCT_TYPE_CONFIG[device_type]
316
+
317
+ # Now build collection of devices
318
+ for device in device_type_data:
202
319
  # Get class to create and collection to append for device
203
- device_class = PRODUCT_TYPE_CONFIG[device_type].get("class")
320
+ device_class = device_config.device_class
321
+
322
+ # TODO: Fix this so not fudged
323
+ if device_type == "Light" and device.get("IsDimmable"):
324
+ device_class = _WiserDimmableLight
325
+
326
+ # Device info id can be DeviceId or id depending on device type
327
+ device_info_id = device.get("DeviceId", device.get("id"))
328
+
329
+ # Get device info data
330
+ device_info_data = self._get_device_info_by_id(device_info_id)
204
331
 
205
332
  # If heating device add room id
206
- if device_config.get("heating"):
207
- if not device.get("RoomId"):
208
- device_info[0]["RoomId"] = self._get_temp_device_room_id(
209
- self._domain_data, device.get("id")
333
+ if device_config.heating:
334
+ if not device.get("RoomId", device_info_data.get("RoomId")):
335
+ device["RoomId"] = self._get_temp_device_room_id(
336
+ self._domain_data, device_info_id
210
337
  )
211
338
 
212
339
  # If schedule device add schedule
213
- if device_config.get("schedule_type"):
214
- device_schedule = [
215
- schedule
216
- for schedule in self._schedules.get_by_type(
217
- device_config.get("schedule_type")
218
- )
219
- if schedule.id == device_info[0].get("ScheduleId")
220
- ]
340
+ if device_config.schedule_type:
341
+ device_schedule = self._schedules.get_by_id(
342
+ device_config.schedule_type, device.get("ScheduleId")
343
+ )
221
344
  else:
222
345
  device_schedule = None
223
346
 
224
- # If has equipment entry, add equipment to device info data
225
- if device_config.get("has_v2_equipment"):
226
- device_info[0]["EquipmentData"] = self._get_equipment_data(
227
- device_info[0].get("EquipmentId")
228
- )
229
-
230
347
  # Add device to collection
231
- self._device_collection[
232
- _WiserDeviceTypeEnum[device_type].value
233
- ]._items.append(
348
+ self._device_collection[device_type]._items.append(
234
349
  device_class(
235
350
  self._wiser_rest_controller,
236
- device_config.get("endpoint"),
351
+ device_config.endpoint,
352
+ device_info_data,
237
353
  device,
238
- device_info[0],
239
- device_schedule[0] if device_schedule else None,
354
+ device_schedule,
240
355
  )
241
356
  )
242
357
 
358
+ # Add ancillary sensors to device
359
+ for anc_device_type, anc_device_type_info in ANCILLARY_SENSOR_CONFIG.items():
360
+ if anc_device_type_data := self._domain_data.get(anc_device_type):
361
+ for anc_device in anc_device_type_data:
362
+ device_id = anc_device.get("DeviceId", anc_device.get("id"))
363
+ if device := self.get_by_id(device_id):
364
+ if hasattr(device, anc_device_type_info.attribute):
365
+ getattr(device, anc_device_type_info.attribute).append(
366
+ anc_device_type_info.device_class(
367
+ self._wiser_rest_controller,
368
+ anc_device_type_info.endpoint,
369
+ anc_device,
370
+ )
371
+ )
372
+
243
373
  def _get_temp_device_room_id(self, domain_data: dict, device_id: int) -> int:
244
374
  rooms = domain_data.get("Room")
245
375
  for room in rooms:
@@ -268,52 +398,90 @@ class _WiserDeviceCollection:
268
398
  @property
269
399
  def heating_actuators(self) -> _WiserHeatingActuatorCollection:
270
400
  """Return all heating actuators"""
271
- return self._device_collection["HeatingActuator"]
401
+ try:
402
+ return self._device_collection["HeatingActuator"]
403
+ except KeyError:
404
+ return None
272
405
 
273
406
  @property
274
407
  def lights(self) -> _WiserLightCollection:
275
408
  """Return all lights"""
276
- return self._device_collection["Light"]
409
+ try:
410
+ return self._device_collection["Light"]
411
+ except KeyError:
412
+ return None
413
+
414
+ @property
415
+ def power_tags_c(self) -> _WiserPowerTagControlCollection:
416
+ """Return all power tag Cs"""
417
+ try:
418
+ return self._device_collection["PTC"]
419
+ except KeyError:
420
+ return None
277
421
 
278
422
  @property
279
423
  def power_tags(self) -> _WiserPowerTagEnergyCollection:
280
424
  """Return all power tags"""
281
- return self._device_collection["PTE"]
425
+ try:
426
+ return self._device_collection["PTE"]
427
+ except KeyError:
428
+ return None
282
429
 
283
430
  @property
284
431
  def roomstats(self) -> _WiserRoomStatCollection:
285
432
  """Return all roomstats"""
286
- return self._device_collection["RoomStat"]
433
+ try:
434
+ return self._device_collection["RoomStat"]
435
+ except KeyError:
436
+ return None
287
437
 
288
438
  @property
289
439
  def shutters(self) -> _WiserShutterCollection:
290
440
  """Return all shutters"""
291
- return self._device_collection["Shutter"]
441
+ try:
442
+ return self._device_collection["Shutter"]
443
+ except KeyError:
444
+ return None
292
445
 
293
446
  @property
294
447
  def smartplugs(self) -> _WiserSmartPlugCollection:
295
448
  """Return all smart plugs"""
296
- return self._device_collection["SmartPlug"]
449
+ try:
450
+ return self._device_collection["SmartPlug"]
451
+ except KeyError:
452
+ return None
297
453
 
298
454
  @property
299
455
  def smartvalves(self) -> _WiserSmartValveCollection:
300
456
  """Return all smart valves (iTRVs)"""
301
- return self._device_collection["SmartValve"]
457
+ try:
458
+ return self._device_collection["SmartValve"]
459
+ except KeyError:
460
+ return None
302
461
 
303
462
  @property
304
463
  def smokealarms(self) -> _WiserSmokeAlarmCollection:
305
464
  """Return all smoke alarms"""
306
- return self._device_collection["SmokeAlarmDevice"]
465
+ try:
466
+ return self._device_collection["SmokeAlarmDevice"]
467
+ except KeyError:
468
+ return None
307
469
 
308
470
  @property
309
471
  def ufh_controllers(self) -> _WiserUFHControllerCollection:
310
472
  """Return all UFH controllers"""
311
- return self._device_collection["UnderFloorHeating"]
473
+ try:
474
+ return self._device_collection["UnderFloorHeating"]
475
+ except KeyError:
476
+ return None
312
477
 
313
478
  @property
314
479
  def binary_sensor(self):
315
480
  """Return all binary sensors"""
316
- return self._device_collection["BinarySensor"]
481
+ try:
482
+ return self._device_collection["BinarySensor"]
483
+ except KeyError:
484
+ return None
317
485
 
318
486
  def get_by_id(self, device_id: int) -> _WiserDevice:
319
487
  """
@@ -2,6 +2,8 @@ from .const import TEMP_OFF, TEXT_UNKNOWN, WISERHEATINGACTUATOR, WiserTempLimits
2
2
  from .helpers.device import _WiserDevice
3
3
  from .helpers.equipment import _WiserEquipment
4
4
  from .helpers.temp import _WiserTemperatureFunctions as tf
5
+ from .helpers.threshold import _WiserThresholdSensor
6
+ from .helpers.uiconfiguration import _WiserUIConfigSensor
5
7
  from .rest_controller import _WiserRestController
6
8
 
7
9
 
@@ -107,6 +109,12 @@ class _WiserTemperatureSensor:
107
109
  class _WiserHeatingActuator(_WiserDevice):
108
110
  """Class representing a Wiser Heating Actuator device"""
109
111
 
112
+ def __init__(self, *args):
113
+ """Initialise."""
114
+ super().__init__(*args)
115
+ self._threshold_sensors: list[_WiserThresholdSensor] = []
116
+ self._uiconfig_sensors: list[_WiserUIConfigSensor] = []
117
+
110
118
  @property
111
119
  def current_target_temperature(self) -> float:
112
120
  """Get the current target temperature setting"""
@@ -161,6 +169,16 @@ class _WiserHeatingActuator(_WiserDevice):
161
169
  """Get output type"""
162
170
  return self._device_type_data.get("OutputType", TEXT_UNKNOWN)
163
171
 
172
+ @property
173
+ def threshold_sensors(self) -> list[_WiserThresholdSensor]:
174
+ """Get threshold sensors."""
175
+ return self._threshold_sensors
176
+
177
+ @property
178
+ def uiconfig_sensors(self) -> list[_WiserUIConfigSensor]:
179
+ """Get ui configuration sensors."""
180
+ return self._uiconfig_sensors
181
+
164
182
 
165
183
  class _WiserHeatingActuatorCollection(object):
166
184
  """Class holding all wiser heating actuators"""
@@ -1,5 +1,7 @@
1
+ import hashlib
1
2
  import inspect
2
3
  from typing import Union
4
+ from uuid import UUID
3
5
 
4
6
  from .. import _LOGGER
5
7
  from ..const import (
@@ -76,7 +78,7 @@ class _WiserDevice(object):
76
78
  @property
77
79
  def device_type_id(self) -> int:
78
80
  """Get the device id for the specific device type"""
79
- return self._data.get("id")
81
+ return self._device_type_data.get("id")
80
82
 
81
83
  @property
82
84
  def identify(self) -> bool:
@@ -188,7 +190,13 @@ class _WiserDevice(object):
188
190
  @property
189
191
  def uuid(self) -> str:
190
192
  """Get UUID zigbee"""
191
- return self._device_type_data.get("UUID", TEXT_UNKNOWN)
193
+ if uuid := self._device_type_data.get("UUID"):
194
+ return uuid
195
+ uname = (
196
+ f"{self.product_type}_{self.serial_number}_{self.id}_{self.device_type_id}"
197
+ )
198
+ hexstr = hashlib.md5(uname.encode("UTF-8")).hexdigest()
199
+ return UUID(hex=hexstr)
192
200
 
193
201
  # END Added by LGO
194
202