PyViCare 2.56.0__tar.gz → 2.57.0__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.
Files changed (30) hide show
  1. {pyvicare-2.56.0 → pyvicare-2.57.0}/PKG-INFO +1 -1
  2. {pyvicare-2.56.0 → pyvicare-2.57.0}/PyViCare/PyViCare.py +1 -1
  3. {pyvicare-2.56.0 → pyvicare-2.57.0}/PyViCare/PyViCareAbstractOAuthManager.py +6 -0
  4. {pyvicare-2.56.0 → pyvicare-2.57.0}/PyViCare/PyViCareCachedService.py +13 -3
  5. {pyvicare-2.56.0 → pyvicare-2.57.0}/PyViCare/PyViCareDeviceConfig.py +29 -4
  6. {pyvicare-2.56.0 → pyvicare-2.57.0}/PyViCare/PyViCareHeatPump.py +36 -1
  7. {pyvicare-2.56.0 → pyvicare-2.57.0}/PyViCare/PyViCareHeatingDevice.py +10 -0
  8. {pyvicare-2.56.0 → pyvicare-2.57.0}/PyViCare/PyViCareUtils.py +12 -0
  9. {pyvicare-2.56.0 → pyvicare-2.57.0}/PyViCare/PyViCareVentilationDevice.py +16 -0
  10. {pyvicare-2.56.0 → pyvicare-2.57.0}/pyproject.toml +3 -2
  11. {pyvicare-2.56.0 → pyvicare-2.57.0}/LICENSE +0 -0
  12. {pyvicare-2.56.0 → pyvicare-2.57.0}/PyViCare/Feature.py +0 -0
  13. {pyvicare-2.56.0 → pyvicare-2.57.0}/PyViCare/PyViCareBrowserOAuthManager.py +0 -0
  14. {pyvicare-2.56.0 → pyvicare-2.57.0}/PyViCare/PyViCareDevice.py +0 -0
  15. {pyvicare-2.56.0 → pyvicare-2.57.0}/PyViCare/PyViCareElectricalEnergySystem.py +0 -0
  16. {pyvicare-2.56.0 → pyvicare-2.57.0}/PyViCare/PyViCareFloorHeating.py +0 -0
  17. {pyvicare-2.56.0 → pyvicare-2.57.0}/PyViCare/PyViCareFuelCell.py +0 -0
  18. {pyvicare-2.56.0 → pyvicare-2.57.0}/PyViCare/PyViCareGateway.py +0 -0
  19. {pyvicare-2.56.0 → pyvicare-2.57.0}/PyViCare/PyViCareGazBoiler.py +0 -0
  20. {pyvicare-2.56.0 → pyvicare-2.57.0}/PyViCare/PyViCareHeatCurveCalculation.py +0 -0
  21. {pyvicare-2.56.0 → pyvicare-2.57.0}/PyViCare/PyViCareHybrid.py +0 -0
  22. {pyvicare-2.56.0 → pyvicare-2.57.0}/PyViCare/PyViCareOAuthManager.py +0 -0
  23. {pyvicare-2.56.0 → pyvicare-2.57.0}/PyViCare/PyViCareOilBoiler.py +0 -0
  24. {pyvicare-2.56.0 → pyvicare-2.57.0}/PyViCare/PyViCarePelletsBoiler.py +0 -0
  25. {pyvicare-2.56.0 → pyvicare-2.57.0}/PyViCare/PyViCareRadiatorActuator.py +0 -0
  26. {pyvicare-2.56.0 → pyvicare-2.57.0}/PyViCare/PyViCareRepeater.py +0 -0
  27. {pyvicare-2.56.0 → pyvicare-2.57.0}/PyViCare/PyViCareRoomSensor.py +0 -0
  28. {pyvicare-2.56.0 → pyvicare-2.57.0}/PyViCare/PyViCareService.py +0 -0
  29. {pyvicare-2.56.0 → pyvicare-2.57.0}/PyViCare/__init__.py +0 -0
  30. {pyvicare-2.56.0 → pyvicare-2.57.0}/README.md +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyViCare
3
- Version: 2.56.0
3
+ Version: 2.57.0
4
4
  Summary: Library to communicate with the Viessmann ViCare API.
5
5
  License: Apache-2.0
6
6
  License-File: LICENSE
@@ -61,7 +61,7 @@ class PyViCare:
61
61
 
62
62
  logger.info("Device found: %s", device.modelId)
63
63
 
64
- yield PyViCareDeviceConfig(service, device.id, device.modelId, device.status)
64
+ yield PyViCareDeviceConfig(service, device.id, device.modelId, device.status, device.deviceType, device.roles)
65
65
 
66
66
 
67
67
  class DictWrap(object):
@@ -7,6 +7,7 @@ from authlib.integrations.requests_client import OAuth2Session
7
7
 
8
8
  from PyViCare import Feature
9
9
  from PyViCare.PyViCareUtils import (PyViCareCommandError,
10
+ PyViCareDeviceCommunicationError,
10
11
  PyViCareInternalServerError,
11
12
  PyViCareRateLimitError)
12
13
 
@@ -39,6 +40,7 @@ class AbstractViCareOAuthManager:
39
40
  logger.debug("Response to get request: %s", response)
40
41
  self.__handle_expired_token(response)
41
42
  self.__handle_rate_limit(response)
43
+ self.__handle_device_communication_error(response)
42
44
  self.__handle_server_error(response)
43
45
  return response
44
46
  except TokenExpiredError:
@@ -59,6 +61,10 @@ class AbstractViCareOAuthManager:
59
61
  if ("statusCode" in response and response["statusCode"] == 429):
60
62
  raise PyViCareRateLimitError(response)
61
63
 
64
+ def __handle_device_communication_error(self, response):
65
+ if ("errorType" in response and response["errorType"] == "DEVICE_COMMUNICATION_ERROR"):
66
+ raise PyViCareDeviceCommunicationError(response)
67
+
62
68
  def __handle_server_error(self, response):
63
69
  if ("statusCode" in response and response["statusCode"] >= 500):
64
70
  raise PyViCareInternalServerError(response)
@@ -5,7 +5,10 @@ from typing import Any, List
5
5
  from PyViCare.PyViCareAbstractOAuthManager import AbstractViCareOAuthManager
6
6
  from PyViCare.PyViCareService import (ViCareDeviceAccessor, ViCareService,
7
7
  readFeature)
8
- from PyViCare.PyViCareUtils import PyViCareInvalidDataError, ViCareTimer
8
+ from PyViCare.PyViCareUtils import (PyViCareDeviceCommunicationError,
9
+ PyViCareInvalidDataError,
10
+ PyViCareInternalServerError,
11
+ ViCareTimer)
9
12
 
10
13
  logger = logging.getLogger('ViCare')
11
14
  logger.addHandler(logging.NullHandler())
@@ -33,13 +36,20 @@ class ViCareCachedService(ViCareService):
33
36
  def __get_or_update_cache(self):
34
37
  with self.__lock:
35
38
  if self.is_cache_invalid():
36
- # we always sett the cache time before we fetch the data
39
+ # we always set the cache time before we fetch the data
37
40
  # to avoid consuming all the api calls if the api is down
38
41
  # see https://github.com/home-assistant/core/issues/67052
39
42
  # we simply return the old cache in this case
40
43
  self.__cacheTime = ViCareTimer().now()
41
44
 
42
- data = self.fetch_all_features()
45
+ try:
46
+ data = self.fetch_all_features()
47
+ except (PyViCareDeviceCommunicationError, PyViCareInternalServerError) as e:
48
+ if self.__cache is not None:
49
+ logger.warning("API error, returning stale cache: %s", e)
50
+ return self.__cache
51
+ raise
52
+
43
53
  if "data" not in data:
44
54
  logger.error("Missing 'data' property when fetching data.")
45
55
  raise PyViCareInvalidDataError(data)
@@ -22,11 +22,14 @@ logger.addHandler(logging.NullHandler())
22
22
 
23
23
 
24
24
  class PyViCareDeviceConfig:
25
- def __init__(self, service, device_id, device_model, status):
25
+ # pylint: disable=too-many-arguments,too-many-positional-arguments
26
+ def __init__(self, service, device_id, device_model, status, device_type=None, roles=None):
26
27
  self.service = service
27
28
  self.device_id = device_id
28
29
  self.device_model = device_model
29
30
  self.status = status
31
+ self.device_type = device_type
32
+ self.roles = roles if roles is not None else []
30
33
 
31
34
  def asGeneric(self):
32
35
  return HeatingDevice(self.service)
@@ -85,6 +88,15 @@ class PyViCareDeviceConfig:
85
88
  def isOnline(self):
86
89
  return self.status == "Online"
87
90
 
91
+ def getDeviceType(self):
92
+ return self.device_type
93
+
94
+ def getRoles(self):
95
+ return self.roles
96
+
97
+ def isGateway(self):
98
+ return self.service._isGateway() # pylint: disable=protected-access
99
+
88
100
  # see: https://vitodata300.viessmann.com/vd300/ApplicationHelp/VD300/1031_de_DE/Ger%C3%A4teliste.html
89
101
  def asAutoDetectDevice(self):
90
102
  device_types = [
@@ -122,12 +134,25 @@ class PyViCareDeviceConfig:
122
134
  return self.service.fetch_all_features()
123
135
 
124
136
  def dump_secure(self, flat=False):
137
+ raw_data = self.get_raw_json()
138
+ device_info = {
139
+ "id": self.device_id,
140
+ "modelId": self.device_model,
141
+ "type": self.device_type,
142
+ "status": self.status,
143
+ "roles": self.roles
144
+ }
145
+ output = {
146
+ "device": device_info,
147
+ "data": raw_data['data']
148
+ }
149
+
125
150
  if flat:
126
- inner = ',\n'.join([json.dumps(x, sort_keys=True) for x in self.get_raw_json()['data']])
127
- outer = json.dumps({'data': ['placeholder']}, indent=0)
151
+ inner = ',\n'.join([json.dumps(x, sort_keys=True) for x in output['data']])
152
+ outer = json.dumps({'device': output['device'], 'data': ['placeholder']}, indent=0, sort_keys=True)
128
153
  dumpJSON = outer.replace('"placeholder"', inner)
129
154
  else:
130
- dumpJSON = json.dumps(self.get_raw_json(), indent=4, sort_keys=True)
155
+ dumpJSON = json.dumps(output, indent=4, sort_keys=True)
131
156
 
132
157
  def repl(m):
133
158
  return m.group(1) + ('#' * len(m.group(2))) + m.group(3)
@@ -1,9 +1,11 @@
1
1
  from __future__ import annotations
2
+ from contextlib import suppress
2
3
  from typing import Any, List
3
4
  from deprecated import deprecated
4
5
 
5
6
  from PyViCare.PyViCareHeatingDevice import HeatingDevice, HeatingDeviceWithComponent
6
- from PyViCare.PyViCareUtils import handleAPICommandErrors, handleNotSupported
7
+ from PyViCare.PyViCareUtils import (PyViCareNotSupportedFeatureError,
8
+ handleAPICommandErrors, handleNotSupported)
7
9
  from PyViCare.PyViCareVentilationDevice import VentilationDevice
8
10
 
9
11
 
@@ -320,6 +322,39 @@ class HeatPump(HeatingDevice, VentilationDevice):
320
322
  def getHeatingRodPowerConsumptionTotalThisYear(self) -> float:
321
323
  return float(self.getProperty("heating.heatingRod.power.consumption.total")["properties"]["year"]["value"][0])
322
324
 
325
+ # Cooling circuits
326
+ @property
327
+ def coolingCircuits(self) -> List[CoolingCircuit]:
328
+ return [self.getCoolingCircuit(x) for x in self.getAvailableCoolingCircuits()]
329
+
330
+ def getCoolingCircuit(self, circuit) -> CoolingCircuit:
331
+ return CoolingCircuit(self, circuit)
332
+
333
+ def getAvailableCoolingCircuits(self):
334
+ """Detect available cooling circuits (0, 1, 2, etc.)."""
335
+ available = []
336
+ for circuit in ['0', '1', '2', '3']:
337
+ with suppress(KeyError, PyViCareNotSupportedFeatureError):
338
+ if self.getProperty(f"heating.coolingCircuits.{circuit}.type") is not None:
339
+ available.append(circuit)
340
+ return available
341
+
342
+
343
+ class CoolingCircuit(HeatingDeviceWithComponent):
344
+ """Cooling circuit component for heat pumps with cooling capability."""
345
+
346
+ @property
347
+ def circuit(self) -> str:
348
+ return self.component
349
+
350
+ @handleNotSupported
351
+ def getType(self) -> str:
352
+ return str(self.getProperty(f"heating.coolingCircuits.{self.circuit}.type")["properties"]["value"]["value"])
353
+
354
+ @handleNotSupported
355
+ def getReverseActive(self) -> bool:
356
+ return bool(self.getProperty(f"heating.coolingCircuits.{self.circuit}.reverse")["properties"]["active"]["value"])
357
+
323
358
 
324
359
  class Compressor(HeatingDeviceWithComponent):
325
360
 
@@ -198,6 +198,16 @@ class HeatingDevice(Device):
198
198
  return self.setProperty("heating.dhw.temperature.temp2", "setTargetTemperature",
199
199
  {"temperature": int(temperature)})
200
200
 
201
+ @handleNotSupported
202
+ def getDomesticHotWaterOperatingModes(self):
203
+ return self.getProperty("heating.dhw.operating.modes.active")[
204
+ "commands"]["setMode"]["params"]["mode"]["constraints"]["enum"]
205
+
206
+ @handleNotSupported
207
+ def getDomesticHotWaterActiveOperatingMode(self):
208
+ return self.getProperty("heating.dhw.operating.modes.active")[
209
+ "properties"]["value"]["value"]
210
+
201
211
  @handleAPICommandErrors
202
212
  def setDomesticHotWaterOperatingMode(self, mode):
203
213
  return self.setProperty("heating.dhw.operating.modes.active", "setMode",
@@ -105,6 +105,18 @@ class PyViCareInvalidDataError(Exception):
105
105
  pass
106
106
 
107
107
 
108
+ class PyViCareDeviceCommunicationError(Exception):
109
+ def __init__(self, response):
110
+ extended_payload = response.get("extendedPayload", {})
111
+ reason = extended_payload.get("reason", "Unknown")
112
+
113
+ msg = f'Device communication error. Reason: {reason}'
114
+
115
+ super().__init__(self, msg)
116
+ self.message = msg
117
+ self.reason = reason
118
+
119
+
108
120
  class PyViCareRateLimitError(Exception):
109
121
 
110
122
  def __init__(self, response):
@@ -289,3 +289,19 @@ class VentilationDevice(Device):
289
289
  @handleNotSupported
290
290
  def getExhaustVolumeFlow(self) -> int:
291
291
  return int(self.getProperty("ventilation.volumeFlow.current.output")["properties"]["value"]["value"])
292
+
293
+ @handleNotSupported
294
+ def getConfiguredLevelOneVolumeFlow(self) -> int:
295
+ return int(self.getProperty("ventilation.levels.levelOne")["properties"]["volumeFlow"]["value"])
296
+
297
+ @handleNotSupported
298
+ def getConfiguredLevelTwoVolumeFlow(self) -> int:
299
+ return int(self.getProperty("ventilation.levels.levelTwo")["properties"]["volumeFlow"]["value"])
300
+
301
+ @handleNotSupported
302
+ def getConfiguredLevelThreeVolumeFlow(self) -> int:
303
+ return int(self.getProperty("ventilation.levels.levelThree")["properties"]["volumeFlow"]["value"])
304
+
305
+ @handleNotSupported
306
+ def getConfiguredLevelFourVolumeFlow(self) -> int:
307
+ return int(self.getProperty("ventilation.levels.levelFour")["properties"]["volumeFlow"]["value"])
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "PyViCare"
3
- version = "2.56.0"
3
+ version = "2.57.0"
4
4
  description = "Library to communicate with the Viessmann ViCare API."
5
5
  authors = [
6
6
  "Simon Gillet <mail+pyvicare@gillet.ninja>"
@@ -26,6 +26,7 @@ classifiers = [
26
26
  "Programming Language :: Python :: 3.10",
27
27
  "Programming Language :: Python :: 3.11",
28
28
  "Programming Language :: Python :: 3.12",
29
+ "Programming Language :: Python :: 3.13",
29
30
  "Programming Language :: Python :: 3",
30
31
  "License :: OSI Approved :: Apache Software License",
31
32
  "Topic :: Software Development :: Libraries :: Python Modules",
@@ -51,7 +52,7 @@ mypy = "^1.11.2"
51
52
  pylint = "^4.0.0"
52
53
  pytest = "^9.0.0"
53
54
  pytest-cov = "^7.0.0"
54
- ruff = "^0.14.0"
55
+ ruff = "^0.15.0"
55
56
  types-deprecated = "^1.2.15.20241117"
56
57
  types-requests = ">=2.31"
57
58
 
File without changes
File without changes
File without changes