plugwise 0.37.9__py3-none-any.whl → 0.38.1__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 CHANGED
@@ -18,7 +18,14 @@ from plugwise.constants import (
18
18
  PlugwiseData,
19
19
  ThermoLoc,
20
20
  )
21
- from plugwise.exceptions import InvalidSetupError, ResponseError, UnsupportedDeviceError
21
+ from plugwise.exceptions import (
22
+ ConnectionFailedError,
23
+ DataMissingError,
24
+ InvalidSetupError,
25
+ PlugwiseError,
26
+ ResponseError,
27
+ UnsupportedDeviceError,
28
+ )
22
29
  from plugwise.helper import SmileComm
23
30
  from plugwise.legacy.smile import SmileLegacyAPI
24
31
  from plugwise.smile import SmileAPI
@@ -141,30 +148,28 @@ class Smile(SmileComm):
141
148
  self._user,
142
149
  self._port,
143
150
  self._timeout,
144
- )
145
- if self.smile_legacy:
146
- self._smile_api = SmileLegacyAPI(
147
- self._host,
148
- self._passwd,
149
- self._websession,
150
- self._is_thermostat,
151
- self._on_off_device,
152
- self._opentherm_device,
153
- self._stretch_v2,
154
- self._target_smile,
155
- self.loc_data,
156
- self.smile_fw_version,
157
- self.smile_hostname,
158
- self.smile_hw_version,
159
- self.smile_mac_address,
160
- self.smile_model,
161
- self.smile_name,
162
- self.smile_type,
163
- self.smile_zigbee_mac_address,
164
- self._user,
165
- self._port,
166
- self._timeout,
167
- )
151
+ ) if not self.smile_legacy else SmileLegacyAPI(
152
+ self._host,
153
+ self._passwd,
154
+ self._websession,
155
+ self._is_thermostat,
156
+ self._on_off_device,
157
+ self._opentherm_device,
158
+ self._stretch_v2,
159
+ self._target_smile,
160
+ self.loc_data,
161
+ self.smile_fw_version,
162
+ self.smile_hostname,
163
+ self.smile_hw_version,
164
+ self.smile_mac_address,
165
+ self.smile_model,
166
+ self.smile_name,
167
+ self.smile_type,
168
+ self.smile_zigbee_mac_address,
169
+ self._user,
170
+ self._port,
171
+ self._timeout,
172
+ )
168
173
 
169
174
  # Update all endpoints on first connect
170
175
  await self._smile_api.full_update_device()
@@ -289,17 +294,22 @@ class Smile(SmileComm):
289
294
  return return_model
290
295
 
291
296
  async def full_update_device(self) -> None:
292
- """Perform a first fetch of all XML data, needed for initialization."""
297
+ """Helper-function used for testing."""
293
298
  await self._smile_api.full_update_device()
294
299
 
295
300
  def get_all_devices(self) -> None:
296
- """Determine the devices present from the obtained XML-data."""
301
+ """Helper-function used for testing."""
297
302
  self._smile_api.get_all_devices()
298
303
 
299
304
  async def async_update(self) -> PlugwiseData:
300
305
  """Perform an incremental update for updating the various device states."""
301
- data: PlugwiseData = await self._smile_api.async_update()
302
- self.gateway_id = data.gateway["gateway_id"]
306
+ data = PlugwiseData({}, {})
307
+ try:
308
+ data = await self._smile_api.async_update()
309
+ self.gateway_id = data.gateway["gateway_id"]
310
+ except (DataMissingError, KeyError) as err:
311
+ raise PlugwiseError("No Plugwise data received") from err
312
+
303
313
  return data
304
314
 
305
315
  ########################################################################################################
@@ -314,7 +324,10 @@ class Smile(SmileComm):
314
324
  state: str | None = None,
315
325
  ) -> None:
316
326
  """Set the selected option for the applicable Select."""
317
- await self._smile_api.set_select(key, loc_id, option, state)
327
+ try:
328
+ await self._smile_api.set_select(key, loc_id, option, state)
329
+ except ConnectionFailedError as exc:
330
+ raise ConnectionFailedError(f"Failed to set select option '{option}': {str(exc)}") from exc
318
331
 
319
332
  async def set_schedule_state(
320
333
  self,
@@ -323,15 +336,25 @@ class Smile(SmileComm):
323
336
  name: str | None = None,
324
337
  ) -> None:
325
338
  """Activate/deactivate the Schedule, with the given name, on the relevant Thermostat."""
326
- await self._smile_api.set_schedule_state(loc_id, state, name)
339
+ try:
340
+ await self._smile_api.set_schedule_state(loc_id, state, name)
341
+ except ConnectionFailedError as exc: # pragma no cover
342
+ raise ConnectionFailedError(f"Failed to set schedule state: {str(exc)}") from exc # pragma no cover
343
+
327
344
 
328
345
  async def set_preset(self, loc_id: str, preset: str) -> None:
329
346
  """Set the given Preset on the relevant Thermostat."""
330
- await self._smile_api.set_preset(loc_id, preset)
347
+ try:
348
+ await self._smile_api.set_preset(loc_id, preset)
349
+ except ConnectionFailedError as exc:
350
+ raise ConnectionFailedError(f"Failed to set preset: {str(exc)}") from exc
331
351
 
332
352
  async def set_temperature(self, loc_id: str, items: dict[str, float]) -> None:
333
353
  """Set the given Temperature on the relevant Thermostat."""
334
- await self._smile_api.set_temperature(loc_id, items)
354
+ try:
355
+ await self._smile_api.set_temperature(loc_id, items)
356
+ except ConnectionFailedError as exc:
357
+ raise ConnectionFailedError(f"Failed to set temperature: {str(exc)}") from exc
335
358
 
336
359
  async def set_number(
337
360
  self,
@@ -340,30 +363,58 @@ class Smile(SmileComm):
340
363
  temperature: float,
341
364
  ) -> None:
342
365
  """Set the maximum boiler- or DHW-setpoint on the Central Heating boiler or the temperature-offset on a Thermostat."""
343
- await self._smile_api.set_number(dev_id, key, temperature)
366
+ try:
367
+ await self._smile_api.set_number(dev_id, key, temperature)
368
+ except ConnectionFailedError as exc:
369
+ raise ConnectionFailedError(f"Failed to set number '{key}': {str(exc)}") from exc
344
370
 
345
371
  async def set_temperature_offset(self, dev_id: str, offset: float) -> None:
346
372
  """Set the Temperature offset for thermostats that support this feature."""
347
- await self._smile_api.set_offset(dev_id, offset) # pragma: no cover
373
+ try: # pragma no cover
374
+ await self._smile_api.set_offset(dev_id, offset) # pragma: no cover
375
+ except ConnectionFailedError as exc: # pragma no cover
376
+ raise ConnectionFailedError(f"Failed to set temperature offset: {str(exc)}") from exc # pragma no cover
348
377
 
349
378
  async def set_switch_state(
350
379
  self, appl_id: str, members: list[str] | None, model: str, state: str
351
380
  ) -> None:
352
381
  """Set the given State of the relevant Switch."""
353
- await self._smile_api.set_switch_state(appl_id, members, model, state)
382
+ try:
383
+ await self._smile_api.set_switch_state(appl_id, members, model, state)
384
+ except ConnectionFailedError as exc:
385
+ raise ConnectionFailedError(f"Failed to set switch state: {str(exc)}") from exc
354
386
 
355
387
  async def set_gateway_mode(self, mode: str) -> None:
356
388
  """Set the gateway mode."""
357
- await self._smile_api.set_gateway_mode(mode) # pragma: no cover
389
+ try: # pragma no cover
390
+ await self._smile_api.set_gateway_mode(mode) # pragma: no cover
391
+ except ConnectionFailedError as exc: # pragma no cover
392
+ raise ConnectionFailedError(f"Failed to set gateway mode: {str(exc)}") from exc # pragma no cover
358
393
 
359
394
  async def set_regulation_mode(self, mode: str) -> None:
360
395
  """Set the heating regulation mode."""
361
- await self._smile_api.set_regulation_mode(mode) # pragma: no cover
396
+ try: # pragma no cover
397
+ await self._smile_api.set_regulation_mode(mode) # pragma: no cover
398
+ except ConnectionFailedError as exc: # pragma no cover
399
+ raise ConnectionFailedError(f"Failed to set regulation mode: {str(exc)}") from exc # pragma no cover
362
400
 
363
401
  async def set_dhw_mode(self, mode: str) -> None:
364
402
  """Set the domestic hot water heating regulation mode."""
365
- await self._smile_api.set_dhw_mode(mode) # pragma: no cover
403
+ try: # pragma no cover
404
+ await self._smile_api.set_dhw_mode(mode) # pragma: no cover
405
+ except ConnectionFailedError as exc: # pragma no cover
406
+ raise ConnectionFailedError(f"Failed to set dhw mode: {str(exc)}") from exc # pragma no cover
366
407
 
367
408
  async def delete_notification(self) -> None:
368
409
  """Delete the active Plugwise Notification."""
369
- await self._smile_api.delete_notification()
410
+ try:
411
+ await self._smile_api.delete_notification()
412
+ except ConnectionFailedError as exc:
413
+ raise ConnectionFailedError(f"Failed to delete notification: {str(exc)}") from exc
414
+
415
+ async def reboot_gateway(self) -> None:
416
+ """Reboot the Plugwise Gateway."""
417
+ try:
418
+ await self._smile_api.reboot_gateway()
419
+ except ConnectionFailedError as exc:
420
+ raise ConnectionFailedError(f"Failed to reboot gateway: {str(exc)}") from exc
plugwise/constants.py CHANGED
@@ -17,6 +17,7 @@ DEGREE: Final = "°"
17
17
  ELECTRIC_POTENTIAL_VOLT: Final = "V"
18
18
  ENERGY_KILO_WATT_HOUR: Final = "kWh"
19
19
  ENERGY_WATT_HOUR: Final = "Wh"
20
+ GATEWAY_REBOOT: Final = "/core/gateways;@reboot"
20
21
  PERCENTAGE: Final = "%"
21
22
  POWER_WATT: Final = "W"
22
23
  PRESET_AWAY: Final = "away"
@@ -388,6 +389,7 @@ class GatewayData(TypedDict, total=False):
388
389
  heater_id: str
389
390
  item_count: int
390
391
  notifications: dict[str, dict[str, str]]
392
+ reboot: bool
391
393
  smile_name: str
392
394
 
393
395
 
plugwise/data.py CHANGED
@@ -38,6 +38,7 @@ class SmileData(SmileHelper):
38
38
  "gateway_id": self.gateway_id,
39
39
  "item_count": self._count,
40
40
  "notifications": self._notifications,
41
+ "reboot": True,
41
42
  "smile_name": self.smile_name,
42
43
  }
43
44
  )
plugwise/exceptions.py CHANGED
@@ -9,6 +9,10 @@ class ConnectionFailedError(PlugwiseException):
9
9
  """Raised when unable to connect."""
10
10
 
11
11
 
12
+ class DataMissingError(PlugwiseException):
13
+ """Raised when expected data is missing."""
14
+
15
+
12
16
  class InvalidAuthentication(PlugwiseException):
13
17
  """Raised when unable to authenticate."""
14
18
 
@@ -29,14 +33,6 @@ class DeviceSetupError(PlugwiseException):
29
33
  """Raised when device is missing critical setup data."""
30
34
 
31
35
 
32
- class DeviceTimeoutError(PlugwiseException):
33
- """Raised when device is not supported."""
34
-
35
-
36
- class ErrorSendingCommandError(PlugwiseException):
37
- """Raised when device is not accepting the command."""
38
-
39
-
40
36
  class ResponseError(PlugwiseException):
41
37
  """Raised when empty or error in response returned."""
42
38
 
plugwise/helper.py CHANGED
@@ -115,22 +115,31 @@ class SmileComm:
115
115
  use_headers = headers
116
116
 
117
117
  try:
118
- if method == "delete":
119
- resp = await self._websession.delete(url, auth=self._auth)
120
- if method == "get":
121
- # Work-around for Stretchv2, should not hurt the other smiles
122
- use_headers = {"Accept-Encoding": "gzip"}
123
- resp = await self._websession.get(
124
- url, headers=use_headers, auth=self._auth
125
- )
126
- if method == "put":
127
- use_headers = {"Content-type": "text/xml"}
128
- resp = await self._websession.put(
129
- url,
130
- headers=use_headers,
131
- data=data,
132
- auth=self._auth,
133
- )
118
+ match method:
119
+ case "delete":
120
+ resp = await self._websession.delete(url, auth=self._auth)
121
+ case "get":
122
+ # Work-around for Stretchv2, should not hurt the other smiles
123
+ use_headers = {"Accept-Encoding": "gzip"}
124
+ resp = await self._websession.get(
125
+ url, headers=use_headers, auth=self._auth
126
+ )
127
+ case "post":
128
+ use_headers = {"Content-type": "text/xml"}
129
+ resp = await self._websession.post(
130
+ url,
131
+ headers=use_headers,
132
+ data=data,
133
+ auth=self._auth,
134
+ )
135
+ case "put":
136
+ use_headers = {"Content-type": "text/xml"}
137
+ resp = await self._websession.put(
138
+ url,
139
+ headers=use_headers,
140
+ data=data,
141
+ auth=self._auth,
142
+ )
134
143
  except (
135
144
  ClientError
136
145
  ) as exc: # ClientError is an ancestor class of ServerTimeoutError
@@ -144,24 +153,41 @@ class SmileComm:
144
153
  raise ConnectionFailedError from exc
145
154
  return await self._request(command, retry - 1)
146
155
 
156
+ if resp.status == 504:
157
+ if retry < 1:
158
+ LOGGER.warning(
159
+ "Failed sending %s %s to Plugwise Smile, error: %s",
160
+ method,
161
+ command,
162
+ "504 Gateway Timeout",
163
+ )
164
+ raise ConnectionFailedError
165
+ return await self._request(command, retry - 1)
166
+
147
167
  return await self._request_validate(resp, method)
148
168
 
149
169
  async def _request_validate(self, resp: ClientResponse, method: str) -> etree:
150
170
  """Helper-function for _request(): validate the returned data."""
151
- # Command accepted gives empty body with status 202
152
- if resp.status == 202:
153
- return
154
-
155
- # Cornercase for stretch not responding with 202
156
- if method == "put" and resp.status == 200:
157
- return
158
-
159
- if resp.status == 401:
160
- msg = "Invalid Plugwise login, please retry with the correct credentials."
161
- LOGGER.error("%s", msg)
162
- raise InvalidAuthentication
163
-
164
- if not (result := await resp.text()) or "<error>" in result:
171
+ match resp.status:
172
+ case 200:
173
+ # Cornercases for server not responding with 202
174
+ if method in ("post", "put"):
175
+ return
176
+ case 202:
177
+ # Command accepted gives empty body with status 202
178
+ return
179
+ case 401:
180
+ msg = "Invalid Plugwise login, please retry with the correct credentials."
181
+ LOGGER.error("%s", msg)
182
+ raise InvalidAuthentication
183
+ case 405:
184
+ msg = "405 Method not allowed."
185
+ LOGGER.error("%s", msg)
186
+ raise ConnectionFailedError
187
+
188
+ if not (result := await resp.text()) or (
189
+ "<error>" in result and "Not started" not in result
190
+ ):
165
191
  LOGGER.warning("Smile response empty or error in %s", result)
166
192
  raise ResponseError
167
193
 
plugwise/legacy/smile.py CHANGED
@@ -5,6 +5,7 @@ Plugwise backend module for Home Assistant Core - covering the legacy P1, Anna,
5
5
  from __future__ import annotations
6
6
 
7
7
  import datetime as dt
8
+ from typing import Any
8
9
 
9
10
  from plugwise.constants import (
10
11
  APPLIANCES,
@@ -23,7 +24,7 @@ from plugwise.constants import (
23
24
  PlugwiseData,
24
25
  ThermoLoc,
25
26
  )
26
- from plugwise.exceptions import PlugwiseError
27
+ from plugwise.exceptions import ConnectionFailedError, PlugwiseError
27
28
  from plugwise.helper import SmileComm
28
29
  from plugwise.legacy.data import SmileLegacyData
29
30
 
@@ -149,6 +150,9 @@ class SmileLegacyAPI(SmileComm, SmileLegacyData):
149
150
  async def delete_notification(self) -> None:
150
151
  """Set-function placeholder for legacy devices."""
151
152
 
153
+ async def reboot_gateway(self) -> None:
154
+ """Set-function placeholder for legacy devices."""
155
+
152
156
  async def set_dhw_mode(self, mode: str) -> None:
153
157
  """Set-function placeholder for legacy devices."""
154
158
 
@@ -177,7 +181,7 @@ class SmileLegacyAPI(SmileComm, SmileLegacyData):
177
181
  rule = self._domain_objects.find(locator)
178
182
  data = f'<rules><rule id="{rule.attrib["id"]}"><active>true</active></rule></rules>'
179
183
 
180
- await self._request(RULES, method="put", data=data)
184
+ await self.call_request(RULES, method="put", data=data)
181
185
 
182
186
  async def set_regulation_mode(self, mode: str) -> None:
183
187
  """Set-function placeholder for legacy devices."""
@@ -223,7 +227,7 @@ class SmileLegacyAPI(SmileComm, SmileLegacyData):
223
227
  f' id="{template_id}" /><active>{new_state}</active></rule></rules>'
224
228
  )
225
229
 
226
- await self._request(uri, method="put", data=data)
230
+ await self.call_request(uri, method="put", data=data)
227
231
 
228
232
  async def set_switch_state(
229
233
  self, appl_id: str, members: list[str] | None, model: str, state: str
@@ -251,7 +255,7 @@ class SmileLegacyAPI(SmileComm, SmileLegacyData):
251
255
  if self._appliances.find(locator).text == "true":
252
256
  raise PlugwiseError("Plugwise: the locked Relay was not switched.")
253
257
 
254
- await self._request(uri, method="put", data=data)
258
+ await self.call_request(uri, method="put", data=data)
255
259
 
256
260
  async def _set_groupswitch_member_state(
257
261
  self, members: list[str], state: str, switch: Munch
@@ -264,7 +268,7 @@ class SmileLegacyAPI(SmileComm, SmileLegacyData):
264
268
  uri = f"{APPLIANCES};id={member}/{switch.func_type}"
265
269
  data = f"<{switch.func_type}><{switch.func}>{state}</{switch.func}></{switch.func_type}>"
266
270
 
267
- await self._request(uri, method="put", data=data)
271
+ await self.call_request(uri, method="put", data=data)
268
272
 
269
273
  async def set_temperature(self, _: str, items: dict[str, float]) -> None:
270
274
  """Set the given Temperature on the relevant Thermostat."""
@@ -284,4 +288,13 @@ class SmileLegacyAPI(SmileComm, SmileLegacyData):
284
288
  f"{temperature}</setpoint></thermostat_functionality>"
285
289
  )
286
290
 
287
- await self._request(uri, method="put", data=data)
291
+ await self.call_request(uri, method="put", data=data)
292
+
293
+ async def call_request(self, uri: str, **kwargs: Any) -> None:
294
+ """ConnectionFailedError wrapper for calling _request()."""
295
+ method: str = kwargs["method"]
296
+ data: str | None = kwargs.get("data")
297
+ try:
298
+ await self._request(uri, method=method, data=data)
299
+ except ConnectionFailedError as exc:
300
+ raise ConnectionFailedError from exc
plugwise/smile.py CHANGED
@@ -5,6 +5,7 @@ Plugwise backend module for Home Assistant Core.
5
5
  from __future__ import annotations
6
6
 
7
7
  import datetime as dt
8
+ from typing import Any
8
9
 
9
10
  from plugwise.constants import (
10
11
  ADAM,
@@ -14,6 +15,7 @@ from plugwise.constants import (
14
15
  DEFAULT_TIMEOUT,
15
16
  DEFAULT_USERNAME,
16
17
  DOMAIN_OBJECTS,
18
+ GATEWAY_REBOOT,
17
19
  LOCATIONS,
18
20
  MAX_SETPOINT,
19
21
  MIN_SETPOINT,
@@ -26,7 +28,7 @@ from plugwise.constants import (
26
28
  ThermoLoc,
27
29
  )
28
30
  from plugwise.data import SmileData
29
- from plugwise.exceptions import PlugwiseError
31
+ from plugwise.exceptions import ConnectionFailedError, DataMissingError, PlugwiseError
30
32
  from plugwise.helper import SmileComm
31
33
 
32
34
  import aiohttp
@@ -131,13 +133,15 @@ class SmileAPI(SmileComm, SmileData):
131
133
  # Perform a full update at day-change
132
134
  self.gw_data: GatewayData = {}
133
135
  self.gw_devices: dict[str, DeviceData] = {}
134
- await self.full_update_device()
135
- self.get_all_devices()
136
-
137
- if "heater_id" in self.gw_data:
138
- self._heater_id = self.gw_data["heater_id"]
139
- if "cooling_enabled" in self.gw_devices[self._heater_id]["binary_sensors"]:
140
- self._cooling_enabled = self.gw_devices[self._heater_id]["binary_sensors"]["cooling_enabled"]
136
+ try:
137
+ await self.full_update_device()
138
+ self.get_all_devices()
139
+ if "heater_id" in self.gw_data:
140
+ self._heater_id = self.gw_data["heater_id"]
141
+ if "cooling_enabled" in self.gw_devices[self._heater_id]["binary_sensors"]:
142
+ self._cooling_enabled = self.gw_devices[self._heater_id]["binary_sensors"]["cooling_enabled"]
143
+ except KeyError as err:
144
+ raise DataMissingError("No Plugwise data received") from err
141
145
 
142
146
  return PlugwiseData(self.gw_data, self.gw_devices)
143
147
 
@@ -147,7 +151,11 @@ class SmileAPI(SmileComm, SmileData):
147
151
 
148
152
  async def delete_notification(self) -> None:
149
153
  """Delete the active Plugwise Notification."""
150
- await self._request(NOTIFICATIONS, method="delete")
154
+ await self.call_request(NOTIFICATIONS, method="delete")
155
+
156
+ async def reboot_gateway(self) -> None:
157
+ """Reboot the Gateway."""
158
+ await self.call_request(GATEWAY_REBOOT, method="post")
151
159
 
152
160
  async def set_number(
153
161
  self,
@@ -173,7 +181,7 @@ class SmileAPI(SmileComm, SmileData):
173
181
 
174
182
  uri = f"{APPLIANCES};id={self._heater_id}/thermostat;id={thermostat_id}"
175
183
  data = f"<thermostat_functionality><setpoint>{temp}</setpoint></thermostat_functionality>"
176
- await self._request(uri, method="put", data=data)
184
+ await self.call_request(uri, method="put", data=data)
177
185
 
178
186
  async def set_offset(self, dev_id: str, offset: float) -> None:
179
187
  """Set the Temperature offset for thermostats that support this feature."""
@@ -186,7 +194,7 @@ class SmileAPI(SmileComm, SmileData):
186
194
  uri = f"{APPLIANCES};id={dev_id}/offset;type=temperature_offset"
187
195
  data = f"<offset_functionality><offset>{value}</offset></offset_functionality>"
188
196
 
189
- await self._request(uri, method="put", data=data)
197
+ await self.call_request(uri, method="put", data=data)
190
198
 
191
199
  async def set_preset(self, loc_id: str, preset: str) -> None:
192
200
  """Set the given Preset on the relevant Thermostat - from LOCATIONS."""
@@ -206,7 +214,7 @@ class SmileAPI(SmileComm, SmileData):
206
214
  f"</type><preset>{preset}</preset></location></locations>"
207
215
  )
208
216
 
209
- await self._request(uri, method="put", data=data)
217
+ await self.call_request(uri, method="put", data=data)
210
218
 
211
219
  async def set_select(self, key: str, loc_id: str, option: str, state: str | None) -> None:
212
220
  """Set a dhw/gateway/regulation mode or the thermostat schedule option."""
@@ -229,7 +237,7 @@ class SmileAPI(SmileComm, SmileData):
229
237
  uri = f"{APPLIANCES};type=heater_central/domestic_hot_water_mode_control"
230
238
  data = f"<domestic_hot_water_mode_control_functionality><mode>{mode}</mode></domestic_hot_water_mode_control_functionality>"
231
239
 
232
- await self._request(uri, method="put", data=data)
240
+ await self.call_request(uri, method="put", data=data)
233
241
 
234
242
  async def set_gateway_mode(self, mode: str) -> None:
235
243
  """Set the gateway mode."""
@@ -252,7 +260,7 @@ class SmileAPI(SmileComm, SmileData):
252
260
  uri = f"{APPLIANCES};id={self.gateway_id}/gateway_mode_control"
253
261
  data = f"<gateway_mode_control_functionality><mode>{mode}</mode>{valid}</gateway_mode_control_functionality>"
254
262
 
255
- await self._request(uri, method="put", data=data)
263
+ await self.call_request(uri, method="put", data=data)
256
264
 
257
265
  async def set_regulation_mode(self, mode: str) -> None:
258
266
  """Set the heating regulation mode."""
@@ -265,7 +273,7 @@ class SmileAPI(SmileComm, SmileData):
265
273
  duration = "<duration>300</duration>"
266
274
  data = f"<regulation_mode_control_functionality>{duration}<mode>{mode}</mode></regulation_mode_control_functionality>"
267
275
 
268
- await self._request(uri, method="put", data=data)
276
+ await self.call_request(uri, method="put", data=data)
269
277
 
270
278
  async def set_schedule_state(
271
279
  self,
@@ -319,7 +327,7 @@ class SmileAPI(SmileComm, SmileData):
319
327
  f"{template}{contexts}</rule></rules>"
320
328
  )
321
329
 
322
- await self._request(uri, method="put", data=data)
330
+ await self.call_request(uri, method="put", data=data)
323
331
  self._schedule_old_states[loc_id][name] = new_state
324
332
 
325
333
  def determine_contexts(
@@ -388,7 +396,7 @@ class SmileAPI(SmileComm, SmileData):
388
396
  if self._domain_objects.find(locator).text == "true":
389
397
  raise PlugwiseError("Plugwise: the locked Relay was not switched.")
390
398
 
391
- await self._request(uri, method="put", data=data)
399
+ await self.call_request(uri, method="put", data=data)
392
400
 
393
401
  async def _set_groupswitch_member_state(
394
402
  self, members: list[str], state: str, switch: Munch
@@ -403,7 +411,7 @@ class SmileAPI(SmileComm, SmileData):
403
411
  uri = f"{APPLIANCES};id={member}/{switch.device};id={switch_id}"
404
412
  data = f"<{switch.func_type}><{switch.func}>{state}</{switch.func}></{switch.func_type}>"
405
413
 
406
- await self._request(uri, method="put", data=data)
414
+ await self.call_request(uri, method="put", data=data)
407
415
 
408
416
  async def set_temperature(self, loc_id: str, items: dict[str, float]) -> None:
409
417
  """Set the given Temperature on the relevant Thermostat."""
@@ -444,4 +452,13 @@ class SmileAPI(SmileComm, SmileData):
444
452
  f"{temperature}</setpoint></thermostat_functionality>"
445
453
  )
446
454
 
447
- await self._request(uri, method="put", data=data)
455
+ await self.call_request(uri, method="put", data=data)
456
+
457
+ async def call_request(self, uri: str, **kwargs: Any) -> None:
458
+ """ConnectionFailedError wrapper for calling _request()."""
459
+ method: str = kwargs["method"]
460
+ data: str | None = kwargs.get("data")
461
+ try:
462
+ await self._request(uri, method=method, data=data)
463
+ except ConnectionFailedError as exc:
464
+ raise ConnectionFailedError from exc
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: plugwise
3
- Version: 0.37.9
3
+ Version: 0.38.1
4
4
  Summary: Plugwise Smile (Adam/Anna/P1) and Stretch module for Python 3.
5
5
  Home-page: https://github.com/plugwise/python-plugwise
6
6
  Author: Plugwise device owners
@@ -54,12 +54,13 @@ This module supports `Smile`s (and `Stretch`), i.e. the networked plugwise devic
54
54
  Our main usage for this module is supporting [Home Assistant](https://www.home-assistant.io) / [home-assistant](http://github.com/home-assistant/core/)
55
55
 
56
56
  [![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-green.svg)](https://github.com/plugwise)
57
- [![PyPI version fury.io](https://badge.fury.io/py/plugwise.svg)](https://pypi.python.org/pypi/plugwise/)
57
+ [![CodeRabbit.ai is Awesome](https://img.shields.io/badge/AI-orange?label=CodeRabbit&color=orange&link=https%3A%2F%2Fcoderabbit.ai)](https://coderabbit.ai)
58
+ [![renovate maintained](https://img.shields.io/badge/maintained%20with-renovate-blue?logo=renovatebot)](https://github.com/plugwise/python-plugwise/issues/291)
58
59
 
60
+ [![PyPI version fury.io](https://badge.fury.io/py/plugwise.svg)](https://pypi.python.org/pypi/plugwise/)
59
61
  [![Latest release](https://github.com/plugwise/python-plugwise/workflows/Latest%20release/badge.svg)](https://github.com/plugwise/python-plugwise/actions)
60
62
  [![Newest commit](https://github.com/plugwise/python-plugwise/workflows/Latest%20commit/badge.svg)](https://github.com/plugwise/python-plugwise/actions)
61
63
  [![pre-commit.ci status](https://results.pre-commit.ci/badge/github/plugwise/python-plugwise/main.svg)](https://results.pre-commit.ci/latest/github/plugwise/python-plugwise/main)
62
- [![renovate maintained](https://img.shields.io/badge/maintained%20with-renovate-blue?logo=renovatebot)](https://github.com/plugwise/python-plugwise/issues/291)
63
64
 
64
65
  [![CodeFactor](https://www.codefactor.io/repository/github/plugwise/python-plugwise/badge)](https://www.codefactor.io/repository/github/plugwise/python-plugwise)
65
66
  [![codecov](https://codecov.io/gh/plugwise/python-plugwise/branch/main/graph/badge.svg)](https://codecov.io/gh/plugwise/python-plugwise)
@@ -94,9 +95,9 @@ See the [`plugwise-beta`](https://github.com/plugwise/plugwise-beta) repository
94
95
 
95
96
  ## Development/patches
96
97
 
97
- Like Home Assistant Core we use `pre-commit` and additionally run [pre-commit.ci](http://pre-commit.ci) to automatically validate your commits and PRs.
98
+ Like Home Assistant Core, we use `pre-commit` and additionally run [pre-commit.ci](http://pre-commit.ci) to automatically validate your commits and PRs.
98
99
 
99
- If you want to create a PR please make sure you at least run `scripts/setup.sh`. This will ensure your environment is set up correctly before attempting to `git commit`. We sincerely and highly recommended also setting up local testing, see [`tests/README.md`](https://github.com/plugwise/python-plugwise/blob/main/tests/README.md) for more information and run `scripts/setup_test.sh` to prepare your environment.
100
+ If you want to create a PR, please ensure you at least run `scripts/setup.sh`. This will ensure your environment is set up correctly before attempting to `git commit`. We sincerely and highly recommended also setting up local testing, see [`tests/README.md`](https://github.com/plugwise/python-plugwise/blob/main/tests/README.md) for more information and run `scripts/setup_test.sh` to prepare your environment.
100
101
 
101
102
  ## Project support status
102
103
 
@@ -125,13 +126,13 @@ Module providing interfacing with the Plugwise devices:
125
126
 
126
127
  ## License, origins and contributors
127
128
 
128
- As per the origins we have retained the appropriate licensing and including the MIT-license for this project.
129
+ As per the origins, we have retained the appropriate licensing and including the MIT-license for this project.
129
130
 
130
131
  Origins (from newest to oldest):
131
132
 
132
133
  - Networked (Smile/Stretch) Plugwise support by @bouwew (Bouwe) and @CoMPaTech (Tom). We both support and help out @brefra (Frank) where possible, he's supporting the USB module and integration.
133
134
  - 'All' available Plugwise support by @bouwew (Bouwe), @brefra (Frank) and @CoMPaTech (Tom)
134
- - Upstreamed haanna/HA-core Anna, including all later products - 'Plugwise-Smile/Plugwise-HA/plugwise-beta` by @bouwew (Bouwe) & @CoMPaTech (Tom)
135
+ - Upstreamed haanna/HA-core Anna, including all later products - 'Plugwise-Smile/Plugwise-HA/plugwise-beta' by @bouwew (Bouwe) & @CoMPaTech (Tom)
135
136
  - Networked Plugwise Anna module with custom_module - `haanna/anna-ha` via <https://github.com/laetificat> (Kevin)
136
137
  - USB-based stick module with custom_module - `plugwise-stick/plugwise` by @brefra (Frank)
137
138
  - USB-plugwise module - `plugwise` by <https://github.com/cyberjunky/python-plugwise> (Ron) originally by <https://github.com/aequitas/python-plugwise> (Johan) (with reference only in license to Sven)
@@ -139,4 +140,4 @@ Origins (from newest to oldest):
139
140
 
140
141
  ## Thanks
141
142
 
142
- On behalf all of us, big thanks to Plugwise and community members @riemers and @tane from [HAshop](https://hashop.nl) for their support and obviously all our users and testers who dealt with our typos and challenges. Disclaimer, while we are communicating with Plugwise and they expressed their gratitude through their newsletter, we are not part of Plugwise as a company. We are just a bunch of guys anxious to get our (and your) Plugwise products working with Home Assistant.
143
+ On behalf of all of us, big thanks to Plugwise and community members @riemers and @tane from [HAshop](https://hashop.nl) for their support and obviously all our users and testers who dealt with our typos and challenges. Disclaimer, while we are communicating with Plugwise and they expressed their gratitude through their newsletter, we are not part of Plugwise as a company. We are just a bunch of guys anxious to get our (and your) Plugwise products working with Home Assistant.
@@ -0,0 +1,17 @@
1
+ plugwise/__init__.py,sha256=zo6whZ4QeZxymEWO_nQ2A4d_XpaXExjKJWZjJzaAxwk,16773
2
+ plugwise/common.py,sha256=P4sUYzgVcFsIR2DmQxeVeOiZvFZWpZXgwHA3XRc1Sx0,12538
3
+ plugwise/constants.py,sha256=aP2ifDFIRuzYzuhJk1cOdqN84yR135eqCzAATrhxMo4,16646
4
+ plugwise/data.py,sha256=HA3OoLrTad4ytns6_rfygwu8eGfopHJBNADGs-hvaQk,9054
5
+ plugwise/exceptions.py,sha256=Ce-tO9uNsMB-8FP6VAxBvsHNJ-NIM9F0onUZOdZI4Ys,1110
6
+ plugwise/helper.py,sha256=NFcxVtY9qdM2TtBbGs-_eh7xKO6G_M3Q4W9bXUCpH84,43861
7
+ plugwise/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ plugwise/smile.py,sha256=mPQsq4qjxZ9cpUmGi_FvYrguj7GGoeqfSQjQNmq5ybU,18617
9
+ plugwise/util.py,sha256=9ld46KJHWFze2eUrVSgUYn0g3zNerlpboM0iUa0H3ak,7830
10
+ plugwise/legacy/data.py,sha256=DsHR9xgiFDg_Vh_6ZpOskw8ZhNQ3CmwjstI3yiH6MEk,3048
11
+ plugwise/legacy/helper.py,sha256=6-tYQMEXepE5rec-hn6lt2EeknADI3J8UFuBSLgu8dk,17878
12
+ plugwise/legacy/smile.py,sha256=E5Ub5TtQcfcbB7AUVf1zwxonpkGuMHELITf5td5HEXw,11321
13
+ plugwise-0.38.1.dist-info/LICENSE,sha256=mL22BjmXtg_wnoDnnaqps5_Bg_VGj_yHueX5lsKwbCc,1144
14
+ plugwise-0.38.1.dist-info/METADATA,sha256=moQq3lGIMmw36rfewCiQB_GCyolvSkUu4WZs-QJUNMs,9098
15
+ plugwise-0.38.1.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
16
+ plugwise-0.38.1.dist-info/top_level.txt,sha256=MYOmktMFf8ZmX6_OE1y9MoCZFfY-L8DA0F2tA2IvE4s,9
17
+ plugwise-0.38.1.dist-info/RECORD,,
@@ -1,17 +0,0 @@
1
- plugwise/__init__.py,sha256=mI2mFZWucme1PJbsZssV7L7957nQot_LVS-ZeIcE-80,14427
2
- plugwise/common.py,sha256=P4sUYzgVcFsIR2DmQxeVeOiZvFZWpZXgwHA3XRc1Sx0,12538
3
- plugwise/constants.py,sha256=dvafW3u7LBbWUN05D1Tss7s4jxEDJiZDqnh4-q9isUM,16580
4
- plugwise/data.py,sha256=9sH2FkwGh5Uvbf42LmZPWutY9Qa-j9qmFZBobwJkLQo,9022
5
- plugwise/exceptions.py,sha256=rymGtWnXosdFkzSehc_41HVl2jXJEg_9Hq9APDEUwrk,1223
6
- plugwise/helper.py,sha256=nF1xJEw-frvuGMAydFAJrz_tN24Q3PcBnYNIwPeueZg,42817
7
- plugwise/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- plugwise/smile.py,sha256=tzE6yxWd8oOO3pK0g2bTw8WcuVQ9tM7tG3p0z7-LqWU,17815
9
- plugwise/util.py,sha256=9ld46KJHWFze2eUrVSgUYn0g3zNerlpboM0iUa0H3ak,7830
10
- plugwise/legacy/data.py,sha256=DsHR9xgiFDg_Vh_6ZpOskw8ZhNQ3CmwjstI3yiH6MEk,3048
11
- plugwise/legacy/helper.py,sha256=6-tYQMEXepE5rec-hn6lt2EeknADI3J8UFuBSLgu8dk,17878
12
- plugwise/legacy/smile.py,sha256=kU6eOh0c4Ao_S0yOJeggDuicRR7r3gScjYCEGJTMowA,10760
13
- plugwise-0.37.9.dist-info/LICENSE,sha256=mL22BjmXtg_wnoDnnaqps5_Bg_VGj_yHueX5lsKwbCc,1144
14
- plugwise-0.37.9.dist-info/METADATA,sha256=imJmtku4hd0Dnxjf7QKpc9BOUPNCNTfuQhjwVhj6FZc,8939
15
- plugwise-0.37.9.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
16
- plugwise-0.37.9.dist-info/top_level.txt,sha256=MYOmktMFf8ZmX6_OE1y9MoCZFfY-L8DA0F2tA2IvE4s,9
17
- plugwise-0.37.9.dist-info/RECORD,,