aioWiserHeatAPI 1.7.7__tar.gz → 1.7.8__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.
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.8}/PKG-INFO +13 -2
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.8}/README.md +12 -1
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.8}/aioWiserHeatAPI/__init__.py +1 -1
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.8}/aioWiserHeatAPI/automation.py +47 -2
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.8}/aioWiserHeatAPI/binary_sensor.py +47 -2
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.8}/aioWiserHeatAPI/const.py +11 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.8}/aioWiserHeatAPI/devices.py +13 -1
- aiowiserheatapi-1.7.8/aioWiserHeatAPI/equipments.py +159 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.8}/aioWiserHeatAPI/evse.py +10 -7
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.8}/aioWiserHeatAPI/helpers/equipment.py +39 -13
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.8}/aioWiserHeatAPI/rest_controller.py +59 -12
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.8}/aioWiserHeatAPI/smokealarm.py +14 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.8}/aioWiserHeatAPI/wiserhub.py +13 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.8}/aioWiserHeatAPI.egg-info/PKG-INFO +13 -2
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.8}/aioWiserHeatAPI.egg-info/SOURCES.txt +7 -0
- aiowiserheatapi-1.7.8/tests/test_device_notifications.py +191 -0
- aiowiserheatapi-1.7.8/tests/test_equipment.py +148 -0
- aiowiserheatapi-1.7.8/tests/test_equipments.py +295 -0
- aiowiserheatapi-1.7.8/tests/test_evse.py +41 -0
- aiowiserheatapi-1.7.8/tests/test_rest_controller.py +94 -0
- aiowiserheatapi-1.7.8/tests/test_rest_payloads.py +73 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.8}/LICENSE +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.8}/aioWiserHeatAPI/boiler_interface.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.8}/aioWiserHeatAPI/button_panel.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.8}/aioWiserHeatAPI/cli.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.8}/aioWiserHeatAPI/discovery.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.8}/aioWiserHeatAPI/exceptions.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.8}/aioWiserHeatAPI/heating.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.8}/aioWiserHeatAPI/heating_actuator.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.8}/aioWiserHeatAPI/helpers/__init__.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.8}/aioWiserHeatAPI/helpers/automations.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.8}/aioWiserHeatAPI/helpers/battery.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.8}/aioWiserHeatAPI/helpers/capabilities.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.8}/aioWiserHeatAPI/helpers/cloud.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.8}/aioWiserHeatAPI/helpers/device.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.8}/aioWiserHeatAPI/helpers/extra_config.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.8}/aioWiserHeatAPI/helpers/firmware.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.8}/aioWiserHeatAPI/helpers/gps.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.8}/aioWiserHeatAPI/helpers/misc.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.8}/aioWiserHeatAPI/helpers/network.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.8}/aioWiserHeatAPI/helpers/opentherm.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.8}/aioWiserHeatAPI/helpers/signal.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.8}/aioWiserHeatAPI/helpers/special_times.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.8}/aioWiserHeatAPI/helpers/status.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.8}/aioWiserHeatAPI/helpers/temp.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.8}/aioWiserHeatAPI/helpers/threshold.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.8}/aioWiserHeatAPI/helpers/uiconfiguration.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.8}/aioWiserHeatAPI/helpers/version.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.8}/aioWiserHeatAPI/helpers/zigbee.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.8}/aioWiserHeatAPI/hot_water.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.8}/aioWiserHeatAPI/light.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.8}/aioWiserHeatAPI/moments.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.8}/aioWiserHeatAPI/ptc.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.8}/aioWiserHeatAPI/pte.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.8}/aioWiserHeatAPI/refactor.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.8}/aioWiserHeatAPI/room.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.8}/aioWiserHeatAPI/roomstat.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.8}/aioWiserHeatAPI/schedule.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.8}/aioWiserHeatAPI/shutter.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.8}/aioWiserHeatAPI/smartplug.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.8}/aioWiserHeatAPI/smartvalve.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.8}/aioWiserHeatAPI/system.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.8}/aioWiserHeatAPI/temp_humidity.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.8}/aioWiserHeatAPI/ufh.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.8}/aioWiserHeatAPI.egg-info/dependency_links.txt +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.8}/aioWiserHeatAPI.egg-info/entry_points.txt +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.8}/aioWiserHeatAPI.egg-info/requires.txt +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.8}/aioWiserHeatAPI.egg-info/top_level.txt +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.8}/setup.cfg +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.8}/setup.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.8}/tests/test_schedule.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aioWiserHeatAPI
|
|
3
|
-
Version: 1.7.
|
|
3
|
+
Version: 1.7.8
|
|
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
|
|
@@ -28,7 +28,7 @@ Dynamic: requires-dist
|
|
|
28
28
|
Dynamic: requires-python
|
|
29
29
|
Dynamic: summary
|
|
30
30
|
|
|
31
|
-
# Drayton Wiser Hub API Async v1.7.
|
|
31
|
+
# Drayton Wiser Hub API Async v1.7.8
|
|
32
32
|
|
|
33
33
|
This repository contains a simple API which queries the Drayton Wiser Heating sysystem used in the UK.
|
|
34
34
|
|
|
@@ -86,6 +86,17 @@ Documentation available in [info.md](https://github.com/msp1974/wiserHeatAPIv2/b
|
|
|
86
86
|
|
|
87
87
|
## Changelog
|
|
88
88
|
|
|
89
|
+
### v1.7.8
|
|
90
|
+
|
|
91
|
+
- Added: Standalone equipment collection through `devices.equipments`, including equipment lookup, available control states, overview schedule IDs, and equipment/EVSE rated power values
|
|
92
|
+
- Added: Create, update, enable, disable, and delete native equipment under-power and over-power notifications
|
|
93
|
+
- Added: Enable or disable native smoke alarm notifications and configure window/door sensor notification modes
|
|
94
|
+
- Added: Hub automation collection through `automations`, with cached automation details (triggers, actions, and time constraints), enable/disable controls, notification controls, and deletion
|
|
95
|
+
- Fix: Retry requests over HTTPS when the hub redirects HTTP traffic or explicitly reports that HTTPS is required, while preserving the configured host and port mapping
|
|
96
|
+
- Fix: Send JSON command payloads containing `false`, `0`, or empty values instead of omitting them
|
|
97
|
+
- Fix: Accept successful EVSE commands with empty responses and preserve existing equipment telemetry when responses contain only changed fields
|
|
98
|
+
- Fix: Correct over-power notification data and preserve zero values, disabled states, and missing settings in partial notification records
|
|
99
|
+
|
|
89
100
|
### v1.7.7
|
|
90
101
|
|
|
91
102
|
- Fix: Avoid eager `aiofiles.os` imports during startup to prevent mixed-version import failures in Home Assistant (issue #763)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Drayton Wiser Hub API Async v1.7.
|
|
1
|
+
# Drayton Wiser Hub API Async v1.7.8
|
|
2
2
|
|
|
3
3
|
This repository contains a simple API which queries the Drayton Wiser Heating sysystem used in the UK.
|
|
4
4
|
|
|
@@ -56,6 +56,17 @@ Documentation available in [info.md](https://github.com/msp1974/wiserHeatAPIv2/b
|
|
|
56
56
|
|
|
57
57
|
## Changelog
|
|
58
58
|
|
|
59
|
+
### v1.7.8
|
|
60
|
+
|
|
61
|
+
- Added: Standalone equipment collection through `devices.equipments`, including equipment lookup, available control states, overview schedule IDs, and equipment/EVSE rated power values
|
|
62
|
+
- Added: Create, update, enable, disable, and delete native equipment under-power and over-power notifications
|
|
63
|
+
- Added: Enable or disable native smoke alarm notifications and configure window/door sensor notification modes
|
|
64
|
+
- Added: Hub automation collection through `automations`, with cached automation details (triggers, actions, and time constraints), enable/disable controls, notification controls, and deletion
|
|
65
|
+
- Fix: Retry requests over HTTPS when the hub redirects HTTP traffic or explicitly reports that HTTPS is required, while preserving the configured host and port mapping
|
|
66
|
+
- Fix: Send JSON command payloads containing `false`, `0`, or empty values instead of omitting them
|
|
67
|
+
- Fix: Accept successful EVSE commands with empty responses and preserve existing equipment telemetry when responses contain only changed fields
|
|
68
|
+
- Fix: Correct over-power notification data and preserve zero values, disabled states, and missing settings in partial notification records
|
|
69
|
+
|
|
59
70
|
### v1.7.7
|
|
60
71
|
|
|
61
72
|
- Fix: Avoid eager `aiofiles.os` imports during startup to prevent mixed-version import failures in Home Assistant (issue #763)
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import inspect
|
|
2
2
|
|
|
3
3
|
from . import _LOGGER
|
|
4
|
-
from .const import
|
|
5
|
-
|
|
4
|
+
from .const import (
|
|
5
|
+
TEXT_UNKNOWN,
|
|
6
|
+
WISERAUTOMATION,
|
|
7
|
+
WISERHUBAUTOMATION,
|
|
8
|
+
WISERSYSTEM,
|
|
9
|
+
)
|
|
10
|
+
from .rest_controller import WiserRestActionEnum, _WiserRestController
|
|
6
11
|
|
|
7
12
|
|
|
8
13
|
class _WiserAutomation:
|
|
@@ -11,6 +16,8 @@ class _WiserAutomation:
|
|
|
11
16
|
):
|
|
12
17
|
self._wiser_rest_controller = wiser_rest_controller
|
|
13
18
|
self._automation_data = automation_data
|
|
19
|
+
self._details = {}
|
|
20
|
+
self._details_loaded = False
|
|
14
21
|
|
|
15
22
|
async def _send_command(self, cmd: dict) -> bool:
|
|
16
23
|
"""
|
|
@@ -46,10 +53,48 @@ class _WiserAutomation:
|
|
|
46
53
|
def notification_enabled(self) -> bool:
|
|
47
54
|
return self._automation_data.get("EnableNotification", False)
|
|
48
55
|
|
|
56
|
+
async def get_details(self, refresh: bool = False) -> dict:
|
|
57
|
+
"""Return the automation triggers, actions and time constraints."""
|
|
58
|
+
if refresh or not self._details_loaded:
|
|
59
|
+
details = await self._wiser_rest_controller.get_hub_data(
|
|
60
|
+
f"{WISERHUBAUTOMATION}{self.id}",
|
|
61
|
+
False,
|
|
62
|
+
)
|
|
63
|
+
self._details = details if isinstance(details, dict) else {}
|
|
64
|
+
self._details_loaded = True
|
|
65
|
+
return self._details.copy()
|
|
66
|
+
|
|
49
67
|
async def enable_notification(self):
|
|
50
68
|
"""Activate automation"""
|
|
51
69
|
return await self._send_command({"EnableNotification": self.id})
|
|
52
70
|
|
|
71
|
+
async def set_notification_enabled(self, enabled: bool) -> bool:
|
|
72
|
+
"""Enable or disable notifications for this automation."""
|
|
73
|
+
await self._wiser_rest_controller._send_command(
|
|
74
|
+
f"{WISERAUTOMATION.format(self.id)}/EnableNotification",
|
|
75
|
+
enabled,
|
|
76
|
+
)
|
|
77
|
+
self._automation_data["EnableNotification"] = enabled
|
|
78
|
+
return True
|
|
79
|
+
|
|
80
|
+
async def set_enabled(self, enabled: bool) -> bool:
|
|
81
|
+
"""Enable or disable this automation."""
|
|
82
|
+
await self._wiser_rest_controller._send_command(
|
|
83
|
+
f"{WISERAUTOMATION.format(self.id)}/Enabled",
|
|
84
|
+
enabled,
|
|
85
|
+
)
|
|
86
|
+
self._automation_data["Enabled"] = enabled
|
|
87
|
+
return True
|
|
88
|
+
|
|
89
|
+
async def delete(self) -> bool:
|
|
90
|
+
"""Delete this automation from the hub."""
|
|
91
|
+
await self._wiser_rest_controller._send_command(
|
|
92
|
+
WISERAUTOMATION.format(self.id),
|
|
93
|
+
None,
|
|
94
|
+
WiserRestActionEnum.DELETE,
|
|
95
|
+
)
|
|
96
|
+
return True
|
|
97
|
+
|
|
53
98
|
|
|
54
99
|
class _WiserAutomationCollection(object):
|
|
55
100
|
def __init__(
|
|
@@ -6,6 +6,7 @@ import inspect
|
|
|
6
6
|
|
|
7
7
|
from aioWiserHeatAPI import _LOGGER
|
|
8
8
|
|
|
9
|
+
from .const import WiserWindowDoorEnableNotificationEnum
|
|
9
10
|
from .helpers.battery import _WiserBattery
|
|
10
11
|
from .helpers.device import _WiserDevice
|
|
11
12
|
from .helpers.threshold import _WiserThresholdSensor
|
|
@@ -60,10 +61,54 @@ class _WiserBinarySensor(_WiserDevice):
|
|
|
60
61
|
return self._device_type_data.get("Type")
|
|
61
62
|
|
|
62
63
|
@property
|
|
63
|
-
def enable_notification(self) -> str:
|
|
64
|
-
"""
|
|
64
|
+
def enable_notification(self) -> str | None:
|
|
65
|
+
"""Return the native notification mode, when supported."""
|
|
65
66
|
return self._device_type_data.get("EnableNotification")
|
|
66
67
|
|
|
68
|
+
@property
|
|
69
|
+
def available_enable_notification(self) -> list[str]:
|
|
70
|
+
"""Return the native notification modes accepted by the hub."""
|
|
71
|
+
return [mode.value for mode in WiserWindowDoorEnableNotificationEnum]
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def notification_enabled(self) -> bool | None:
|
|
75
|
+
"""Return whether any native notification mode is enabled."""
|
|
76
|
+
mode = self.enable_notification
|
|
77
|
+
if mode is None:
|
|
78
|
+
return None
|
|
79
|
+
if isinstance(mode, bool):
|
|
80
|
+
return mode
|
|
81
|
+
return mode != WiserWindowDoorEnableNotificationEnum.none.value
|
|
82
|
+
|
|
83
|
+
async def set_enable_notification(
|
|
84
|
+
self,
|
|
85
|
+
enable_notification: WiserWindowDoorEnableNotificationEnum | str,
|
|
86
|
+
) -> bool:
|
|
87
|
+
"""Set the native notification mode for this sensor."""
|
|
88
|
+
if isinstance(
|
|
89
|
+
enable_notification, WiserWindowDoorEnableNotificationEnum
|
|
90
|
+
):
|
|
91
|
+
enable_notification = enable_notification.value
|
|
92
|
+
if enable_notification not in self.available_enable_notification:
|
|
93
|
+
raise ValueError(
|
|
94
|
+
f"{enable_notification} is not a valid notification mode. "
|
|
95
|
+
f"Valid modes are {self.available_enable_notification}"
|
|
96
|
+
)
|
|
97
|
+
await self._wiser_rest_controller._send_command(
|
|
98
|
+
self._endpoint.format(self.device_type_id),
|
|
99
|
+
{"EnableNotification": enable_notification},
|
|
100
|
+
)
|
|
101
|
+
self._device_type_data["EnableNotification"] = enable_notification
|
|
102
|
+
return True
|
|
103
|
+
|
|
104
|
+
async def set_notification_enabled(self, enabled: bool) -> bool:
|
|
105
|
+
"""Enable or disable native notifications for this sensor."""
|
|
106
|
+
return await self.set_enable_notification(
|
|
107
|
+
WiserWindowDoorEnableNotificationEnum.oneither
|
|
108
|
+
if enabled
|
|
109
|
+
else WiserWindowDoorEnableNotificationEnum.none
|
|
110
|
+
)
|
|
111
|
+
|
|
67
112
|
@property
|
|
68
113
|
def battery(self) -> _WiserBattery:
|
|
69
114
|
"""Get the battery information for the smokealarm"""
|
|
@@ -93,6 +93,7 @@ WISERHUBSCHEDULES = WISERHUBURL + "schedules/"
|
|
|
93
93
|
WISERHUBOPENTHERM = WISERHUBURL + "opentherm/"
|
|
94
94
|
WISERHUBOPENTHERMV2 = WISERHUBURL + "openTherm/"
|
|
95
95
|
WISERHUBSTATUS = WISERHUBURL + "status/"
|
|
96
|
+
WISERHUBAUTOMATION = "{}:{}/Automation/"
|
|
96
97
|
WISERSYSTEM = "System"
|
|
97
98
|
WISERDEVICE = "Device/{}"
|
|
98
99
|
WISERHOTWATER = "HotWater/{}"
|
|
@@ -113,6 +114,7 @@ WISERBUTTONPANEL = "ButtonPanel/{}"
|
|
|
113
114
|
WISEREQUIPMENT = "Equipment/{}"
|
|
114
115
|
WISERTHRESHOLDSENSOR = "ThresholdSensor/{}"
|
|
115
116
|
WISERUICONFIGURATION = "UIConfiguration/{}"
|
|
117
|
+
WISERAUTOMATION = "Automation/{}"
|
|
116
118
|
|
|
117
119
|
|
|
118
120
|
# Enums
|
|
@@ -131,6 +133,15 @@ class WiserTempLimitsEnum(enum.Enum):
|
|
|
131
133
|
floorHeatingOffset = {"min": -9, "max": 9, "type": "range"}
|
|
132
134
|
|
|
133
135
|
|
|
136
|
+
class WiserWindowDoorEnableNotificationEnum(enum.Enum):
|
|
137
|
+
"""Native notification modes supported by window and door sensors."""
|
|
138
|
+
|
|
139
|
+
oneither = "OnEither"
|
|
140
|
+
none = "None"
|
|
141
|
+
ontrue = "OnTrue"
|
|
142
|
+
onfalse = "OnFalse"
|
|
143
|
+
|
|
144
|
+
|
|
134
145
|
class WiserAwayActionEnum(enum.Enum):
|
|
135
146
|
off = TEXT_OFF
|
|
136
147
|
nochange = TEXT_NO_CHANGE
|
|
@@ -29,6 +29,7 @@ from .const import (
|
|
|
29
29
|
WISERUICONFIGURATION,
|
|
30
30
|
)
|
|
31
31
|
from .evse import _WiserEVSE, _WiserEVSECollection
|
|
32
|
+
from .equipments import _WiserEquipmentsCollection
|
|
32
33
|
from .heating_actuator import _WiserHeatingActuator, _WiserHeatingActuatorCollection
|
|
33
34
|
from .helpers.device import _WiserDevice
|
|
34
35
|
from .helpers.threshold import (
|
|
@@ -182,7 +183,13 @@ class _WiserDeviceCollection:
|
|
|
182
183
|
self._wiser_rest_controller = wiser_rest_controller
|
|
183
184
|
self._device_data = domain_data.get("Device", {})
|
|
184
185
|
self._domain_data = domain_data
|
|
185
|
-
|
|
186
|
+
equipment_data = domain_data.get("Equipment")
|
|
187
|
+
self._equipment_data = (
|
|
188
|
+
equipment_data if isinstance(equipment_data, list) else []
|
|
189
|
+
)
|
|
190
|
+
self._equipments = _WiserEquipmentsCollection(
|
|
191
|
+
wiser_rest_controller, self._equipment_data
|
|
192
|
+
)
|
|
186
193
|
self._schedules = schedules
|
|
187
194
|
self._device_collection = {}
|
|
188
195
|
|
|
@@ -358,6 +365,11 @@ class _WiserDeviceCollection:
|
|
|
358
365
|
except KeyError:
|
|
359
366
|
return None
|
|
360
367
|
|
|
368
|
+
@property
|
|
369
|
+
def equipments(self) -> _WiserEquipmentsCollection:
|
|
370
|
+
"""Return all equipment records reported by the hub."""
|
|
371
|
+
return self._equipments
|
|
372
|
+
|
|
361
373
|
@property
|
|
362
374
|
def lights(self) -> _WiserLightCollection:
|
|
363
375
|
"""Return all lights"""
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"""Represent and manage equipment records returned by a Wiser hub."""
|
|
2
|
+
|
|
3
|
+
from typing import Literal
|
|
4
|
+
|
|
5
|
+
from .const import WISEREQUIPMENT
|
|
6
|
+
from .helpers.equipment import _WiserEquipment
|
|
7
|
+
from .rest_controller import WiserRestActionEnum, _WiserRestController
|
|
8
|
+
|
|
9
|
+
PowerNotificationName = Literal[
|
|
10
|
+
"UnderPowerNotification", "OverPowerNotification"
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class _WiserEquipments(_WiserEquipment):
|
|
15
|
+
"""A single equipment record, including its writable settings."""
|
|
16
|
+
|
|
17
|
+
def __init__(
|
|
18
|
+
self, wiser_rest_controller: _WiserRestController, data: dict
|
|
19
|
+
) -> None:
|
|
20
|
+
super().__init__(data)
|
|
21
|
+
self._wiser_rest_controller = wiser_rest_controller
|
|
22
|
+
|
|
23
|
+
async def _set_power_notification(
|
|
24
|
+
self,
|
|
25
|
+
notification_name: PowerNotificationName,
|
|
26
|
+
period_mins: int,
|
|
27
|
+
limit: int,
|
|
28
|
+
enabled: bool,
|
|
29
|
+
) -> bool:
|
|
30
|
+
"""Create or replace a native power notification on the hub."""
|
|
31
|
+
notification = {
|
|
32
|
+
"PeriodMins": period_mins,
|
|
33
|
+
"Limit": limit,
|
|
34
|
+
"Enabled": enabled,
|
|
35
|
+
}
|
|
36
|
+
await self._wiser_rest_controller._send_command(
|
|
37
|
+
WISEREQUIPMENT.format(self.id),
|
|
38
|
+
{notification_name: notification},
|
|
39
|
+
)
|
|
40
|
+
self._data[notification_name] = notification
|
|
41
|
+
return True
|
|
42
|
+
|
|
43
|
+
async def set_under_power_notification(
|
|
44
|
+
self, period_mins: int, limit: int, enabled: bool = True
|
|
45
|
+
) -> bool:
|
|
46
|
+
"""Create or replace the native under-power notification."""
|
|
47
|
+
return await self._set_power_notification(
|
|
48
|
+
"UnderPowerNotification", period_mins, limit, enabled
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
async def set_over_power_notification(
|
|
52
|
+
self, period_mins: int, limit: int, enabled: bool = True
|
|
53
|
+
) -> bool:
|
|
54
|
+
"""Create or replace the native over-power notification."""
|
|
55
|
+
return await self._set_power_notification(
|
|
56
|
+
"OverPowerNotification", period_mins, limit, enabled
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
async def _set_power_notification_enabled(
|
|
60
|
+
self, notification_name: PowerNotificationName, enabled: bool
|
|
61
|
+
) -> bool:
|
|
62
|
+
"""Update an existing notification without losing its settings."""
|
|
63
|
+
notification = self._data.get(notification_name)
|
|
64
|
+
if not isinstance(notification, dict):
|
|
65
|
+
return False
|
|
66
|
+
|
|
67
|
+
updated_notification = {
|
|
68
|
+
key: notification[key]
|
|
69
|
+
for key in ("PeriodMins", "Limit")
|
|
70
|
+
if key in notification
|
|
71
|
+
}
|
|
72
|
+
updated_notification["Enabled"] = enabled
|
|
73
|
+
await self._wiser_rest_controller._send_command(
|
|
74
|
+
WISEREQUIPMENT.format(self.id),
|
|
75
|
+
{notification_name: updated_notification},
|
|
76
|
+
)
|
|
77
|
+
notification["Enabled"] = enabled
|
|
78
|
+
return True
|
|
79
|
+
|
|
80
|
+
async def set_under_power_notification_enabled(self, enabled: bool) -> bool:
|
|
81
|
+
"""Enable or disable the under-power notification."""
|
|
82
|
+
return await self._set_power_notification_enabled(
|
|
83
|
+
"UnderPowerNotification", enabled
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
async def set_over_power_notification_enabled(self, enabled: bool) -> bool:
|
|
87
|
+
"""Enable or disable the over-power notification."""
|
|
88
|
+
return await self._set_power_notification_enabled(
|
|
89
|
+
"OverPowerNotification", enabled
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
async def _delete_power_notification(
|
|
93
|
+
self, notification_name: PowerNotificationName
|
|
94
|
+
) -> bool:
|
|
95
|
+
"""Delete a native power notification from the hub."""
|
|
96
|
+
if notification_name not in self._data:
|
|
97
|
+
return False
|
|
98
|
+
|
|
99
|
+
await self._wiser_rest_controller._send_command(
|
|
100
|
+
f"{WISEREQUIPMENT.format(self.id)}/{notification_name}/",
|
|
101
|
+
None,
|
|
102
|
+
WiserRestActionEnum.DELETE,
|
|
103
|
+
)
|
|
104
|
+
self._data.pop(notification_name, None)
|
|
105
|
+
return True
|
|
106
|
+
|
|
107
|
+
async def delete_under_power_notification(self) -> bool:
|
|
108
|
+
"""Delete the native under-power notification."""
|
|
109
|
+
return await self._delete_power_notification("UnderPowerNotification")
|
|
110
|
+
|
|
111
|
+
async def delete_over_power_notification(self) -> bool:
|
|
112
|
+
"""Delete the native over-power notification."""
|
|
113
|
+
return await self._delete_power_notification("OverPowerNotification")
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class _WiserEquipmentsCollection:
|
|
117
|
+
"""Collection of equipment records reported by the hub."""
|
|
118
|
+
|
|
119
|
+
def __init__(
|
|
120
|
+
self,
|
|
121
|
+
wiser_rest_controller: _WiserRestController,
|
|
122
|
+
equipments_data: list[dict] | None = None,
|
|
123
|
+
) -> None:
|
|
124
|
+
records = equipments_data if isinstance(equipments_data, list) else []
|
|
125
|
+
self._items = [
|
|
126
|
+
_WiserEquipments(wiser_rest_controller, record)
|
|
127
|
+
for record in records
|
|
128
|
+
if isinstance(record, dict)
|
|
129
|
+
]
|
|
130
|
+
self._items_by_id = {
|
|
131
|
+
item.id: item
|
|
132
|
+
for item in self._items
|
|
133
|
+
if isinstance(item._data.get("id"), int)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
@property
|
|
137
|
+
def all(self) -> list[_WiserEquipments]:
|
|
138
|
+
"""Return all equipment records."""
|
|
139
|
+
return list(self._items)
|
|
140
|
+
|
|
141
|
+
@property
|
|
142
|
+
def count(self) -> int:
|
|
143
|
+
"""Return the number of equipment records."""
|
|
144
|
+
return len(self._items)
|
|
145
|
+
|
|
146
|
+
def get_by_id(self, equipment_id: int) -> _WiserEquipments | None:
|
|
147
|
+
"""Return an equipment record by its id."""
|
|
148
|
+
return self._items_by_id.get(equipment_id)
|
|
149
|
+
|
|
150
|
+
def get_by_equipment_id(self, equipment_id: int) -> _WiserEquipments | None:
|
|
151
|
+
"""Return an equipment record by its equipment id."""
|
|
152
|
+
return self.get_by_id(equipment_id)
|
|
153
|
+
|
|
154
|
+
def get_by_device_id(self, device_id: int) -> _WiserEquipments | None:
|
|
155
|
+
"""Return the equipment record linked to a device instance."""
|
|
156
|
+
return next(
|
|
157
|
+
(item for item in self._items if item.device_id == device_id),
|
|
158
|
+
None,
|
|
159
|
+
)
|
|
@@ -36,7 +36,7 @@ class _WiserEVSE(_WiserDevice):
|
|
|
36
36
|
@property
|
|
37
37
|
def available_control_states(self) -> list[str]:
|
|
38
38
|
"""Return control states accepted by the hub for this equipment."""
|
|
39
|
-
return self.
|
|
39
|
+
return self.equipment.available_control_states
|
|
40
40
|
|
|
41
41
|
@property
|
|
42
42
|
def control_state(self) -> str:
|
|
@@ -69,12 +69,15 @@ class _WiserEVSE(_WiserDevice):
|
|
|
69
69
|
result = await self._wiser_rest_controller._send_command(
|
|
70
70
|
WISEREQUIPMENT.format(self.equipment_id), {"ControlState": state}
|
|
71
71
|
)
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
72
|
+
# Successful hub commands can return an empty object or only the
|
|
73
|
+
# changed field. Preserve existing telemetry and reflect the requested
|
|
74
|
+
# state until the next poll supplies a complete equipment record.
|
|
75
|
+
self._device_type_data = {
|
|
76
|
+
**self._device_type_data,
|
|
77
|
+
**result,
|
|
78
|
+
"ControlState": result.get("ControlState", state),
|
|
79
|
+
}
|
|
80
|
+
return True
|
|
78
81
|
|
|
79
82
|
async def turn_on(self) -> bool:
|
|
80
83
|
"""Return the EVSE to its normal self-managed state."""
|
|
@@ -47,14 +47,14 @@ class _WiserEquipmentUnderPowerNotificationInfo(object):
|
|
|
47
47
|
self._data = data
|
|
48
48
|
|
|
49
49
|
@property
|
|
50
|
-
def period_mins(self) -> int:
|
|
51
|
-
"""Get
|
|
52
|
-
return self._data.get("PeriodMins"
|
|
50
|
+
def period_mins(self) -> int | None:
|
|
51
|
+
"""Get notification period in minutes."""
|
|
52
|
+
return self._data.get("PeriodMins")
|
|
53
53
|
|
|
54
54
|
@property
|
|
55
|
-
def limit(self) -> int:
|
|
55
|
+
def limit(self) -> int | None:
|
|
56
56
|
"""Get limit"""
|
|
57
|
-
return self._data.get("Limit"
|
|
57
|
+
return self._data.get("Limit")
|
|
58
58
|
|
|
59
59
|
@property
|
|
60
60
|
def enabled(self) -> bool:
|
|
@@ -68,19 +68,19 @@ class _WiserEquipmentOverPowerNotificationInfo:
|
|
|
68
68
|
self._equipment_instance = equipment_instance
|
|
69
69
|
|
|
70
70
|
@property
|
|
71
|
-
def period_mins(self) -> int:
|
|
71
|
+
def period_mins(self) -> int | None:
|
|
72
72
|
"""Get period in minutes"""
|
|
73
|
-
return self._data.get("PeriodMins")
|
|
73
|
+
return self._data.get("PeriodMins")
|
|
74
74
|
|
|
75
75
|
@property
|
|
76
|
-
def limit(self) -> int:
|
|
76
|
+
def limit(self) -> int | None:
|
|
77
77
|
"""Get limit"""
|
|
78
|
-
return self._data.get("Limit"
|
|
78
|
+
return self._data.get("Limit")
|
|
79
79
|
|
|
80
80
|
@property
|
|
81
81
|
def enabled(self) -> bool:
|
|
82
82
|
"""Get status enable"""
|
|
83
|
-
return self._data.get("Enabled", False)
|
|
83
|
+
return self._data.get("Enabled", False)
|
|
84
84
|
|
|
85
85
|
|
|
86
86
|
class _WiserEquipment:
|
|
@@ -189,6 +189,27 @@ class _WiserEquipment:
|
|
|
189
189
|
"""Get current control mode"""
|
|
190
190
|
return self._data.get("CurrentControlMode", TEXT_UNKNOWN)
|
|
191
191
|
|
|
192
|
+
@property
|
|
193
|
+
def available_control_states(self) -> list[str]:
|
|
194
|
+
"""Get the control states available for this equipment"""
|
|
195
|
+
states = self._data.get("AvailableControlState")
|
|
196
|
+
return list(states) if isinstance(states, list) else []
|
|
197
|
+
|
|
198
|
+
@property
|
|
199
|
+
def overview_schedule_id(self) -> int | None:
|
|
200
|
+
"""Get the overview schedule ID for this equipment"""
|
|
201
|
+
return self._data.get("OverviewScheduleId")
|
|
202
|
+
|
|
203
|
+
@property
|
|
204
|
+
def stated_power(self) -> int | None:
|
|
205
|
+
"""Get the stated power value available for this equipment"""
|
|
206
|
+
return self._data.get("StatedPower")
|
|
207
|
+
|
|
208
|
+
@property
|
|
209
|
+
def cable_stated_power(self) -> int | None:
|
|
210
|
+
"""Get the cabled stated power value available for this EVSE equipment"""
|
|
211
|
+
return self._data.get("CableStatedPower")
|
|
212
|
+
|
|
192
213
|
@property
|
|
193
214
|
def load_state_status(self) -> str:
|
|
194
215
|
"""Get load state status"""
|
|
@@ -252,7 +273,9 @@ class _WiserEquipment:
|
|
|
252
273
|
# Add for a notification feature Notification if the Power is under or over a threshold
|
|
253
274
|
# for a time (periodmins)...
|
|
254
275
|
@property
|
|
255
|
-
def under_power_notification(
|
|
276
|
+
def under_power_notification(
|
|
277
|
+
self,
|
|
278
|
+
) -> _WiserEquipmentUnderPowerNotificationInfo | None:
|
|
256
279
|
"""Get notification info"""
|
|
257
280
|
return (
|
|
258
281
|
_WiserEquipmentUnderPowerNotificationInfo(
|
|
@@ -263,10 +286,13 @@ class _WiserEquipment:
|
|
|
263
286
|
)
|
|
264
287
|
|
|
265
288
|
@property
|
|
266
|
-
def over_power_notification(
|
|
289
|
+
def over_power_notification(
|
|
290
|
+
self,
|
|
291
|
+
) -> _WiserEquipmentOverPowerNotificationInfo | None:
|
|
267
292
|
"""Get notification info"""
|
|
268
293
|
return (
|
|
269
|
-
|
|
294
|
+
_WiserEquipmentOverPowerNotificationInfo(
|
|
295
|
+
self,
|
|
270
296
|
self._data.get("OverPowerNotification")
|
|
271
297
|
)
|
|
272
298
|
if self._data.get("OverPowerNotification")
|