plugwise 0.36.3__py3-none-any.whl → 0.37.0__py3-none-any.whl
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.
- plugwise/__init__.py +187 -713
- plugwise/data.py +263 -0
- plugwise/helper.py +122 -367
- plugwise/legacy/data.py +124 -0
- plugwise/legacy/helper.py +775 -0
- plugwise/legacy/smile.py +272 -0
- plugwise/smile.py +426 -0
- plugwise/util.py +35 -1
- {plugwise-0.36.3.dist-info → plugwise-0.37.0.dist-info}/METADATA +1 -1
- plugwise-0.37.0.dist-info/RECORD +16 -0
- plugwise-0.36.3.dist-info/RECORD +0 -11
- {plugwise-0.36.3.dist-info → plugwise-0.37.0.dist-info}/LICENSE +0 -0
- {plugwise-0.36.3.dist-info → plugwise-0.37.0.dist-info}/WHEEL +0 -0
- {plugwise-0.36.3.dist-info → plugwise-0.37.0.dist-info}/top_level.txt +0 -0
plugwise/data.py
ADDED
@@ -0,0 +1,263 @@
|
|
1
|
+
"""Use of this source code is governed by the MIT license found in the LICENSE file.
|
2
|
+
|
3
|
+
Plugwise Smile protocol data-collection helpers.
|
4
|
+
"""
|
5
|
+
from __future__ import annotations
|
6
|
+
|
7
|
+
from plugwise.constants import (
|
8
|
+
ADAM,
|
9
|
+
ANNA,
|
10
|
+
MAX_SETPOINT,
|
11
|
+
MIN_SETPOINT,
|
12
|
+
NONE,
|
13
|
+
OFF,
|
14
|
+
SWITCH_GROUP_TYPES,
|
15
|
+
ZONE_THERMOSTATS,
|
16
|
+
ActuatorData,
|
17
|
+
DeviceData,
|
18
|
+
)
|
19
|
+
from plugwise.helper import SmileHelper
|
20
|
+
from plugwise.util import remove_empty_platform_dicts
|
21
|
+
|
22
|
+
|
23
|
+
class SmileData(SmileHelper):
|
24
|
+
"""The Plugwise Smile main class."""
|
25
|
+
|
26
|
+
def __init__(self) -> None:
|
27
|
+
"""Init."""
|
28
|
+
SmileHelper.__init__(self)
|
29
|
+
|
30
|
+
|
31
|
+
def _update_gw_devices(self) -> None:
|
32
|
+
"""Helper-function for _all_device_data() and async_update().
|
33
|
+
|
34
|
+
Collect data for each device and add to self.gw_devices.
|
35
|
+
"""
|
36
|
+
for device_id, device in self.gw_devices.items():
|
37
|
+
data = self._get_device_data(device_id)
|
38
|
+
self._add_or_update_notifications(device_id, device, data)
|
39
|
+
device.update(data)
|
40
|
+
self._update_for_cooling(device)
|
41
|
+
remove_empty_platform_dicts(device)
|
42
|
+
|
43
|
+
def _add_or_update_notifications(
|
44
|
+
self, device_id: str, device: DeviceData, data: DeviceData
|
45
|
+
) -> None:
|
46
|
+
"""Helper-function adding or updating the Plugwise notifications."""
|
47
|
+
if (
|
48
|
+
device_id == self.gateway_id
|
49
|
+
and (
|
50
|
+
self._is_thermostat or self.smile_type == "power"
|
51
|
+
)
|
52
|
+
) or (
|
53
|
+
"binary_sensors" in device
|
54
|
+
and "plugwise_notification" in device["binary_sensors"]
|
55
|
+
):
|
56
|
+
data["binary_sensors"]["plugwise_notification"] = bool(self._notifications)
|
57
|
+
self._count += 1
|
58
|
+
|
59
|
+
def _update_for_cooling(self, device: DeviceData) -> None:
|
60
|
+
"""Helper-function for adding/updating various cooling-related values."""
|
61
|
+
# For Anna and heating + cooling, replace setpoint with setpoint_high/_low
|
62
|
+
if (
|
63
|
+
self.smile(ANNA)
|
64
|
+
and self._cooling_present
|
65
|
+
and device["dev_class"] == "thermostat"
|
66
|
+
):
|
67
|
+
thermostat = device["thermostat"]
|
68
|
+
sensors = device["sensors"]
|
69
|
+
temp_dict: ActuatorData = {
|
70
|
+
"setpoint_low": thermostat["setpoint"],
|
71
|
+
"setpoint_high": MAX_SETPOINT,
|
72
|
+
}
|
73
|
+
if self._cooling_enabled:
|
74
|
+
temp_dict = {
|
75
|
+
"setpoint_low": MIN_SETPOINT,
|
76
|
+
"setpoint_high": thermostat["setpoint"],
|
77
|
+
}
|
78
|
+
thermostat.pop("setpoint")
|
79
|
+
temp_dict.update(thermostat)
|
80
|
+
device["thermostat"] = temp_dict
|
81
|
+
if "setpoint" in sensors:
|
82
|
+
sensors.pop("setpoint")
|
83
|
+
sensors["setpoint_low"] = temp_dict["setpoint_low"]
|
84
|
+
sensors["setpoint_high"] = temp_dict["setpoint_high"]
|
85
|
+
self._count += 2
|
86
|
+
|
87
|
+
def _all_device_data(self) -> None:
|
88
|
+
"""Helper-function for get_all_devices().
|
89
|
+
|
90
|
+
Collect data for each device and add to self.gw_data and self.gw_devices.
|
91
|
+
"""
|
92
|
+
self._update_gw_devices()
|
93
|
+
self.device_items = self._count
|
94
|
+
self.gw_data.update(
|
95
|
+
{
|
96
|
+
"gateway_id": self.gateway_id,
|
97
|
+
"item_count": self._count,
|
98
|
+
"notifications": self._notifications,
|
99
|
+
"smile_name": self.smile_name,
|
100
|
+
}
|
101
|
+
)
|
102
|
+
if self._is_thermostat:
|
103
|
+
self.gw_data.update(
|
104
|
+
{"heater_id": self._heater_id, "cooling_present": self._cooling_present}
|
105
|
+
)
|
106
|
+
|
107
|
+
def _device_data_switching_group(
|
108
|
+
self, device: DeviceData, data: DeviceData
|
109
|
+
) -> None:
|
110
|
+
"""Helper-function for _get_device_data().
|
111
|
+
|
112
|
+
Determine switching group device data.
|
113
|
+
"""
|
114
|
+
if device["dev_class"] in SWITCH_GROUP_TYPES:
|
115
|
+
counter = 0
|
116
|
+
for member in device["members"]:
|
117
|
+
if self.gw_devices[member]["switches"].get("relay"):
|
118
|
+
counter += 1
|
119
|
+
data["switches"]["relay"] = counter != 0
|
120
|
+
self._count += 1
|
121
|
+
|
122
|
+
def _device_data_adam(self, device: DeviceData, data: DeviceData) -> None:
|
123
|
+
"""Helper-function for _get_device_data().
|
124
|
+
|
125
|
+
Determine Adam heating-status for on-off heating via valves,
|
126
|
+
available regulations_modes and thermostat control_states.
|
127
|
+
"""
|
128
|
+
if self.smile(ADAM):
|
129
|
+
# Indicate heating_state based on valves being open in case of city-provided heating
|
130
|
+
if (
|
131
|
+
device["dev_class"] == "heater_central"
|
132
|
+
and self._on_off_device
|
133
|
+
and isinstance(self._heating_valves(), int)
|
134
|
+
):
|
135
|
+
data["binary_sensors"]["heating_state"] = self._heating_valves() != 0
|
136
|
+
|
137
|
+
# Show the allowed regulation modes and gateway_modes
|
138
|
+
if device["dev_class"] == "gateway":
|
139
|
+
if self._reg_allowed_modes:
|
140
|
+
data["regulation_modes"] = self._reg_allowed_modes
|
141
|
+
self._count += 1
|
142
|
+
if self._gw_allowed_modes:
|
143
|
+
data["gateway_modes"] = self._gw_allowed_modes
|
144
|
+
self._count += 1
|
145
|
+
|
146
|
+
# Control_state, only for Adam master thermostats
|
147
|
+
if device["dev_class"] in ZONE_THERMOSTATS:
|
148
|
+
loc_id = device["location"]
|
149
|
+
if ctrl_state := self._control_state(loc_id):
|
150
|
+
data["control_state"] = ctrl_state
|
151
|
+
self._count += 1
|
152
|
+
|
153
|
+
def _device_data_climate(self, device: DeviceData, data: DeviceData) -> None:
|
154
|
+
"""Helper-function for _get_device_data().
|
155
|
+
|
156
|
+
Determine climate-control device data.
|
157
|
+
"""
|
158
|
+
loc_id = device["location"]
|
159
|
+
|
160
|
+
# Presets
|
161
|
+
data["preset_modes"] = None
|
162
|
+
data["active_preset"] = None
|
163
|
+
self._count += 2
|
164
|
+
if presets := self._presets(loc_id):
|
165
|
+
data["preset_modes"] = list(presets)
|
166
|
+
data["active_preset"] = self._preset(loc_id)
|
167
|
+
|
168
|
+
# Schedule
|
169
|
+
avail_schedules, sel_schedule = self._schedules(loc_id)
|
170
|
+
data["available_schedules"] = avail_schedules
|
171
|
+
data["select_schedule"] = sel_schedule
|
172
|
+
self._count += 2
|
173
|
+
|
174
|
+
# Operation modes: auto, heat, heat_cool, cool and off
|
175
|
+
data["mode"] = "auto"
|
176
|
+
self._count += 1
|
177
|
+
if sel_schedule == NONE:
|
178
|
+
data["mode"] = "heat"
|
179
|
+
if self._cooling_present:
|
180
|
+
data["mode"] = "cool" if self.check_reg_mode("cooling") else "heat_cool"
|
181
|
+
|
182
|
+
if self.check_reg_mode("off"):
|
183
|
+
data["mode"] = "off"
|
184
|
+
|
185
|
+
if NONE not in avail_schedules:
|
186
|
+
self._get_schedule_states_with_off(
|
187
|
+
loc_id, avail_schedules, sel_schedule, data
|
188
|
+
)
|
189
|
+
|
190
|
+
def check_reg_mode(self, mode: str) -> bool:
|
191
|
+
"""Helper-function for device_data_climate()."""
|
192
|
+
gateway = self.gw_devices[self.gateway_id]
|
193
|
+
return (
|
194
|
+
"regulation_modes" in gateway and gateway["select_regulation_mode"] == mode
|
195
|
+
)
|
196
|
+
|
197
|
+
def _get_schedule_states_with_off(
|
198
|
+
self, location: str, schedules: list[str], selected: str, data: DeviceData
|
199
|
+
) -> None:
|
200
|
+
"""Collect schedules with states for each thermostat.
|
201
|
+
|
202
|
+
Also, replace NONE by OFF when none of the schedules are active.
|
203
|
+
"""
|
204
|
+
loc_schedule_states: dict[str, str] = {}
|
205
|
+
for schedule in schedules:
|
206
|
+
loc_schedule_states[schedule] = "off"
|
207
|
+
if schedule == selected and data["mode"] == "auto":
|
208
|
+
loc_schedule_states[schedule] = "on"
|
209
|
+
self._schedule_old_states[location] = loc_schedule_states
|
210
|
+
|
211
|
+
all_off = True
|
212
|
+
for state in self._schedule_old_states[location].values():
|
213
|
+
if state == "on":
|
214
|
+
all_off = False
|
215
|
+
if all_off:
|
216
|
+
data["select_schedule"] = OFF
|
217
|
+
|
218
|
+
def _check_availability(
|
219
|
+
self, device: DeviceData, dev_class: str, data: DeviceData, message: str
|
220
|
+
) -> None:
|
221
|
+
"""Helper-function for _get_device_data().
|
222
|
+
|
223
|
+
Provide availability status for the wired-commected devices.
|
224
|
+
"""
|
225
|
+
if device["dev_class"] == dev_class:
|
226
|
+
data["available"] = True
|
227
|
+
self._count += 1
|
228
|
+
for item in self._notifications.values():
|
229
|
+
for msg in item.values():
|
230
|
+
if message in msg:
|
231
|
+
data["available"] = False
|
232
|
+
|
233
|
+
def _get_device_data(self, dev_id: str) -> DeviceData:
|
234
|
+
"""Helper-function for _all_device_data() and async_update().
|
235
|
+
|
236
|
+
Provide device-data, based on Location ID (= dev_id), from APPLIANCES.
|
237
|
+
"""
|
238
|
+
device = self.gw_devices[dev_id]
|
239
|
+
data = self._get_measurement_data(dev_id)
|
240
|
+
|
241
|
+
# Check availability of wired-connected devices
|
242
|
+
# Smartmeter
|
243
|
+
self._check_availability(
|
244
|
+
device, "smartmeter", data, "P1 does not seem to be connected"
|
245
|
+
)
|
246
|
+
# OpenTherm device
|
247
|
+
if device["name"] != "OnOff":
|
248
|
+
self._check_availability(
|
249
|
+
device, "heater_central", data, "no OpenTherm communication"
|
250
|
+
)
|
251
|
+
|
252
|
+
# Switching groups data
|
253
|
+
self._device_data_switching_group(device, data)
|
254
|
+
# Adam data
|
255
|
+
self._device_data_adam(device, data)
|
256
|
+
# Skip obtaining data for non master-thermostats
|
257
|
+
if device["dev_class"] not in ZONE_THERMOSTATS:
|
258
|
+
return data
|
259
|
+
|
260
|
+
# Thermostat data (presets, temperatures etc)
|
261
|
+
self._device_data_climate(device, data)
|
262
|
+
|
263
|
+
return data
|