PyViCare 2.56.0__tar.gz → 2.58.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.58.0}/PKG-INFO +1 -1
  2. {pyvicare-2.56.0 → pyvicare-2.58.0}/PyViCare/PyViCare.py +1 -1
  3. {pyvicare-2.56.0 → pyvicare-2.58.0}/PyViCare/PyViCareAbstractOAuthManager.py +6 -0
  4. {pyvicare-2.56.0 → pyvicare-2.58.0}/PyViCare/PyViCareCachedService.py +13 -3
  5. {pyvicare-2.56.0 → pyvicare-2.58.0}/PyViCare/PyViCareDeviceConfig.py +29 -4
  6. {pyvicare-2.56.0 → pyvicare-2.58.0}/PyViCare/PyViCareFuelCell.py +20 -0
  7. {pyvicare-2.56.0 → pyvicare-2.58.0}/PyViCare/PyViCareGazBoiler.py +46 -1
  8. {pyvicare-2.56.0 → pyvicare-2.58.0}/PyViCare/PyViCareHeatPump.py +296 -14
  9. {pyvicare-2.56.0 → pyvicare-2.58.0}/PyViCare/PyViCareHeatingDevice.py +28 -0
  10. {pyvicare-2.56.0 → pyvicare-2.58.0}/PyViCare/PyViCareUtils.py +12 -0
  11. {pyvicare-2.56.0 → pyvicare-2.58.0}/PyViCare/PyViCareVentilationDevice.py +16 -0
  12. {pyvicare-2.56.0 → pyvicare-2.58.0}/pyproject.toml +3 -2
  13. {pyvicare-2.56.0 → pyvicare-2.58.0}/LICENSE +0 -0
  14. {pyvicare-2.56.0 → pyvicare-2.58.0}/PyViCare/Feature.py +0 -0
  15. {pyvicare-2.56.0 → pyvicare-2.58.0}/PyViCare/PyViCareBrowserOAuthManager.py +0 -0
  16. {pyvicare-2.56.0 → pyvicare-2.58.0}/PyViCare/PyViCareDevice.py +0 -0
  17. {pyvicare-2.56.0 → pyvicare-2.58.0}/PyViCare/PyViCareElectricalEnergySystem.py +0 -0
  18. {pyvicare-2.56.0 → pyvicare-2.58.0}/PyViCare/PyViCareFloorHeating.py +0 -0
  19. {pyvicare-2.56.0 → pyvicare-2.58.0}/PyViCare/PyViCareGateway.py +0 -0
  20. {pyvicare-2.56.0 → pyvicare-2.58.0}/PyViCare/PyViCareHeatCurveCalculation.py +0 -0
  21. {pyvicare-2.56.0 → pyvicare-2.58.0}/PyViCare/PyViCareHybrid.py +0 -0
  22. {pyvicare-2.56.0 → pyvicare-2.58.0}/PyViCare/PyViCareOAuthManager.py +0 -0
  23. {pyvicare-2.56.0 → pyvicare-2.58.0}/PyViCare/PyViCareOilBoiler.py +0 -0
  24. {pyvicare-2.56.0 → pyvicare-2.58.0}/PyViCare/PyViCarePelletsBoiler.py +0 -0
  25. {pyvicare-2.56.0 → pyvicare-2.58.0}/PyViCare/PyViCareRadiatorActuator.py +0 -0
  26. {pyvicare-2.56.0 → pyvicare-2.58.0}/PyViCare/PyViCareRepeater.py +0 -0
  27. {pyvicare-2.56.0 → pyvicare-2.58.0}/PyViCare/PyViCareRoomSensor.py +0 -0
  28. {pyvicare-2.56.0 → pyvicare-2.58.0}/PyViCare/PyViCareService.py +0 -0
  29. {pyvicare-2.56.0 → pyvicare-2.58.0}/PyViCare/__init__.py +0 -0
  30. {pyvicare-2.56.0 → pyvicare-2.58.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.58.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)
@@ -23,6 +23,7 @@ class FuelCell(HeatingDevice):
23
23
  def getReturnTemperature(self):
24
24
  return self.getProperty("heating.sensors.temperature.return")["properties"]["value"]["value"]
25
25
 
26
+ # Total power consumption:
26
27
  @handleNotSupported
27
28
  def getPowerConsumptionUnit(self):
28
29
  return self.getProperty("heating.power.consumption.total")["properties"]["day"]["unit"]
@@ -59,6 +60,7 @@ class FuelCell(HeatingDevice):
59
60
  def getPowerConsumptionThisYear(self):
60
61
  return self.getProperty('heating.power.consumption.total')['properties']['year']['value'][0]
61
62
 
63
+ # Power consumption for Heating:
62
64
  @handleNotSupported
63
65
  def getPowerConsumptionHeatingUnit(self):
64
66
  return self.getProperty("heating.power.consumption.heating")["properties"]["day"]["unit"]
@@ -95,6 +97,24 @@ class FuelCell(HeatingDevice):
95
97
  def getPowerConsumptionHeatingThisYear(self):
96
98
  return self.getProperty('heating.power.consumption.heating')['properties']['year']['value'][0]
97
99
 
100
+ # Power consumption for Domestic Hot Water:
101
+ @handleNotSupported
102
+ def getPowerConsumptionDomesticHotWaterUnit(self):
103
+ return self.getProperty("heating.power.consumption.dhw")["properties"]["day"]["unit"]
104
+
105
+ @handleNotSupported
106
+ def getPowerConsumptionDomesticHotWaterToday(self):
107
+ return self.getProperty("heating.power.consumption.dhw")["properties"]["day"]["value"][0]
108
+
109
+ @handleNotSupported
110
+ def getPowerConsumptionDomesticHotWaterThisMonth(self):
111
+ return self.getProperty("heating.power.consumption.dhw")["properties"]["month"]["value"][0]
112
+
113
+ @handleNotSupported
114
+ def getPowerConsumptionDomesticHotWaterThisYear(self):
115
+ return self.getProperty("heating.power.consumption.dhw")["properties"]["year"]["value"][0]
116
+
117
+ # Gas consumption:
98
118
  @handleNotSupported
99
119
  def getGasConsumptionUnit(self):
100
120
  return self.getProperty("heating.gas.consumption.total")["properties"]["day"]["unit"]
@@ -103,9 +103,13 @@ class GazBoiler(HeatingDevice):
103
103
  def getDomesticHotWaterChargingLevel(self):
104
104
  return self.getProperty("heating.dhw.charging.level")["properties"]["value"]["value"]
105
105
 
106
+ # Total power consumption:
106
107
  @handleNotSupported
107
108
  def getPowerConsumptionUnit(self):
108
- return self.getProperty("heating.power.consumption.total")["properties"]["day"]["unit"]
109
+ try :
110
+ return self.getProperty("heating.power.consumption.total")["properties"]["day"]["unit"]
111
+ except KeyError:
112
+ return self.getProperty("heating.power.consumption.total")["properties"]["unit"]["value"]
109
113
 
110
114
  @handleNotSupported
111
115
  def getPowerConsumptionDays(self):
@@ -139,6 +143,7 @@ class GazBoiler(HeatingDevice):
139
143
  def getPowerConsumptionThisYear(self):
140
144
  return self.getProperty("heating.power.consumption.total")["properties"]["year"]["value"][0]
141
145
 
146
+ # Flow
142
147
  @handleNotSupported
143
148
  def getVolumetricFlowReturn(self):
144
149
  return self.getProperty("heating.sensors.volumetricFlow.allengra")["properties"]["value"]["value"]
@@ -204,6 +209,26 @@ class GazBoiler(HeatingDevice):
204
209
 
205
210
  # Power consumption for Heating:
206
211
  @handleNotSupported
212
+ def getPowerConsumptionHeatingUnit(self):
213
+ try:
214
+ return self.getProperty("heating.power.consumption.heating")["properties"]["day"]["unit"]
215
+ except KeyError:
216
+ return self.getProperty("heating.power.consumption.heating")["properties"]["unit"]["value"]
217
+
218
+ @handleNotSupported
219
+ def getPowerConsumptionHeatingToday(self):
220
+ return self.getProperty("heating.power.consumption.heating")["properties"]["day"]["value"][0]
221
+
222
+ @handleNotSupported
223
+ def getPowerConsumptionHeatingThisMonth(self):
224
+ return self.getProperty("heating.power.consumption.heating")["properties"]["month"]["value"][0]
225
+
226
+ @handleNotSupported
227
+ def getPowerConsumptionHeatingThisYear(self):
228
+ return self.getProperty("heating.power.consumption.heating")["properties"]["year"]["value"][0]
229
+
230
+ # Power summary consumption for Heating:
231
+ @handleNotSupported
207
232
  def getPowerSummaryConsumptionHeatingUnit(self):
208
233
  return self.getProperty("heating.power.consumption.summary.heating")["properties"]["currentDay"]["unit"]
209
234
 
@@ -233,6 +258,26 @@ class GazBoiler(HeatingDevice):
233
258
 
234
259
  # Power consumption for Domestic Hot Water:
235
260
  @handleNotSupported
261
+ def getPowerConsumptionDomesticHotWaterUnit(self):
262
+ try:
263
+ return self.getProperty("heating.power.consumption.dhw")["properties"]["day"]["unit"]
264
+ except KeyError:
265
+ return self.getProperty("heating.power.consumption.dhw")["properties"]["unit"]["value"]
266
+
267
+ @handleNotSupported
268
+ def getPowerConsumptionDomesticHotWaterToday(self):
269
+ return self.getProperty("heating.power.consumption.dhw")["properties"]["day"]["value"][0]
270
+
271
+ @handleNotSupported
272
+ def getPowerConsumptionDomesticHotWaterThisMonth(self):
273
+ return self.getProperty("heating.power.consumption.dhw")["properties"]["month"]["value"][0]
274
+
275
+ @handleNotSupported
276
+ def getPowerConsumptionDomesticHotWaterThisYear(self):
277
+ return self.getProperty("heating.power.consumption.dhw")["properties"]["year"]["value"][0]
278
+
279
+ # Power summary consumption for Domestic Hot Water:
280
+ @handleNotSupported
236
281
  def getPowerSummaryConsumptionDomesticHotWaterUnit(self):
237
282
  return self.getProperty("heating.power.consumption.summary.dhw")["properties"]["currentDay"]["unit"]
238
283
 
@@ -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
 
@@ -43,14 +45,31 @@ class HeatPump(HeatingDevice, VentilationDevice):
43
45
 
44
46
  @handleNotSupported
45
47
  def getBufferMainTemperature(self):
46
- return self.getProperty("heating.bufferCylinder.sensors.temperature.main")["properties"]['value']['value']
48
+ return self.getProperty("heating.bufferCylinder.sensors.temperature.main")["properties"]["value"]["value"]
47
49
 
48
50
  @handleNotSupported
49
51
  def getBufferTopTemperature(self):
50
- return self.getProperty("heating.bufferCylinder.sensors.temperature.top")["properties"]['value']['value']
52
+ return self.getProperty("heating.bufferCylinder.sensors.temperature.top")["properties"]["value"]["value"]
51
53
 
52
54
  # Power consumption for Heating:
53
55
  @handleNotSupported
56
+ def getPowerConsumptionHeatingUnit(self):
57
+ return self.getProperty("heating.power.consumption.heating")["properties"]["day"]["unit"]
58
+
59
+ @handleNotSupported
60
+ def getPowerConsumptionHeatingToday(self):
61
+ return self.getProperty("heating.power.consumption.heating")["properties"]["day"]["value"][0]
62
+
63
+ @handleNotSupported
64
+ def getPowerConsumptionHeatingThisMonth(self):
65
+ return self.getProperty("heating.power.consumption.heating")["properties"]["month"]["value"][0]
66
+
67
+ @handleNotSupported
68
+ def getPowerConsumptionHeatingThisYear(self):
69
+ return self.getProperty("heating.power.consumption.heating")["properties"]["year"]["value"][0]
70
+
71
+ # Power summary consumption for Heating:
72
+ @handleNotSupported
54
73
  def getPowerSummaryConsumptionHeatingUnit(self):
55
74
  return self.getProperty("heating.power.consumption.summary.heating")["properties"]["currentDay"]["unit"]
56
75
 
@@ -95,6 +114,7 @@ class HeatPump(HeatingDevice, VentilationDevice):
95
114
  def getPowerConsumptionCoolingThisYear(self):
96
115
  return self.getProperty("heating.power.consumption.cooling")["properties"]["year"]["value"][0]
97
116
 
117
+ # Total power consumption:
98
118
  @handleNotSupported
99
119
  def getPowerConsumptionUnit(self):
100
120
  return self.getProperty("heating.power.consumption.total")["properties"]["day"]["unit"]
@@ -103,11 +123,32 @@ class HeatPump(HeatingDevice, VentilationDevice):
103
123
  def getPowerConsumptionToday(self):
104
124
  return self.getProperty("heating.power.consumption.total")["properties"]["day"]["value"][0]
105
125
 
126
+ @handleNotSupported
127
+ def getPowerConsumptionThisMonth(self):
128
+ return self.getProperty("heating.power.consumption.total")["properties"]["month"]["value"][0]
129
+
130
+ @handleNotSupported
131
+ def getPowerConsumptionThisYear(self):
132
+ return self.getProperty("heating.power.consumption.total")["properties"]["year"]["value"][0]
133
+
134
+ # Power consumption for Domestic Hot Water:
135
+ @handleNotSupported
136
+ def getPowerConsumptionDomesticHotWaterUnit(self):
137
+ return self.getProperty("heating.power.consumption.dhw")["properties"]["day"]["unit"]
138
+
106
139
  @handleNotSupported
107
140
  def getPowerConsumptionDomesticHotWaterToday(self):
108
141
  return self.getProperty("heating.power.consumption.dhw")["properties"]["day"]["value"][0]
109
142
 
110
- # Power consumption for Domestic Hot Water:
143
+ @handleNotSupported
144
+ def getPowerConsumptionDomesticHotWaterThisMonth(self):
145
+ return self.getProperty("heating.power.consumption.dhw")["properties"]["month"]["value"][0]
146
+
147
+ @handleNotSupported
148
+ def getPowerConsumptionDomesticHotWaterThisYear(self):
149
+ return self.getProperty("heating.power.consumption.dhw")["properties"]["year"]["value"][0]
150
+
151
+ # Power summary consumption for Domestic Hot Water:
111
152
  @handleNotSupported
112
153
  def getPowerSummaryConsumptionDomesticHotWaterUnit(self):
113
154
  return self.getProperty("heating.power.consumption.summary.dhw")["properties"]["currentDay"]["unit"]
@@ -284,6 +325,24 @@ class HeatPump(HeatingDevice, VentilationDevice):
284
325
  def getSeasonalPerformanceFactorTotal(self) -> float:
285
326
  return float(self.getProperty("heating.spf.total")["properties"]["value"]["value"])
286
327
 
328
+ # COP (Coefficient of Performance) - instantaneous efficiency metrics
329
+ # Some devices expose COP instead of SPF
330
+ @handleNotSupported
331
+ def getCoefficientOfPerformanceHeating(self) -> float:
332
+ return float(self.getProperty("heating.cop.heating")["properties"]["value"]["value"])
333
+
334
+ @handleNotSupported
335
+ def getCoefficientOfPerformanceDHW(self) -> float:
336
+ return float(self.getProperty("heating.cop.dhw")["properties"]["value"]["value"])
337
+
338
+ @handleNotSupported
339
+ def getCoefficientOfPerformanceTotal(self) -> float:
340
+ return float(self.getProperty("heating.cop.total")["properties"]["value"]["value"])
341
+
342
+ @handleNotSupported
343
+ def getCoefficientOfPerformanceCooling(self) -> float:
344
+ return float(self.getProperty("heating.cop.cooling")["properties"]["value"]["value"])
345
+
287
346
  @handleNotSupported
288
347
  def getHeatingRodStarts(self) -> int:
289
348
  return int(self.getProperty("heating.heatingRod.statistics")["properties"]["starts"]["value"])
@@ -320,6 +379,191 @@ class HeatPump(HeatingDevice, VentilationDevice):
320
379
  def getHeatingRodPowerConsumptionTotalThisYear(self) -> float:
321
380
  return float(self.getProperty("heating.heatingRod.power.consumption.total")["properties"]["year"]["value"][0])
322
381
 
382
+ # Heating rod power consumption summary for DHW:
383
+ @handleNotSupported
384
+ def getHeatingRodPowerConsumptionSummaryDHWUnit(self) -> str:
385
+ return str(self.getProperty("heating.heatingRod.power.consumption.summary.dhw")["properties"]["currentDay"]["unit"])
386
+
387
+ @handleNotSupported
388
+ def getHeatingRodPowerConsumptionSummaryDHWCurrentDay(self) -> float:
389
+ return float(self.getProperty("heating.heatingRod.power.consumption.summary.dhw")["properties"]["currentDay"]["value"])
390
+
391
+ @handleNotSupported
392
+ def getHeatingRodPowerConsumptionSummaryDHWCurrentMonth(self) -> float:
393
+ return float(self.getProperty("heating.heatingRod.power.consumption.summary.dhw")["properties"]["currentMonth"]["value"])
394
+
395
+ @handleNotSupported
396
+ def getHeatingRodPowerConsumptionSummaryDHWCurrentYear(self) -> float:
397
+ return float(self.getProperty("heating.heatingRod.power.consumption.summary.dhw")["properties"]["currentYear"]["value"])
398
+
399
+ @handleNotSupported
400
+ def getHeatingRodPowerConsumptionSummaryDHWLastMonth(self) -> float:
401
+ return float(self.getProperty("heating.heatingRod.power.consumption.summary.dhw")["properties"]["lastMonth"]["value"])
402
+
403
+ @handleNotSupported
404
+ def getHeatingRodPowerConsumptionSummaryDHWLastSevenDays(self) -> float:
405
+ return float(self.getProperty("heating.heatingRod.power.consumption.summary.dhw")["properties"]["lastSevenDays"]["value"])
406
+
407
+ @handleNotSupported
408
+ def getHeatingRodPowerConsumptionSummaryDHWLastYear(self) -> float:
409
+ return float(self.getProperty("heating.heatingRod.power.consumption.summary.dhw")["properties"]["lastYear"]["value"])
410
+
411
+ # Heating rod power consumption summary for Heating:
412
+ @handleNotSupported
413
+ def getHeatingRodPowerConsumptionSummaryHeatingUnit(self) -> str:
414
+ return str(self.getProperty("heating.heatingRod.power.consumption.summary.heating")["properties"]["currentDay"]["unit"])
415
+
416
+ @handleNotSupported
417
+ def getHeatingRodPowerConsumptionSummaryHeatingCurrentDay(self) -> float:
418
+ return float(self.getProperty("heating.heatingRod.power.consumption.summary.heating")["properties"]["currentDay"]["value"])
419
+
420
+ @handleNotSupported
421
+ def getHeatingRodPowerConsumptionSummaryHeatingCurrentMonth(self) -> float:
422
+ return float(self.getProperty("heating.heatingRod.power.consumption.summary.heating")["properties"]["currentMonth"]["value"])
423
+
424
+ @handleNotSupported
425
+ def getHeatingRodPowerConsumptionSummaryHeatingCurrentYear(self) -> float:
426
+ return float(self.getProperty("heating.heatingRod.power.consumption.summary.heating")["properties"]["currentYear"]["value"])
427
+
428
+ @handleNotSupported
429
+ def getHeatingRodPowerConsumptionSummaryHeatingLastMonth(self) -> float:
430
+ return float(self.getProperty("heating.heatingRod.power.consumption.summary.heating")["properties"]["lastMonth"]["value"])
431
+
432
+ @handleNotSupported
433
+ def getHeatingRodPowerConsumptionSummaryHeatingLastSevenDays(self) -> float:
434
+ return float(self.getProperty("heating.heatingRod.power.consumption.summary.heating")["properties"]["lastSevenDays"]["value"])
435
+
436
+ @handleNotSupported
437
+ def getHeatingRodPowerConsumptionSummaryHeatingLastYear(self) -> float:
438
+ return float(self.getProperty("heating.heatingRod.power.consumption.summary.heating")["properties"]["lastYear"]["value"])
439
+
440
+ # Heating rod runtime by level
441
+ @handleNotSupported
442
+ def getHeatingRodRuntimeLevelOne(self) -> int:
443
+ return int(self.getProperty("heating.heatingRod.runtime")["properties"]["levelOne"]["value"])
444
+
445
+ @handleNotSupported
446
+ def getHeatingRodRuntimeLevelTwo(self) -> int:
447
+ return int(self.getProperty("heating.heatingRod.runtime")["properties"]["levelTwo"]["value"])
448
+
449
+ @handleNotSupported
450
+ def getHeatingRodRuntimeLevelOneUnit(self) -> str:
451
+ return str(self.getProperty("heating.heatingRod.runtime")["properties"]["levelOne"]["unit"])
452
+
453
+ # Additional pressure sensors (refrigerant circuit)
454
+ @handleNotSupported
455
+ def getHotGasPressure(self) -> float:
456
+ return float(self.getProperty("heating.sensors.pressure.hotGas")["properties"]["value"]["value"])
457
+
458
+ @handleNotSupported
459
+ def getHotGasPressureUnit(self) -> str:
460
+ return str(self.getProperty("heating.sensors.pressure.hotGas")["properties"]["value"]["unit"])
461
+
462
+ @handleNotSupported
463
+ def getSuctionGasPressure(self) -> float:
464
+ return float(self.getProperty("heating.sensors.pressure.suctionGas")["properties"]["value"]["value"])
465
+
466
+ @handleNotSupported
467
+ def getSuctionGasPressureUnit(self) -> str:
468
+ return str(self.getProperty("heating.sensors.pressure.suctionGas")["properties"]["value"]["unit"])
469
+
470
+ # Additional temperature sensors (refrigerant circuit)
471
+ @handleNotSupported
472
+ def getHotGasTemperature(self) -> float:
473
+ return float(self.getProperty("heating.sensors.temperature.hotGas")["properties"]["value"]["value"])
474
+
475
+ @handleNotSupported
476
+ def getHotGasTemperatureUnit(self) -> str:
477
+ return str(self.getProperty("heating.sensors.temperature.hotGas")["properties"]["value"]["unit"])
478
+
479
+ @handleNotSupported
480
+ def getLiquidGasTemperature(self) -> float:
481
+ return float(self.getProperty("heating.sensors.temperature.liquidGas")["properties"]["value"]["value"])
482
+
483
+ @handleNotSupported
484
+ def getLiquidGasTemperatureUnit(self) -> str:
485
+ return str(self.getProperty("heating.sensors.temperature.liquidGas")["properties"]["value"]["unit"])
486
+
487
+ @handleNotSupported
488
+ def getSuctionGasTemperature(self) -> float:
489
+ return float(self.getProperty("heating.sensors.temperature.suctionGas")["properties"]["value"]["value"])
490
+
491
+ @handleNotSupported
492
+ def getSuctionGasTemperatureUnit(self) -> str:
493
+ return str(self.getProperty("heating.sensors.temperature.suctionGas")["properties"]["value"]["unit"])
494
+
495
+ # Main ECU runtime
496
+ @handleNotSupported
497
+ def getMainECURuntime(self) -> int:
498
+ return int(self.getProperty("heating.device.mainECU")["properties"]["runtime"]["value"])
499
+
500
+ @handleNotSupported
501
+ def getMainECURuntimeUnit(self) -> str:
502
+ return str(self.getProperty("heating.device.mainECU")["properties"]["runtime"]["unit"])
503
+
504
+ # Configuration values
505
+ @handleNotSupported
506
+ def getConfigurationBufferTemperatureMax(self) -> float:
507
+ return float(self.getProperty("heating.configuration.buffer.temperature.max")["properties"]["value"]["value"])
508
+
509
+ @handleNotSupported
510
+ def getConfigurationBufferTemperatureMaxUnit(self) -> str:
511
+ return str(self.getProperty("heating.configuration.buffer.temperature.max")["properties"]["value"]["unit"])
512
+
513
+ @handleNotSupported
514
+ def getConfigurationOutsideTemperatureDampingFactor(self) -> int:
515
+ return int(self.getProperty("heating.configuration.temperature.outside.DampingFactor")["properties"]["value"]["value"])
516
+
517
+ @handleNotSupported
518
+ def getConfigurationOutsideTemperatureDampingFactorUnit(self) -> str:
519
+ return str(self.getProperty("heating.configuration.temperature.outside.DampingFactor")["properties"]["value"]["unit"])
520
+
521
+ @handleNotSupported
522
+ def getConfigurationHeatingRodDHWApproved(self) -> bool:
523
+ return bool(self.getProperty("heating.configuration.heatingRod.dhw")["properties"]["useApproved"]["value"])
524
+
525
+ @handleNotSupported
526
+ def getConfigurationHeatingRodHeatingApproved(self) -> bool:
527
+ return bool(self.getProperty("heating.configuration.heatingRod.heating")["properties"]["useApproved"]["value"])
528
+
529
+ @handleNotSupported
530
+ def getConfigurationDHWHeaterApproved(self) -> bool:
531
+ return bool(self.getProperty("heating.configuration.dhwHeater")["properties"]["useApproved"]["value"])
532
+
533
+ # Cooling circuits
534
+ @property
535
+ def coolingCircuits(self) -> List[CoolingCircuit]:
536
+ return [self.getCoolingCircuit(x) for x in self.getAvailableCoolingCircuits()]
537
+
538
+ def getCoolingCircuit(self, circuit) -> CoolingCircuit:
539
+ return CoolingCircuit(self, circuit)
540
+
541
+ def getAvailableCoolingCircuits(self):
542
+ """Detect available cooling circuits (0, 1, 2, etc.)."""
543
+ available = []
544
+ for circuit in ['0', '1', '2', '3']:
545
+ with suppress(KeyError, PyViCareNotSupportedFeatureError):
546
+ if self.getProperty(f"heating.coolingCircuits.{circuit}.type") is not None:
547
+ available.append(circuit)
548
+ return available
549
+
550
+
551
+ class CoolingCircuit(HeatingDeviceWithComponent):
552
+ """Cooling circuit component for heat pumps with cooling capability."""
553
+
554
+ @property
555
+ def circuit(self) -> str:
556
+ return self.component
557
+
558
+ @handleNotSupported
559
+ def getType(self) -> str:
560
+ return str(self.getProperty(f"heating.coolingCircuits.{self.circuit}.type")["properties"]["value"]["value"])
561
+
562
+ @handleNotSupported
563
+ def getReverseActive(self) -> bool:
564
+ return bool(self.getProperty(f"heating.coolingCircuits.{self.circuit}.reverse")["properties"]["active"]["value"])
565
+
566
+
323
567
 
324
568
  class Compressor(HeatingDeviceWithComponent):
325
569
 
@@ -335,25 +579,45 @@ class Compressor(HeatingDeviceWithComponent):
335
579
  def getHours(self):
336
580
  return self.getProperty(f"heating.compressors.{self.compressor}.statistics")["properties"]["hours"]["value"]
337
581
 
338
- @handleNotSupported
339
582
  def getHoursLoadClass1(self):
340
- return self.getProperty(f"heating.compressors.{self.compressor}.statistics")["properties"]["hoursLoadClassOne"]["value"]
583
+ """Get hours in load class 1. Tries 'statistics' path first, then 'statistics.load'."""
584
+ with suppress(KeyError):
585
+ return self.getProperty(f"heating.compressors.{self.compressor}.statistics")["properties"]["hoursLoadClassOne"]["value"]
586
+ with suppress(KeyError):
587
+ return self.getProperty(f"heating.compressors.{self.compressor}.statistics.load")["properties"]["hoursLoadClassOne"]["value"]
588
+ raise PyViCareNotSupportedFeatureError("getHoursLoadClass1")
341
589
 
342
- @handleNotSupported
343
590
  def getHoursLoadClass2(self):
344
- return self.getProperty(f"heating.compressors.{self.compressor}.statistics")["properties"]["hoursLoadClassTwo"]["value"]
591
+ """Get hours in load class 2. Tries 'statistics' path first, then 'statistics.load'."""
592
+ with suppress(KeyError):
593
+ return self.getProperty(f"heating.compressors.{self.compressor}.statistics")["properties"]["hoursLoadClassTwo"]["value"]
594
+ with suppress(KeyError):
595
+ return self.getProperty(f"heating.compressors.{self.compressor}.statistics.load")["properties"]["hoursLoadClassTwo"]["value"]
596
+ raise PyViCareNotSupportedFeatureError("getHoursLoadClass2")
345
597
 
346
- @handleNotSupported
347
598
  def getHoursLoadClass3(self):
348
- return self.getProperty(f"heating.compressors.{self.compressor}.statistics")["properties"]["hoursLoadClassThree"]["value"]
599
+ """Get hours in load class 3. Tries 'statistics' path first, then 'statistics.load'."""
600
+ with suppress(KeyError):
601
+ return self.getProperty(f"heating.compressors.{self.compressor}.statistics")["properties"]["hoursLoadClassThree"]["value"]
602
+ with suppress(KeyError):
603
+ return self.getProperty(f"heating.compressors.{self.compressor}.statistics.load")["properties"]["hoursLoadClassThree"]["value"]
604
+ raise PyViCareNotSupportedFeatureError("getHoursLoadClass3")
349
605
 
350
- @handleNotSupported
351
606
  def getHoursLoadClass4(self):
352
- return self.getProperty(f"heating.compressors.{self.compressor}.statistics")["properties"]["hoursLoadClassFour"]["value"]
607
+ """Get hours in load class 4. Tries 'statistics' path first, then 'statistics.load'."""
608
+ with suppress(KeyError):
609
+ return self.getProperty(f"heating.compressors.{self.compressor}.statistics")["properties"]["hoursLoadClassFour"]["value"]
610
+ with suppress(KeyError):
611
+ return self.getProperty(f"heating.compressors.{self.compressor}.statistics.load")["properties"]["hoursLoadClassFour"]["value"]
612
+ raise PyViCareNotSupportedFeatureError("getHoursLoadClass4")
353
613
 
354
- @handleNotSupported
355
614
  def getHoursLoadClass5(self):
356
- return self.getProperty(f"heating.compressors.{self.compressor}.statistics")["properties"]["hoursLoadClassFive"]["value"]
615
+ """Get hours in load class 5. Tries 'statistics' path first, then 'statistics.load'."""
616
+ with suppress(KeyError):
617
+ return self.getProperty(f"heating.compressors.{self.compressor}.statistics")["properties"]["hoursLoadClassFive"]["value"]
618
+ with suppress(KeyError):
619
+ return self.getProperty(f"heating.compressors.{self.compressor}.statistics.load")["properties"]["hoursLoadClassFive"]["value"]
620
+ raise PyViCareNotSupportedFeatureError("getHoursLoadClass5")
357
621
 
358
622
  @handleNotSupported
359
623
  def getActive(self):
@@ -363,6 +627,24 @@ class Compressor(HeatingDeviceWithComponent):
363
627
  def getPhase(self):
364
628
  return self.getProperty(f"heating.compressors.{self.compressor}")["properties"]["phase"]["value"]
365
629
 
630
+ @handleNotSupported
631
+ def getPower(self) -> float:
632
+ # Returns the nominal/maximum power of the compressor in kW
633
+ return float(self.getProperty(f"heating.compressors.{self.compressor}.power")["properties"]["value"]["value"])
634
+
635
+ @handleNotSupported
636
+ def getPowerUnit(self) -> str:
637
+ return str(self.getProperty(f"heating.compressors.{self.compressor}.power")["properties"]["value"]["unit"])
638
+
639
+ @handleNotSupported
640
+ def getModulation(self) -> int:
641
+ # Returns the current compressor modulation/power level as percentage (0-100)
642
+ return int(self.getProperty(f"heating.compressors.{self.compressor}.sensors.power")["properties"]["value"]["value"])
643
+
644
+ @handleNotSupported
645
+ def getModulationUnit(self) -> str:
646
+ return str(self.getProperty(f"heating.compressors.{self.compressor}.sensors.power")["properties"]["value"]["unit"])
647
+
366
648
  @handleNotSupported
367
649
  def getSpeed(self) -> int:
368
650
  return int(self.getProperty(f"heating.compressors.{self.compressor}.speed.current")["properties"]["value"]["value"])
@@ -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",
@@ -358,6 +368,15 @@ class HeatingDevice(Device):
358
368
  return self.getProperty("heating.primaryCircuit.sensors.temperature.return")["properties"]["value"][
359
369
  "value"]
360
370
 
371
+ @handleNotSupported
372
+ def getPrimaryCircuitPumpRotation(self) -> float:
373
+ """Get primary circuit pump rotation/speed as percentage."""
374
+ return float(self.getProperty("heating.primaryCircuit.sensors.rotation")["properties"]["value"]["value"])
375
+
376
+ @handleNotSupported
377
+ def getPrimaryCircuitPumpRotationUnit(self):
378
+ return self.getProperty("heating.primaryCircuit.sensors.rotation")["properties"]["value"]["unit"]
379
+
361
380
  @handleNotSupported
362
381
  def getSupplyTemperatureSecondaryCircuit(self):
363
382
  return self.getProperty("heating.secondaryCircuit.sensors.temperature.supply")["properties"]["value"][
@@ -538,6 +557,15 @@ class HeatingCircuit(HeatingDeviceWithComponent):
538
557
  self.getProperty(f"heating.circuits.{self.circuit}.sensors.temperature.supply")["properties"][
539
558
  "value"]["value"]
540
559
 
560
+ @handleNotSupported
561
+ def getTargetTemperature(self) -> float:
562
+ """Get the circuit target temperature."""
563
+ return float(self.getProperty(f"heating.circuits.{self.circuit}.temperature")["properties"]["value"]["value"])
564
+
565
+ @handleNotSupported
566
+ def getTargetTemperatureUnit(self):
567
+ return self.getProperty(f"heating.circuits.{self.circuit}.temperature")["properties"]["value"]["unit"]
568
+
541
569
  @handleNotSupported
542
570
  def getRoomTemperature(self):
543
571
  return self.getProperty(f"heating.circuits.{self.circuit}.sensors.temperature.room")["properties"][
@@ -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.58.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