Skip to content

Commit 5684a19

Browse files
authored
Merge branch 'master' into feature/heating-schedule-setter
2 parents c23842b + 8f17bef commit 5684a19

33 files changed

Lines changed: 8814 additions & 46 deletions

.github/workflows/format.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,19 @@ jobs:
2828
-exec sh -c 'mv $1 $1.tmp; jq ".data|=sort_by(.feature)" --sort-keys $1.tmp > $1; rm $1.tmp' shell {} ";"
2929
- name: 🔍 Verify
3030
run: git diff --name-only --exit-code
31+
32+
deprecations:
33+
name: validate deprecation database
34+
runs-on: ubuntu-latest
35+
steps:
36+
- name: ⤵️ Check out code from GitHub
37+
uses: actions/checkout@v6.0.2
38+
- name: Set up Python
39+
uses: actions/setup-python@v6.2.0
40+
with:
41+
python-version: ${{ env.DEFAULT_PYTHON }}
42+
- name: Update deprecation database
43+
run: python check_deprecations.py --update
44+
- name: Verify no changes
45+
run: git diff --exit-code
46+
if: always()

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ jobs:
1818
runs-on: ubuntu-latest
1919
strategy:
2020
matrix:
21-
python: ["3.10", "3.11", "3.12"]
21+
python: ["3.10", "3.11", "3.12", "3.13"]
2222
steps:
2323
- name: ⤵️ Check out code from GitHub
2424
uses: actions/checkout@v6.0.2

PyViCare/PyViCare.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ def __extract_devices(self):
6161

6262
logger.info("Device found: %s", device.modelId)
6363

64-
yield PyViCareDeviceConfig(service, device.id, device.modelId, device.status)
64+
yield PyViCareDeviceConfig(service, device.id, device.modelId, device.status, device.deviceType, device.roles)
6565

6666

6767
class DictWrap(object):

PyViCare/PyViCareAbstractOAuthManager.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
from PyViCare import Feature
99
from PyViCare.PyViCareUtils import (PyViCareCommandError,
10+
PyViCareDeviceCommunicationError,
1011
PyViCareInternalServerError,
1112
PyViCareRateLimitError)
1213

@@ -39,6 +40,7 @@ def get(self, url: str) -> Any:
3940
logger.debug("Response to get request: %s", response)
4041
self.__handle_expired_token(response)
4142
self.__handle_rate_limit(response)
43+
self.__handle_device_communication_error(response)
4244
self.__handle_server_error(response)
4345
return response
4446
except TokenExpiredError:
@@ -59,6 +61,10 @@ def __handle_rate_limit(self, response):
5961
if ("statusCode" in response and response["statusCode"] == 429):
6062
raise PyViCareRateLimitError(response)
6163

64+
def __handle_device_communication_error(self, response):
65+
if ("errorType" in response and response["errorType"] == "DEVICE_COMMUNICATION_ERROR"):
66+
raise PyViCareDeviceCommunicationError(response)
67+
6268
def __handle_server_error(self, response):
6369
if ("statusCode" in response and response["statusCode"] >= 500):
6470
raise PyViCareInternalServerError(response)

PyViCare/PyViCareCachedService.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
from PyViCare.PyViCareAbstractOAuthManager import AbstractViCareOAuthManager
66
from PyViCare.PyViCareService import (ViCareDeviceAccessor, ViCareService,
77
readFeature)
8-
from PyViCare.PyViCareUtils import PyViCareInvalidDataError, ViCareTimer
8+
from PyViCare.PyViCareUtils import (PyViCareDeviceCommunicationError,
9+
PyViCareInvalidDataError,
10+
PyViCareInternalServerError,
11+
ViCareTimer)
912

1013
logger = logging.getLogger('ViCare')
1114
logger.addHandler(logging.NullHandler())
@@ -33,13 +36,20 @@ def setProperty(self, property_name, action, data):
3336
def __get_or_update_cache(self):
3437
with self.__lock:
3538
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
3740
# to avoid consuming all the api calls if the api is down
3841
# see https://github.com/home-assistant/core/issues/67052
3942
# we simply return the old cache in this case
4043
self.__cacheTime = ViCareTimer().now()
4144

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+
4353
if "data" not in data:
4454
logger.error("Missing 'data' property when fetching data.")
4555
raise PyViCareInvalidDataError(data)

PyViCare/PyViCareDeviceConfig.py

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,14 @@
2222

2323

2424
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):
2627
self.service = service
2728
self.device_id = device_id
2829
self.device_model = device_model
2930
self.status = status
31+
self.device_type = device_type
32+
self.roles = roles if roles is not None else []
3033

3134
def asGeneric(self):
3235
return HeatingDevice(self.service)
@@ -85,6 +88,15 @@ def getModel(self):
8588
def isOnline(self):
8689
return self.status == "Online"
8790

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+
88100
# see: https://vitodata300.viessmann.com/vd300/ApplicationHelp/VD300/1031_de_DE/Ger%C3%A4teliste.html
89101
def asAutoDetectDevice(self):
90102
device_types = [
@@ -122,12 +134,25 @@ def get_raw_json(self):
122134
return self.service.fetch_all_features()
123135

124136
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+
125150
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)
128153
dumpJSON = outer.replace('"placeholder"', inner)
129154
else:
130-
dumpJSON = json.dumps(self.get_raw_json(), indent=4, sort_keys=True)
155+
dumpJSON = json.dumps(output, indent=4, sort_keys=True)
131156

132157
def repl(m):
133158
return m.group(1) + ('#' * len(m.group(2))) + m.group(3)

PyViCare/PyViCareFuelCell.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ def getAvailableBurners(self):
2323
def getReturnTemperature(self):
2424
return self.getProperty("heating.sensors.temperature.return")["properties"]["value"]["value"]
2525

26+
# Total power consumption:
2627
@handleNotSupported
2728
def getPowerConsumptionUnit(self):
2829
return self.getProperty("heating.power.consumption.total")["properties"]["day"]["unit"]
@@ -59,6 +60,7 @@ def getPowerConsumptionYears(self):
5960
def getPowerConsumptionThisYear(self):
6061
return self.getProperty('heating.power.consumption.total')['properties']['year']['value'][0]
6162

63+
# Power consumption for Heating:
6264
@handleNotSupported
6365
def getPowerConsumptionHeatingUnit(self):
6466
return self.getProperty("heating.power.consumption.heating")["properties"]["day"]["unit"]
@@ -95,6 +97,24 @@ def getPowerConsumptionHeatingYears(self):
9597
def getPowerConsumptionHeatingThisYear(self):
9698
return self.getProperty('heating.power.consumption.heating')['properties']['year']['value'][0]
9799

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:
98118
@handleNotSupported
99119
def getGasConsumptionUnit(self):
100120
return self.getProperty("heating.gas.consumption.total")["properties"]["day"]["unit"]

PyViCare/PyViCareGazBoiler.py

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,9 +103,13 @@ def getBoilerTargetTemperature(self):
103103
def getDomesticHotWaterChargingLevel(self):
104104
return self.getProperty("heating.dhw.charging.level")["properties"]["value"]["value"]
105105

106+
# Total power consumption:
106107
@handleNotSupported
107108
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"]
109113

110114
@handleNotSupported
111115
def getPowerConsumptionDays(self):
@@ -139,6 +143,7 @@ def getPowerConsumptionYears(self):
139143
def getPowerConsumptionThisYear(self):
140144
return self.getProperty("heating.power.consumption.total")["properties"]["year"]["value"][0]
141145

146+
# Flow
142147
@handleNotSupported
143148
def getVolumetricFlowReturn(self):
144149
return self.getProperty("heating.sensors.volumetricFlow.allengra")["properties"]["value"]["value"]
@@ -204,6 +209,26 @@ def getGasSummaryConsumptionDomesticHotWaterLastYear(self):
204209

205210
# Power consumption for Heating:
206211
@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
207232
def getPowerSummaryConsumptionHeatingUnit(self):
208233
return self.getProperty("heating.power.consumption.summary.heating")["properties"]["currentDay"]["unit"]
209234

@@ -233,6 +258,26 @@ def getPowerSummaryConsumptionHeatingLastYear(self):
233258

234259
# Power consumption for Domestic Hot Water:
235260
@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
236281
def getPowerSummaryConsumptionDomesticHotWaterUnit(self):
237282
return self.getProperty("heating.power.consumption.summary.dhw")["properties"]["currentDay"]["unit"]
238283

PyViCare/PyViCareHeatPump.py

Lines changed: 76 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
from __future__ import annotations
2+
from contextlib import suppress
23
from typing import Any, List
34
from deprecated import deprecated
45

56
from PyViCare.PyViCareHeatingDevice import HeatingDevice, HeatingDeviceWithComponent
6-
from PyViCare.PyViCareUtils import handleAPICommandErrors, handleNotSupported
7+
from PyViCare.PyViCareUtils import (PyViCareNotSupportedFeatureError,
8+
handleAPICommandErrors, handleNotSupported)
79
from PyViCare.PyViCareVentilationDevice import VentilationDevice
810

911

@@ -51,6 +53,23 @@ def getBufferTopTemperature(self):
5153

5254
# Power consumption for Heating:
5355
@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
5473
def getPowerSummaryConsumptionHeatingUnit(self):
5574
return self.getProperty("heating.power.consumption.summary.heating")["properties"]["currentDay"]["unit"]
5675

@@ -95,6 +114,7 @@ def getPowerConsumptionCoolingThisMonth(self):
95114
def getPowerConsumptionCoolingThisYear(self):
96115
return self.getProperty("heating.power.consumption.cooling")["properties"]["year"]["value"][0]
97116

117+
# Total power consumption:
98118
@handleNotSupported
99119
def getPowerConsumptionUnit(self):
100120
return self.getProperty("heating.power.consumption.total")["properties"]["day"]["unit"]
@@ -103,11 +123,32 @@ def getPowerConsumptionUnit(self):
103123
def getPowerConsumptionToday(self):
104124
return self.getProperty("heating.power.consumption.total")["properties"]["day"]["value"][0]
105125

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+
106139
@handleNotSupported
107140
def getPowerConsumptionDomesticHotWaterToday(self):
108141
return self.getProperty("heating.power.consumption.dhw")["properties"]["day"]["value"][0]
109142

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:
111152
@handleNotSupported
112153
def getPowerSummaryConsumptionDomesticHotWaterUnit(self):
113154
return self.getProperty("heating.power.consumption.summary.dhw")["properties"]["currentDay"]["unit"]
@@ -320,6 +361,39 @@ def getHeatingRodPowerConsumptionHeatingThisYear(self) -> float:
320361
def getHeatingRodPowerConsumptionTotalThisYear(self) -> float:
321362
return float(self.getProperty("heating.heatingRod.power.consumption.total")["properties"]["year"]["value"][0])
322363

364+
# Cooling circuits
365+
@property
366+
def coolingCircuits(self) -> List[CoolingCircuit]:
367+
return [self.getCoolingCircuit(x) for x in self.getAvailableCoolingCircuits()]
368+
369+
def getCoolingCircuit(self, circuit) -> CoolingCircuit:
370+
return CoolingCircuit(self, circuit)
371+
372+
def getAvailableCoolingCircuits(self):
373+
"""Detect available cooling circuits (0, 1, 2, etc.)."""
374+
available = []
375+
for circuit in ['0', '1', '2', '3']:
376+
with suppress(KeyError, PyViCareNotSupportedFeatureError):
377+
if self.getProperty(f"heating.coolingCircuits.{circuit}.type") is not None:
378+
available.append(circuit)
379+
return available
380+
381+
382+
class CoolingCircuit(HeatingDeviceWithComponent):
383+
"""Cooling circuit component for heat pumps with cooling capability."""
384+
385+
@property
386+
def circuit(self) -> str:
387+
return self.component
388+
389+
@handleNotSupported
390+
def getType(self) -> str:
391+
return str(self.getProperty(f"heating.coolingCircuits.{self.circuit}.type")["properties"]["value"]["value"])
392+
393+
@handleNotSupported
394+
def getReverseActive(self) -> bool:
395+
return bool(self.getProperty(f"heating.coolingCircuits.{self.circuit}.reverse")["properties"]["active"]["value"])
396+
323397

324398
class Compressor(HeatingDeviceWithComponent):
325399

0 commit comments

Comments
 (0)