plugwise 0.36.2__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/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