aioWiserHeatAPI 1.5.18__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.18 → aiowiserheatapi-1.6.0}/PKG-INFO +18 -3
  2. {aiowiserheatapi-1.5.18 → aiowiserheatapi-1.6.0}/README.md +16 -1
  3. {aiowiserheatapi-1.5.18 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/__init__.py +1 -1
  4. {aiowiserheatapi-1.5.18 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/binary_sensor.py +40 -0
  5. aiowiserheatapi-1.6.0/aioWiserHeatAPI/button_panel.py +43 -0
  6. {aiowiserheatapi-1.5.18 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/const.py +4 -0
  7. {aiowiserheatapi-1.5.18 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/devices.py +241 -74
  8. {aiowiserheatapi-1.5.18 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/heating_actuator.py +18 -0
  9. {aiowiserheatapi-1.5.18 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/helpers/device.py +10 -2
  10. aiowiserheatapi-1.6.0/aioWiserHeatAPI/helpers/threshold.py +93 -0
  11. aiowiserheatapi-1.6.0/aioWiserHeatAPI/helpers/uiconfiguration.py +73 -0
  12. {aiowiserheatapi-1.5.18 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/hot_water.py +114 -35
  13. {aiowiserheatapi-1.5.18 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/light.py +24 -3
  14. aiowiserheatapi-1.6.0/aioWiserHeatAPI/ptc.py +201 -0
  15. {aiowiserheatapi-1.5.18 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/rest_controller.py +9 -7
  16. {aiowiserheatapi-1.5.18 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI.egg-info/PKG-INFO +18 -3
  17. {aiowiserheatapi-1.5.18 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI.egg-info/SOURCES.txt +4 -0
  18. {aiowiserheatapi-1.5.18 → aiowiserheatapi-1.6.0}/setup.py +1 -1
  19. {aiowiserheatapi-1.5.18 → aiowiserheatapi-1.6.0}/LICENSE +0 -0
  20. {aiowiserheatapi-1.5.18 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/automation.py +0 -0
  21. {aiowiserheatapi-1.5.18 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/boiler_interface.py +0 -0
  22. {aiowiserheatapi-1.5.18 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/cli.py +0 -0
  23. {aiowiserheatapi-1.5.18 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/discovery.py +0 -0
  24. {aiowiserheatapi-1.5.18 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/exceptions.py +0 -0
  25. {aiowiserheatapi-1.5.18 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/heating.py +0 -0
  26. {aiowiserheatapi-1.5.18 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/helpers/__init__.py +0 -0
  27. {aiowiserheatapi-1.5.18 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/helpers/automations.py +0 -0
  28. {aiowiserheatapi-1.5.18 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/helpers/battery.py +0 -0
  29. {aiowiserheatapi-1.5.18 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/helpers/capabilities.py +0 -0
  30. {aiowiserheatapi-1.5.18 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/helpers/cloud.py +0 -0
  31. {aiowiserheatapi-1.5.18 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/helpers/equipment.py +0 -0
  32. {aiowiserheatapi-1.5.18 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/helpers/extra_config.py +0 -0
  33. {aiowiserheatapi-1.5.18 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/helpers/firmware.py +0 -0
  34. {aiowiserheatapi-1.5.18 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/helpers/gps.py +0 -0
  35. {aiowiserheatapi-1.5.18 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/helpers/misc.py +0 -0
  36. {aiowiserheatapi-1.5.18 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/helpers/network.py +0 -0
  37. {aiowiserheatapi-1.5.18 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/helpers/opentherm.py +0 -0
  38. {aiowiserheatapi-1.5.18 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/helpers/signal.py +0 -0
  39. {aiowiserheatapi-1.5.18 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/helpers/special_times.py +0 -0
  40. {aiowiserheatapi-1.5.18 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/helpers/status.py +0 -0
  41. {aiowiserheatapi-1.5.18 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/helpers/temp.py +0 -0
  42. {aiowiserheatapi-1.5.18 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/helpers/version.py +0 -0
  43. {aiowiserheatapi-1.5.18 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/helpers/zigbee.py +0 -0
  44. {aiowiserheatapi-1.5.18 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/moments.py +0 -0
  45. {aiowiserheatapi-1.5.18 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/pte.py +0 -0
  46. {aiowiserheatapi-1.5.18 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/room.py +0 -0
  47. {aiowiserheatapi-1.5.18 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/roomstat.py +0 -0
  48. {aiowiserheatapi-1.5.18 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/schedule.py +0 -0
  49. {aiowiserheatapi-1.5.18 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/shutter.py +0 -0
  50. {aiowiserheatapi-1.5.18 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/smartplug.py +0 -0
  51. {aiowiserheatapi-1.5.18 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/smartvalve.py +0 -0
  52. {aiowiserheatapi-1.5.18 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/smokealarm.py +0 -0
  53. {aiowiserheatapi-1.5.18 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/system.py +0 -0
  54. {aiowiserheatapi-1.5.18 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/ufh.py +0 -0
  55. {aiowiserheatapi-1.5.18 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI/wiserhub.py +0 -0
  56. {aiowiserheatapi-1.5.18 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI.egg-info/dependency_links.txt +0 -0
  57. {aiowiserheatapi-1.5.18 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI.egg-info/entry_points.txt +0 -0
  58. {aiowiserheatapi-1.5.18 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI.egg-info/requires.txt +0 -0
  59. {aiowiserheatapi-1.5.18 → aiowiserheatapi-1.6.0}/aioWiserHeatAPI.egg-info/top_level.txt +0 -0
  60. {aiowiserheatapi-1.5.18 → aiowiserheatapi-1.6.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: aioWiserHeatAPI
3
- Version: 1.5.18
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
@@ -10,7 +10,7 @@ Classifier: Intended Audience :: Developers
10
10
  Classifier: Programming Language :: Python :: 3.11
11
11
  Classifier: License :: OSI Approved :: MIT License
12
12
  Classifier: Operating System :: OS Independent
13
- Requires-Python: >=3.11
13
+ Requires-Python: >=3.12
14
14
  Description-Content-Type: text/markdown
15
15
  License-File: LICENSE
16
16
  Requires-Dist: zeroconf>=0.131.0
@@ -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.18
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,21 @@ 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
+
85
+ ### v1.5.19
86
+ * Fixed error when BoilerInterface does not provide DeviceId field
87
+ * Fixed not supporting non ascii characters in schedule names
88
+ * Added ButtonPanel device support
89
+
75
90
  ### v1.5.18
76
91
 
77
92
  * Better handle client response exeption and fit into retry routing
@@ -1,4 +1,4 @@
1
- # Drayton Wiser Hub API Async v1.5.18
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,21 @@ 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
+
65
+ ### v1.5.19
66
+ * Fixed error when BoilerInterface does not provide DeviceId field
67
+ * Fixed not supporting non ascii characters in schedule names
68
+ * Added ButtonPanel device support
69
+
55
70
  ### v1.5.18
56
71
 
57
72
  * Better handle client response exeption and fit into retry routing
@@ -3,6 +3,6 @@ import logging
3
3
  name = "aioWiserHeatAPI"
4
4
  __all__ = ["wiserAPI", "wiserDiscovery"]
5
5
 
6
- __VERSION__ = "1.5.18"
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"""
@@ -0,0 +1,43 @@
1
+ from typing import Any
2
+
3
+ from .helpers.device import _WiserDevice
4
+
5
+
6
+ class _WiserButtonPanel(_WiserDevice):
7
+ """Class representing a Wiser Button Panel"""
8
+
9
+ @property
10
+ def number_of_gangs(self) -> list[int]:
11
+ """Number of gangs."""
12
+ return self._device_type_data.get("NumberOfGangs")
13
+
14
+ @property
15
+ def events(self) -> list[dict[str, Any]]:
16
+ """Events info."""
17
+ return self._device_type_data.get("Events")
18
+
19
+
20
+ class _WiserButtonPanelCollection:
21
+ """Class holding all Wiser Button Panels"""
22
+
23
+ def __init__(self):
24
+ self._items = []
25
+
26
+ @property
27
+ def all(self) -> list[_WiserButtonPanel]:
28
+ return list(self._items)
29
+
30
+ @property
31
+ def count(self) -> int:
32
+ return len(self.all)
33
+
34
+ def get_by_id(self, id: int) -> _WiserButtonPanel:
35
+ """
36
+ Gets a button panel object from the sensor id
37
+ param id: id of button panel
38
+ return: _WiserButtonPanel object
39
+ """
40
+ try:
41
+ return [buttonpanel for buttonpanel in self.all if buttonpanel.id == id][0]
42
+ except IndexError:
43
+ return None
@@ -101,10 +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/{}"
109
+ WISERBUTTONPANEL = "ButtonPanel/{}"
110
+ WISERTHRESHOLDSENSOR = "ThresholdSensor/{}"
111
+ WISERUICONFIGURATION = "UIConfiguration/{}"
108
112
 
109
113
 
110
114
  # Enums
@@ -3,32 +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
9
  from .binary_sensor import (
10
+ _WiserBinarySensor,
8
11
  _WiserBinarySensorCollection,
9
12
  _WiserWindowDoorSensor,
10
13
  )
11
14
  from .boiler_interface import _WiserBoilerInterface, _WiserBoilerInterfaceCollection
15
+ from .button_panel import _WiserButtonPanel, _WiserButtonPanelCollection
12
16
  from .const import (
13
- TEXT_UNKNOWN,
14
17
  WISERBINARYSENSOR,
15
18
  WISERBOILERINTERFACE,
19
+ WISERBUTTONPANEL,
16
20
  WISERHEATINGACTUATOR,
17
21
  WISERLIGHT,
22
+ WISERPOWERTAGCONTROL,
18
23
  WISERPOWERTAGENERGY,
19
24
  WISERROOMSTAT,
20
25
  WISERSHUTTER,
21
26
  WISERSMARTPLUG,
22
27
  WISERSMARTVALVE,
23
28
  WISERSMOKEALARM,
29
+ WISERTHRESHOLDSENSOR,
24
30
  WISERUFHCONTROLLER,
31
+ WISERUICONFIGURATION,
25
32
  )
26
- from .heating_actuator import (
27
- _WiserHeatingActuator,
28
- _WiserHeatingActuatorCollection,
29
- )
33
+ from .heating_actuator import _WiserHeatingActuator, _WiserHeatingActuatorCollection
30
34
  from .helpers.device import _WiserDevice
35
+ from .helpers.threshold import (
36
+ _WiserThresholdSensor,
37
+ )
38
+ from .helpers.uiconfiguration import _WiserUIConfigSensor
31
39
  from .light import _WiserDimmableLight, _WiserLight, _WiserLightCollection
40
+ from .ptc import _WiserPowerTagControl, _WiserPowerTagControlCollection
32
41
  from .pte import _WiserPowerTagEnergy, _WiserPowerTagEnergyCollection
33
42
  from .rest_controller import _WiserRestController
34
43
  from .roomstat import _WiserRoomStat, _WiserRoomStatCollection
@@ -40,6 +49,116 @@ from .smokealarm import _WiserSmokeAlarm, _WiserSmokeAlarmCollection
40
49
  from .ufh import _WiserUFHController, _WiserUFHControllerCollection
41
50
 
42
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
+
43
162
  class _WiserDeviceTypeEnum(enum.Enum):
44
163
  iTRV = "SmartValve"
45
164
  RoomStat = "RoomStat"
@@ -54,9 +173,10 @@ class _WiserDeviceTypeEnum(enum.Enum):
54
173
  WindowDoorSensor = "BinarySensor"
55
174
  BoilerInterface = "BoilerInterface"
56
175
  CFMT = "HeatingActuator"
176
+ ButtonPanel = "ButtonPanel"
57
177
 
58
178
 
59
- PRODUCT_TYPE_CONFIG = {
179
+ PRODUCT_TYPE_CONFIG_OLD = {
60
180
  "iTRV": {
61
181
  "class": _WiserSmartValve,
62
182
  "collection": _WiserSmartValveCollection,
@@ -93,47 +213,40 @@ PRODUCT_TYPE_CONFIG = {
93
213
  "class": _WiserShutter,
94
214
  "collection": _WiserShutterCollection,
95
215
  "endpoint": WISERSHUTTER,
96
- "device_id_field": "DeviceId",
97
216
  "schedule_type": WiserScheduleTypeEnum.level,
98
217
  },
99
218
  "OnOffLight": {
100
219
  "class": _WiserLight,
101
220
  "collection": _WiserLightCollection,
102
221
  "endpoint": WISERLIGHT,
103
- "device_id_field": "DeviceId",
104
222
  "schedule_type": WiserScheduleTypeEnum.level,
105
223
  },
106
224
  "DimmableLight": {
107
225
  "class": _WiserDimmableLight,
108
226
  "collection": _WiserLightCollection,
109
227
  "endpoint": WISERLIGHT,
110
- "device_id_field": "DeviceId",
111
228
  "schedule_type": WiserScheduleTypeEnum.level,
112
229
  },
113
230
  "PowerTagE": {
114
231
  "class": _WiserPowerTagEnergy,
115
232
  "collection": _WiserPowerTagEnergyCollection,
116
233
  "endpoint": WISERPOWERTAGENERGY,
117
- "device_id_field": "DeviceId",
118
234
  "has_v2_equipment": True,
119
235
  },
120
236
  "SmokeAlarmDevice": {
121
237
  "class": _WiserSmokeAlarm,
122
238
  "collection": _WiserSmokeAlarmCollection,
123
239
  "endpoint": WISERSMOKEALARM,
124
- "device_id_field": "DeviceId",
125
240
  },
126
241
  "WindowDoorSensor": {
127
242
  "class": _WiserWindowDoorSensor,
128
243
  "collection": _WiserBinarySensorCollection,
129
244
  "endpoint": WISERBINARYSENSOR,
130
- "device_id_field": "DeviceId",
131
245
  },
132
246
  "BoilerInterface": {
133
247
  "class": _WiserBoilerInterface,
134
248
  "collection": _WiserBoilerInterfaceCollection,
135
249
  "endpoint": WISERBOILERINTERFACE,
136
- "device_id_field": "DeviceId",
137
250
  },
138
251
  "CFMT": {
139
252
  "class": _WiserHeatingActuator,
@@ -142,6 +255,11 @@ PRODUCT_TYPE_CONFIG = {
142
255
  "heating": True,
143
256
  "has_v2_equipment": True,
144
257
  },
258
+ "ButtonPanel": {
259
+ "class": _WiserButtonPanel,
260
+ "collection": _WiserButtonPanelCollection,
261
+ "endpoint": WISERBUTTONPANEL,
262
+ },
145
263
  }
146
264
 
147
265
 
@@ -172,75 +290,86 @@ class _WiserDeviceCollection:
172
290
  ]
173
291
  return equipment_data[0] if equipment_data else None
174
292
 
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
299
+
175
300
  def _build(self):
176
- """Build collection of devices by type"""
177
-
178
- # Instantiate collection classes
179
- for device_type in _WiserDeviceTypeEnum:
180
- if (
181
- device_type.value not in self._device_collection
182
- and device_type.name in PRODUCT_TYPE_CONFIG
183
- ):
184
- self._device_collection[device_type.value] = PRODUCT_TYPE_CONFIG[
185
- device_type.name
186
- ].get("collection")()
187
-
188
- # Iterate device data for all known device types
189
- if self._device_data:
190
- for device in self._device_data:
191
- device_type = device.get("ProductType", TEXT_UNKNOWN)
192
- if device_type in PRODUCT_TYPE_CONFIG:
193
- device_config = PRODUCT_TYPE_CONFIG[device_type]
194
- device_info = [
195
- device_info
196
- for device_info in self._domain_data.get(
197
- _WiserDeviceTypeEnum[device_type].value
198
- )
199
- if device_info.get(device_config.get("device_id_field", "id"))
200
- == device.get("id")
201
- ]
301
+ """Updated builf collection of devices.
202
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:
203
319
  # Get class to create and collection to append for device
204
- 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)
205
331
 
206
332
  # If heating device add room id
207
- if device_config.get("heating"):
208
- if not device.get("RoomId"):
209
- device_info[0]["RoomId"] = self._get_temp_device_room_id(
210
- 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
211
337
  )
212
338
 
213
339
  # If schedule device add schedule
214
- if device_config.get("schedule_type"):
215
- device_schedule = [
216
- schedule
217
- for schedule in self._schedules.get_by_type(
218
- device_config.get("schedule_type")
219
- )
220
- if schedule.id == device_info[0].get("ScheduleId")
221
- ]
340
+ if device_config.schedule_type:
341
+ device_schedule = self._schedules.get_by_id(
342
+ device_config.schedule_type, device.get("ScheduleId")
343
+ )
222
344
  else:
223
345
  device_schedule = None
224
346
 
225
- # If has equipment entry, add equipment to device info data
226
- if device_config.get("has_v2_equipment"):
227
- device_info[0]["EquipmentData"] = self._get_equipment_data(
228
- device_info[0].get("EquipmentId")
229
- )
230
-
231
347
  # Add device to collection
232
- self._device_collection[
233
- _WiserDeviceTypeEnum[device_type].value
234
- ]._items.append(
348
+ self._device_collection[device_type]._items.append(
235
349
  device_class(
236
350
  self._wiser_rest_controller,
237
- device_config.get("endpoint"),
351
+ device_config.endpoint,
352
+ device_info_data,
238
353
  device,
239
- device_info[0],
240
- device_schedule[0] if device_schedule else None,
354
+ device_schedule,
241
355
  )
242
356
  )
243
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
+
244
373
  def _get_temp_device_room_id(self, domain_data: dict, device_id: int) -> int:
245
374
  rooms = domain_data.get("Room")
246
375
  for room in rooms:
@@ -269,52 +398,90 @@ class _WiserDeviceCollection:
269
398
  @property
270
399
  def heating_actuators(self) -> _WiserHeatingActuatorCollection:
271
400
  """Return all heating actuators"""
272
- return self._device_collection["HeatingActuator"]
401
+ try:
402
+ return self._device_collection["HeatingActuator"]
403
+ except KeyError:
404
+ return None
273
405
 
274
406
  @property
275
407
  def lights(self) -> _WiserLightCollection:
276
408
  """Return all lights"""
277
- 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
278
421
 
279
422
  @property
280
423
  def power_tags(self) -> _WiserPowerTagEnergyCollection:
281
424
  """Return all power tags"""
282
- return self._device_collection["PTE"]
425
+ try:
426
+ return self._device_collection["PTE"]
427
+ except KeyError:
428
+ return None
283
429
 
284
430
  @property
285
431
  def roomstats(self) -> _WiserRoomStatCollection:
286
432
  """Return all roomstats"""
287
- return self._device_collection["RoomStat"]
433
+ try:
434
+ return self._device_collection["RoomStat"]
435
+ except KeyError:
436
+ return None
288
437
 
289
438
  @property
290
439
  def shutters(self) -> _WiserShutterCollection:
291
440
  """Return all shutters"""
292
- return self._device_collection["Shutter"]
441
+ try:
442
+ return self._device_collection["Shutter"]
443
+ except KeyError:
444
+ return None
293
445
 
294
446
  @property
295
447
  def smartplugs(self) -> _WiserSmartPlugCollection:
296
448
  """Return all smart plugs"""
297
- return self._device_collection["SmartPlug"]
449
+ try:
450
+ return self._device_collection["SmartPlug"]
451
+ except KeyError:
452
+ return None
298
453
 
299
454
  @property
300
455
  def smartvalves(self) -> _WiserSmartValveCollection:
301
456
  """Return all smart valves (iTRVs)"""
302
- return self._device_collection["SmartValve"]
457
+ try:
458
+ return self._device_collection["SmartValve"]
459
+ except KeyError:
460
+ return None
303
461
 
304
462
  @property
305
463
  def smokealarms(self) -> _WiserSmokeAlarmCollection:
306
464
  """Return all smoke alarms"""
307
- return self._device_collection["SmokeAlarmDevice"]
465
+ try:
466
+ return self._device_collection["SmokeAlarmDevice"]
467
+ except KeyError:
468
+ return None
308
469
 
309
470
  @property
310
471
  def ufh_controllers(self) -> _WiserUFHControllerCollection:
311
472
  """Return all UFH controllers"""
312
- return self._device_collection["UnderFloorHeating"]
473
+ try:
474
+ return self._device_collection["UnderFloorHeating"]
475
+ except KeyError:
476
+ return None
313
477
 
314
478
  @property
315
479
  def binary_sensor(self):
316
480
  """Return all binary sensors"""
317
- return self._device_collection["BinarySensor"]
481
+ try:
482
+ return self._device_collection["BinarySensor"]
483
+ except KeyError:
484
+ return None
318
485
 
319
486
  def get_by_id(self, device_id: int) -> _WiserDevice:
320
487
  """