aioWiserHeatAPI 1.7.7__tar.gz → 1.7.9__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.9/PKG-INFO +73 -0
- aiowiserheatapi-1.7.9/README.md +43 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.9}/aioWiserHeatAPI/__init__.py +1 -1
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.9}/aioWiserHeatAPI/automation.py +47 -2
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.9}/aioWiserHeatAPI/binary_sensor.py +47 -2
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.9}/aioWiserHeatAPI/const.py +30 -4
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.9}/aioWiserHeatAPI/devices.py +13 -1
- aiowiserheatapi-1.7.9/aioWiserHeatAPI/equipments.py +159 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.9}/aioWiserHeatAPI/evse.py +10 -7
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.9}/aioWiserHeatAPI/heating_actuator.py +50 -6
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.9}/aioWiserHeatAPI/helpers/equipment.py +39 -13
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.9}/aioWiserHeatAPI/light.py +42 -16
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.9}/aioWiserHeatAPI/ptc.py +4 -4
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.9}/aioWiserHeatAPI/rest_controller.py +59 -12
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.9}/aioWiserHeatAPI/smartplug.py +65 -1
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.9}/aioWiserHeatAPI/smokealarm.py +14 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.9}/aioWiserHeatAPI/wiserhub.py +13 -0
- aiowiserheatapi-1.7.9/aioWiserHeatAPI.egg-info/PKG-INFO +73 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.9}/aioWiserHeatAPI.egg-info/SOURCES.txt +12 -1
- aiowiserheatapi-1.7.9/tests/test_device_notifications.py +191 -0
- aiowiserheatapi-1.7.9/tests/test_equipment.py +148 -0
- aiowiserheatapi-1.7.9/tests/test_equipments.py +295 -0
- aiowiserheatapi-1.7.9/tests/test_evse.py +41 -0
- aiowiserheatapi-1.7.9/tests/test_heating_actuator.py +103 -0
- aiowiserheatapi-1.7.9/tests/test_light.py +115 -0
- aiowiserheatapi-1.7.9/tests/test_ptc.py +74 -0
- aiowiserheatapi-1.7.9/tests/test_rest_controller.py +94 -0
- aiowiserheatapi-1.7.9/tests/test_rest_payloads.py +73 -0
- aiowiserheatapi-1.7.9/tests/test_smartplug.py +120 -0
- aiowiserheatapi-1.7.7/PKG-INFO +0 -368
- aiowiserheatapi-1.7.7/README.md +0 -338
- aiowiserheatapi-1.7.7/aioWiserHeatAPI.egg-info/PKG-INFO +0 -368
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.9}/LICENSE +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.9}/aioWiserHeatAPI/boiler_interface.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.9}/aioWiserHeatAPI/button_panel.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.9}/aioWiserHeatAPI/cli.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.9}/aioWiserHeatAPI/discovery.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.9}/aioWiserHeatAPI/exceptions.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.9}/aioWiserHeatAPI/heating.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.9}/aioWiserHeatAPI/helpers/__init__.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.9}/aioWiserHeatAPI/helpers/automations.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.9}/aioWiserHeatAPI/helpers/battery.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.9}/aioWiserHeatAPI/helpers/capabilities.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.9}/aioWiserHeatAPI/helpers/cloud.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.9}/aioWiserHeatAPI/helpers/device.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.9}/aioWiserHeatAPI/helpers/extra_config.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.9}/aioWiserHeatAPI/helpers/firmware.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.9}/aioWiserHeatAPI/helpers/gps.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.9}/aioWiserHeatAPI/helpers/misc.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.9}/aioWiserHeatAPI/helpers/network.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.9}/aioWiserHeatAPI/helpers/opentherm.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.9}/aioWiserHeatAPI/helpers/signal.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.9}/aioWiserHeatAPI/helpers/special_times.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.9}/aioWiserHeatAPI/helpers/status.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.9}/aioWiserHeatAPI/helpers/temp.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.9}/aioWiserHeatAPI/helpers/threshold.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.9}/aioWiserHeatAPI/helpers/uiconfiguration.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.9}/aioWiserHeatAPI/helpers/version.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.9}/aioWiserHeatAPI/helpers/zigbee.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.9}/aioWiserHeatAPI/hot_water.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.9}/aioWiserHeatAPI/moments.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.9}/aioWiserHeatAPI/pte.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.9}/aioWiserHeatAPI/refactor.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.9}/aioWiserHeatAPI/room.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.9}/aioWiserHeatAPI/roomstat.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.9}/aioWiserHeatAPI/schedule.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.9}/aioWiserHeatAPI/shutter.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.9}/aioWiserHeatAPI/smartvalve.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.9}/aioWiserHeatAPI/system.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.9}/aioWiserHeatAPI/temp_humidity.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.9}/aioWiserHeatAPI/ufh.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.9}/aioWiserHeatAPI.egg-info/dependency_links.txt +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.9}/aioWiserHeatAPI.egg-info/entry_points.txt +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.9}/aioWiserHeatAPI.egg-info/requires.txt +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.9}/aioWiserHeatAPI.egg-info/top_level.txt +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.9}/setup.cfg +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.9}/setup.py +0 -0
- {aiowiserheatapi-1.7.7 → aiowiserheatapi-1.7.9}/tests/test_schedule.py +0 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: aioWiserHeatAPI
|
|
3
|
+
Version: 1.7.9
|
|
4
|
+
Summary: An AsyncIO API for controlling the Drayton Wiser Heating system
|
|
5
|
+
Home-page: https://github.com/msp1974/aioWiserHeatAPI
|
|
6
|
+
Author: Mark Parker
|
|
7
|
+
Author-email: msparker@sky.com
|
|
8
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Requires-Python: >=3.12
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
License-File: LICENSE
|
|
16
|
+
Requires-Dist: zeroconf>=0.131.0
|
|
17
|
+
Requires-Dist: aiohttp>=3.9.3
|
|
18
|
+
Requires-Dist: aiofiles>=23.2.1
|
|
19
|
+
Requires-Dist: pyyaml>=6.0.1
|
|
20
|
+
Dynamic: author
|
|
21
|
+
Dynamic: author-email
|
|
22
|
+
Dynamic: classifier
|
|
23
|
+
Dynamic: description
|
|
24
|
+
Dynamic: description-content-type
|
|
25
|
+
Dynamic: home-page
|
|
26
|
+
Dynamic: license-file
|
|
27
|
+
Dynamic: requires-dist
|
|
28
|
+
Dynamic: requires-python
|
|
29
|
+
Dynamic: summary
|
|
30
|
+
|
|
31
|
+
# Drayton Wiser Hub API Async
|
|
32
|
+
|
|
33
|
+
An asynchronous Python API for the Drayton Wiser heating system used in the UK.
|
|
34
|
+
It supports one-, two-, and three-channel HeatHubs, Smart Plugs, shutters, lights,
|
|
35
|
+
and other Wiser devices.
|
|
36
|
+
|
|
37
|
+
## Requirements
|
|
38
|
+
|
|
39
|
+
Python 3.12 or later.
|
|
40
|
+
|
|
41
|
+
## Installation
|
|
42
|
+
|
|
43
|
+
```sh
|
|
44
|
+
python -m pip install aioWiserHeatAPI
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Hub setup
|
|
48
|
+
|
|
49
|
+
### 1. Find your HeatHub secret key
|
|
50
|
+
|
|
51
|
+
1. Press the setup button on your HeatHub. The light will start flashing.
|
|
52
|
+
2. Connect your computer or phone to the Wi-Fi network named `WiserHeatXXXXXX`,
|
|
53
|
+
where `XXXXXX` is the last six digits of the hub's MAC address.
|
|
54
|
+
3. Open `http://192.168.8.1/secret` in a browser. Save the returned string as your
|
|
55
|
+
hub's secret key.
|
|
56
|
+
4. Press the setup button again to return the HeatHub to normal operation.
|
|
57
|
+
|
|
58
|
+
See the [HeatHub connection guide](https://it.knightnet.org.uk/kb/nr-qa/drayton-wiser-heating-control/#controlling-the-system)
|
|
59
|
+
for more information.
|
|
60
|
+
|
|
61
|
+
### 2. Find your HeatHub IP address
|
|
62
|
+
|
|
63
|
+
Use your router's connected-device list to find the HeatHub's IP address. The hub
|
|
64
|
+
usually identifies itself as `WiserHeatXXXXXX`.
|
|
65
|
+
|
|
66
|
+
Hub discovery is also available through the API; see
|
|
67
|
+
[discovery.py](aioWiserHeatAPI/discovery.py).
|
|
68
|
+
|
|
69
|
+
## Documentation
|
|
70
|
+
|
|
71
|
+
- [API documentation](docs/info.md)
|
|
72
|
+
- [API implementation](aioWiserHeatAPI/wiserhub.py)
|
|
73
|
+
- [Release history](CHANGELOG.md)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Drayton Wiser Hub API Async
|
|
2
|
+
|
|
3
|
+
An asynchronous Python API for the Drayton Wiser heating system used in the UK.
|
|
4
|
+
It supports one-, two-, and three-channel HeatHubs, Smart Plugs, shutters, lights,
|
|
5
|
+
and other Wiser devices.
|
|
6
|
+
|
|
7
|
+
## Requirements
|
|
8
|
+
|
|
9
|
+
Python 3.12 or later.
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
python -m pip install aioWiserHeatAPI
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Hub setup
|
|
18
|
+
|
|
19
|
+
### 1. Find your HeatHub secret key
|
|
20
|
+
|
|
21
|
+
1. Press the setup button on your HeatHub. The light will start flashing.
|
|
22
|
+
2. Connect your computer or phone to the Wi-Fi network named `WiserHeatXXXXXX`,
|
|
23
|
+
where `XXXXXX` is the last six digits of the hub's MAC address.
|
|
24
|
+
3. Open `http://192.168.8.1/secret` in a browser. Save the returned string as your
|
|
25
|
+
hub's secret key.
|
|
26
|
+
4. Press the setup button again to return the HeatHub to normal operation.
|
|
27
|
+
|
|
28
|
+
See the [HeatHub connection guide](https://it.knightnet.org.uk/kb/nr-qa/drayton-wiser-heating-control/#controlling-the-system)
|
|
29
|
+
for more information.
|
|
30
|
+
|
|
31
|
+
### 2. Find your HeatHub IP address
|
|
32
|
+
|
|
33
|
+
Use your router's connected-device list to find the HeatHub's IP address. The hub
|
|
34
|
+
usually identifies itself as `WiserHeatXXXXXX`.
|
|
35
|
+
|
|
36
|
+
Hub discovery is also available through the API; see
|
|
37
|
+
[discovery.py](aioWiserHeatAPI/discovery.py).
|
|
38
|
+
|
|
39
|
+
## Documentation
|
|
40
|
+
|
|
41
|
+
- [API documentation](docs/info.md)
|
|
42
|
+
- [API implementation](aioWiserHeatAPI/wiserhub.py)
|
|
43
|
+
- [Release history](CHANGELOG.md)
|
|
@@ -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"""
|
|
@@ -63,7 +63,6 @@ TEXT_WEEKDAYS = "Weekdays"
|
|
|
63
63
|
TEXT_WEEKENDS = "Weekends"
|
|
64
64
|
TEXT_ALL = "All"
|
|
65
65
|
TEXT_UNABLE = "Unable"
|
|
66
|
-
# added by Lgo44
|
|
67
66
|
|
|
68
67
|
TEXT_REVERSE = "ReverseWithLoad"
|
|
69
68
|
TEXT_CONSISTENT = "ConsistentWithLoad"
|
|
@@ -74,7 +73,10 @@ TEXT_OFF_AFTER_2S = "OffAfter2s"
|
|
|
74
73
|
TEXT_LASTON = "LastOnBrightness"
|
|
75
74
|
TEXT_LEVELPERCENT = "LevelPercent"
|
|
76
75
|
|
|
77
|
-
|
|
76
|
+
TEXT_RL_LED = "RL_LED"
|
|
77
|
+
|
|
78
|
+
TEXT_RELAY = "Relay"
|
|
79
|
+
TEXT_FIL_PILOTE = "FilPilote"
|
|
78
80
|
|
|
79
81
|
# Day Value Lists
|
|
80
82
|
WEEKDAYS = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"]
|
|
@@ -93,6 +95,7 @@ WISERHUBSCHEDULES = WISERHUBURL + "schedules/"
|
|
|
93
95
|
WISERHUBOPENTHERM = WISERHUBURL + "opentherm/"
|
|
94
96
|
WISERHUBOPENTHERMV2 = WISERHUBURL + "openTherm/"
|
|
95
97
|
WISERHUBSTATUS = WISERHUBURL + "status/"
|
|
98
|
+
WISERHUBAUTOMATION = "{}:{}/Automation/"
|
|
96
99
|
WISERSYSTEM = "System"
|
|
97
100
|
WISERDEVICE = "Device/{}"
|
|
98
101
|
WISERHOTWATER = "HotWater/{}"
|
|
@@ -113,6 +116,7 @@ WISERBUTTONPANEL = "ButtonPanel/{}"
|
|
|
113
116
|
WISEREQUIPMENT = "Equipment/{}"
|
|
114
117
|
WISERTHRESHOLDSENSOR = "ThresholdSensor/{}"
|
|
115
118
|
WISERUICONFIGURATION = "UIConfiguration/{}"
|
|
119
|
+
WISERAUTOMATION = "Automation/{}"
|
|
116
120
|
|
|
117
121
|
|
|
118
122
|
# Enums
|
|
@@ -131,6 +135,15 @@ class WiserTempLimitsEnum(enum.Enum):
|
|
|
131
135
|
floorHeatingOffset = {"min": -9, "max": 9, "type": "range"}
|
|
132
136
|
|
|
133
137
|
|
|
138
|
+
class WiserWindowDoorEnableNotificationEnum(enum.Enum):
|
|
139
|
+
"""Native notification modes supported by window and door sensors."""
|
|
140
|
+
|
|
141
|
+
oneither = "OnEither"
|
|
142
|
+
none = "None"
|
|
143
|
+
ontrue = "OnTrue"
|
|
144
|
+
onfalse = "OnFalse"
|
|
145
|
+
|
|
146
|
+
|
|
134
147
|
class WiserAwayActionEnum(enum.Enum):
|
|
135
148
|
off = TEXT_OFF
|
|
136
149
|
nochange = TEXT_NO_CHANGE
|
|
@@ -141,7 +154,6 @@ class WiserShutterAwayActionEnum(enum.Enum):
|
|
|
141
154
|
nochange = TEXT_NO_CHANGE
|
|
142
155
|
|
|
143
156
|
|
|
144
|
-
# added by Lgo44
|
|
145
157
|
class WiserLightLedIndicatorEnum(enum.Enum):
|
|
146
158
|
reverse = TEXT_REVERSE
|
|
147
159
|
consistent = TEXT_CONSISTENT
|
|
@@ -155,7 +167,21 @@ class WiserLightPowerOnBehaviourEnum(enum.Enum):
|
|
|
155
167
|
levelpercent = TEXT_LEVELPERCENT
|
|
156
168
|
|
|
157
169
|
|
|
158
|
-
|
|
170
|
+
class WiserLightOutputModeEnum(enum.Enum):
|
|
171
|
+
auto = TEXT_AUTO
|
|
172
|
+
rl_led = TEXT_RL_LED
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
class WiserSmartPlugLedIndicatorEnum(enum.Enum):
|
|
176
|
+
reverse = TEXT_REVERSE
|
|
177
|
+
consistent = TEXT_CONSISTENT
|
|
178
|
+
allwayson = TEXT_ALWAYSON
|
|
179
|
+
allwaysoff = TEXT_ALWAYSOFF
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
class WiserHeatingActuatorOutputTypeEnum(enum.Enum):
|
|
183
|
+
relay = TEXT_RELAY
|
|
184
|
+
fil_pilote = TEXT_FIL_PILOTE
|
|
159
185
|
|
|
160
186
|
|
|
161
187
|
class WiserDeviceModeEnum(enum.Enum):
|
|
@@ -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."""
|
|
@@ -1,4 +1,13 @@
|
|
|
1
|
-
from
|
|
1
|
+
from typing import Union
|
|
2
|
+
|
|
3
|
+
from .const import (
|
|
4
|
+
TEMP_OFF,
|
|
5
|
+
TEXT_UNKNOWN,
|
|
6
|
+
WISERDEVICE,
|
|
7
|
+
WISERHEATINGACTUATOR,
|
|
8
|
+
WiserHeatingActuatorOutputTypeEnum,
|
|
9
|
+
WiserTempLimitsEnum,
|
|
10
|
+
)
|
|
2
11
|
from .helpers.device import _WiserDevice
|
|
3
12
|
from .helpers.equipment import _WiserEquipment
|
|
4
13
|
from .helpers.temp import _WiserTemperatureFunctions as tf
|
|
@@ -115,6 +124,22 @@ class _WiserHeatingActuator(_WiserDevice):
|
|
|
115
124
|
self._threshold_sensors: list[_WiserThresholdSensor] = []
|
|
116
125
|
self._uiconfig_sensors: list[_WiserUIConfigSensor] = []
|
|
117
126
|
|
|
127
|
+
async def _send_command(self, cmd: dict, device_level: bool = False) -> bool:
|
|
128
|
+
"""Send commands using the ID belonging to the requested endpoint."""
|
|
129
|
+
endpoint = (
|
|
130
|
+
WISERDEVICE.format(self.id)
|
|
131
|
+
if device_level
|
|
132
|
+
else self._endpoint.format(self.device_type_id)
|
|
133
|
+
)
|
|
134
|
+
result = await self._wiser_rest_controller._send_command(endpoint, cmd)
|
|
135
|
+
if not result:
|
|
136
|
+
return False
|
|
137
|
+
if device_level:
|
|
138
|
+
self._data = result
|
|
139
|
+
else:
|
|
140
|
+
self._device_type_data = result
|
|
141
|
+
return True
|
|
142
|
+
|
|
118
143
|
@property
|
|
119
144
|
def current_target_temperature(self) -> float:
|
|
120
145
|
"""Get the current target temperature setting"""
|
|
@@ -164,11 +189,6 @@ class _WiserHeatingActuator(_WiserDevice):
|
|
|
164
189
|
"""Get the amount of current passing through the device now"""
|
|
165
190
|
return self._device_type_data.get("InstantaneousDemand", None)
|
|
166
191
|
|
|
167
|
-
@property
|
|
168
|
-
def output_type(self) -> str:
|
|
169
|
-
"""Get output type"""
|
|
170
|
-
return self._device_type_data.get("OutputType", TEXT_UNKNOWN)
|
|
171
|
-
|
|
172
192
|
@property
|
|
173
193
|
def threshold_sensors(self) -> list[_WiserThresholdSensor]:
|
|
174
194
|
"""Get threshold sensors."""
|
|
@@ -179,6 +199,30 @@ class _WiserHeatingActuator(_WiserDevice):
|
|
|
179
199
|
"""Get ui configuration sensors."""
|
|
180
200
|
return self._uiconfig_sensors
|
|
181
201
|
|
|
202
|
+
@property
|
|
203
|
+
def available_output_type(self) -> list[str]:
|
|
204
|
+
"""Get available heating-actuator output types."""
|
|
205
|
+
return [output_type.value for output_type in WiserHeatingActuatorOutputTypeEnum]
|
|
206
|
+
|
|
207
|
+
@property
|
|
208
|
+
def output_type(self) -> str:
|
|
209
|
+
"""Get the output type for the heating actuator."""
|
|
210
|
+
return self._device_type_data.get("OutputType", TEXT_UNKNOWN)
|
|
211
|
+
|
|
212
|
+
async def set_output_type(
|
|
213
|
+
self, output_type: Union[WiserHeatingActuatorOutputTypeEnum, str]
|
|
214
|
+
) -> bool:
|
|
215
|
+
"""Set the output type using an enum or case-insensitive string."""
|
|
216
|
+
if isinstance(output_type, WiserHeatingActuatorOutputTypeEnum):
|
|
217
|
+
output_type = output_type.value
|
|
218
|
+
if isinstance(output_type, str):
|
|
219
|
+
for mode in WiserHeatingActuatorOutputTypeEnum:
|
|
220
|
+
if output_type.casefold() == mode.value.casefold():
|
|
221
|
+
return await self._send_command({"OutputType": mode.value})
|
|
222
|
+
raise ValueError(
|
|
223
|
+
f"{output_type} is not a valid type. Valid types are {self.available_output_type}"
|
|
224
|
+
)
|
|
225
|
+
|
|
182
226
|
|
|
183
227
|
class _WiserHeatingActuatorCollection(object):
|
|
184
228
|
"""Class holding all wiser heating actuators"""
|