PyViCare 2.61.0__tar.gz → 2.62.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 (35) hide show
  1. {pyvicare-2.61.0 → pyvicare-2.62.0}/PKG-INFO +2 -2
  2. {pyvicare-2.61.0 → pyvicare-2.62.0}/PyViCare/PyViCareCachedServiceBase.py +5 -0
  3. {pyvicare-2.61.0 → pyvicare-2.62.0}/PyViCare/PyViCareCachedServiceViaGateway.py +0 -4
  4. {pyvicare-2.61.0 → pyvicare-2.62.0}/PyViCare/PyViCareDeviceConfig.py +5 -0
  5. pyvicare-2.62.0/PyViCare/PyViCareWaterTreatment.py +141 -0
  6. {pyvicare-2.61.0 → pyvicare-2.62.0}/pyproject.toml +6 -6
  7. {pyvicare-2.61.0 → pyvicare-2.62.0}/LICENSE +0 -0
  8. {pyvicare-2.61.0 → pyvicare-2.62.0}/PyViCare/Feature.py +0 -0
  9. {pyvicare-2.61.0 → pyvicare-2.62.0}/PyViCare/PyViCare.py +0 -0
  10. {pyvicare-2.61.0 → pyvicare-2.62.0}/PyViCare/PyViCareAbstractOAuthManager.py +0 -0
  11. {pyvicare-2.61.0 → pyvicare-2.62.0}/PyViCare/PyViCareBrowserOAuthManager.py +0 -0
  12. {pyvicare-2.61.0 → pyvicare-2.62.0}/PyViCare/PyViCareCachedService.py +0 -0
  13. {pyvicare-2.61.0 → pyvicare-2.62.0}/PyViCare/PyViCareDevice.py +0 -0
  14. {pyvicare-2.61.0 → pyvicare-2.62.0}/PyViCare/PyViCareElectricalEnergySystem.py +0 -0
  15. {pyvicare-2.61.0 → pyvicare-2.62.0}/PyViCare/PyViCareFloorHeating.py +0 -0
  16. {pyvicare-2.61.0 → pyvicare-2.62.0}/PyViCare/PyViCareFuelCell.py +0 -0
  17. {pyvicare-2.61.0 → pyvicare-2.62.0}/PyViCare/PyViCareGateway.py +0 -0
  18. {pyvicare-2.61.0 → pyvicare-2.62.0}/PyViCare/PyViCareGazBoiler.py +0 -0
  19. {pyvicare-2.61.0 → pyvicare-2.62.0}/PyViCare/PyViCareHeatCurveCalculation.py +0 -0
  20. {pyvicare-2.61.0 → pyvicare-2.62.0}/PyViCare/PyViCareHeatPump.py +0 -0
  21. {pyvicare-2.61.0 → pyvicare-2.62.0}/PyViCare/PyViCareHeatingDevice.py +0 -0
  22. {pyvicare-2.61.0 → pyvicare-2.62.0}/PyViCare/PyViCareHybrid.py +0 -0
  23. {pyvicare-2.61.0 → pyvicare-2.62.0}/PyViCare/PyViCareOAuthManager.py +0 -0
  24. {pyvicare-2.61.0 → pyvicare-2.62.0}/PyViCare/PyViCareOilBoiler.py +0 -0
  25. {pyvicare-2.61.0 → pyvicare-2.62.0}/PyViCare/PyViCarePelletsBoiler.py +0 -0
  26. {pyvicare-2.61.0 → pyvicare-2.62.0}/PyViCare/PyViCareRadiatorActuator.py +0 -0
  27. {pyvicare-2.61.0 → pyvicare-2.62.0}/PyViCare/PyViCareRepeater.py +0 -0
  28. {pyvicare-2.61.0 → pyvicare-2.62.0}/PyViCare/PyViCareRoomControl.py +0 -0
  29. {pyvicare-2.61.0 → pyvicare-2.62.0}/PyViCare/PyViCareRoomSensor.py +0 -0
  30. {pyvicare-2.61.0 → pyvicare-2.62.0}/PyViCare/PyViCareService.py +0 -0
  31. {pyvicare-2.61.0 → pyvicare-2.62.0}/PyViCare/PyViCareServiceViaGateway.py +0 -0
  32. {pyvicare-2.61.0 → pyvicare-2.62.0}/PyViCare/PyViCareUtils.py +0 -0
  33. {pyvicare-2.61.0 → pyvicare-2.62.0}/PyViCare/PyViCareVentilationDevice.py +0 -0
  34. {pyvicare-2.61.0 → pyvicare-2.62.0}/PyViCare/__init__.py +0 -0
  35. {pyvicare-2.61.0 → pyvicare-2.62.0}/README.md +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyViCare
3
- Version: 2.61.0
3
+ Version: 2.62.0
4
4
  Summary: Library to communicate with the Viessmann ViCare API.
5
5
  License: Apache-2.0
6
6
  License-File: LICENSE
@@ -22,7 +22,7 @@ Classifier: Programming Language :: Python :: 3.13
22
22
  Classifier: Programming Language :: Python :: 3.14
23
23
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
24
24
  Requires-Dist: authlib (>1.2.0)
25
- Requires-Dist: deprecated (>=1.2.15,<2.0.0)
25
+ Requires-Dist: deprecated (>=1.3.0,<2.0.0)
26
26
  Requires-Dist: requests (>=2.31.0)
27
27
  Project-URL: Bug Tracker, https://github.com/openviess/PyViCare/issues
28
28
  Project-URL: Changelog, https://github.com/openviess/PyViCare/releases
@@ -34,6 +34,11 @@ class ViCareCachedServiceBase(ViCareService):
34
34
  entities = self._extract_entities(data, accessor)
35
35
  return readFeature(entities, property_name)
36
36
 
37
+ def fetch_all_features(self, accessor: ViCareDeviceAccessor) -> Any:
38
+ # cached, so a caller refreshing with fetch_all_features() warms the
39
+ # cache the following getProperty calls read from
40
+ return self._get_or_update_cache(accessor)
41
+
37
42
  def setProperty(self, accessor: ViCareDeviceAccessor, property_name: str, action: str, data: Any) -> Any:
38
43
  response = super().setProperty(accessor, property_name, action, data)
39
44
  self.clear_cache()
@@ -28,7 +28,3 @@ class ViCareCachedServiceViaGateway(ViCareCachedServiceBase, ViCareServiceViaGat
28
28
 
29
29
  def _extract_entities(self, data: dict, accessor: ViCareDeviceAccessor) -> list[dict[str, Any]]:
30
30
  return filter_features_for_device(data["data"], accessor.device_id)
31
-
32
- def fetch_all_features(self, accessor: ViCareDeviceAccessor) -> Any:
33
- # cached too, so per-device coordinators share one bulk fetch
34
- return self._get_or_update_cache(accessor)
@@ -20,6 +20,7 @@ from PyViCare.PyViCareService import (ViCareDeviceAccessor, ViCareService,
20
20
  hasRoles, is_gateway_role)
21
21
  from PyViCare.PyViCareUtils import PyViCareNotPaidForError
22
22
  from PyViCare.PyViCareVentilationDevice import VentilationDevice
23
+ from PyViCare.PyViCareWaterTreatment import WaterTreatment
23
24
 
24
25
  logger = logging.getLogger(__name__)
25
26
  logger.addHandler(logging.NullHandler())
@@ -84,6 +85,9 @@ class PyViCareDeviceConfig:
84
85
  def asVentilation(self):
85
86
  return VentilationDevice(self.accessor, self.service, self.roles)
86
87
 
88
+ def asWaterTreatment(self):
89
+ return WaterTreatment(self.accessor, self.service, self.roles)
90
+
87
91
  def getConfig(self):
88
92
  return self.accessor
89
93
 
@@ -127,6 +131,7 @@ class PyViCareDeviceConfig:
127
131
  (self.asRoomControl, r"E3_RoomControl|Smart_RoomControl", ["type:virtual;smartRoomControl"]),
128
132
  (self.asRoomSensor, r"E3_RoomSensor", ["type:climateSensor"]),
129
133
  (self.asRepeater, r"E3_Repeater", ["type:repeater"]),
134
+ (self.asWaterTreatment, r"VitosetAqua", ["type:waterTreatment"]),
130
135
  (self.asGateway, r"E3_TCU41_x04", ["type:gateway;TCU100"]),
131
136
  (self.asGateway, r"E3_TCU19_x05", ["type:gateway;TCU200"]),
132
137
  (self.asGateway, r"E3_TCU10_x07", ["type:gateway;TCU300"]),
@@ -0,0 +1,141 @@
1
+ from contextlib import suppress
2
+ from typing import Any, List, Optional
3
+
4
+ from PyViCare.PyViCareDevice import Device
5
+ from PyViCare.PyViCareUtils import PyViCareNotSupportedFeatureError, handleNotSupported
6
+
7
+
8
+ _LEAK_SENSOR_SLOTS = 5
9
+
10
+
11
+ class WaterTreatment(Device):
12
+ """Viessmann Vitoset Aqua water treatment station.
13
+
14
+ Combines water softener, leak detection (up to 5 sensors), consumption metering
15
+ and main shutoff valve in one device.
16
+ """
17
+
18
+ # --- Softener ---
19
+
20
+ @handleNotSupported
21
+ def getSaltDaysRemaining(self) -> int:
22
+ return int(self.getProperty("water.softener.salt.level.days")["properties"]["remaining"]["value"])
23
+
24
+ @handleNotSupported
25
+ def getLowSaltAlertDays(self) -> int:
26
+ return int(self.getProperty("water.softener.configuration.lowSaltAlert")["properties"]["lowLevelAlertDays"]["value"])
27
+
28
+ # --- Consumption ---
29
+
30
+ @handleNotSupported
31
+ def getCurrentFlow(self) -> float:
32
+ return float(self.getProperty("water.consumption.flow.current")["properties"]["value"]["value"])
33
+
34
+ @handleNotSupported
35
+ def getMaxFlow(self) -> float:
36
+ return float(self.getProperty("water.consumption.flow.max")["properties"]["value"]["value"])
37
+
38
+ @handleNotSupported
39
+ def getTotalConsumption(self) -> int:
40
+ return int(self.getProperty("water.consumption.summary")["properties"]["total"]["value"])
41
+
42
+ # --- Leak Detection ---
43
+
44
+ @property
45
+ def leakSensors(self) -> List["LeakSensor"]:
46
+ return [LeakSensor(self, slot) for slot in self.getAvailableLeakSensorSlots()]
47
+
48
+ def getAvailableLeakSensorSlots(self) -> List[int]:
49
+ available: List[int] = []
50
+ for slot in range(_LEAK_SENSOR_SLOTS):
51
+ with suppress(PyViCareNotSupportedFeatureError):
52
+ feature = self.getProperty(f"water.leakDetection.sensors.leakage.{slot}")
53
+ if feature.get("isEnabled"):
54
+ available.append(slot)
55
+ return available
56
+
57
+ @handleNotSupported
58
+ def getFlowAlertMaxDuration(self) -> int:
59
+ return int(self.getProperty("water.leakDetection.configuration.flowAlert")["properties"]["maxDuration"]["value"])
60
+
61
+ @handleNotSupported
62
+ def getFlowAlertMaxFlow(self) -> float:
63
+ return float(self.getProperty("water.leakDetection.configuration.flowAlert")["properties"]["maxFlow"]["value"])
64
+
65
+ # --- Shutoff Valve ---
66
+
67
+ @handleNotSupported
68
+ def getShutoffPosition(self) -> str:
69
+ return str(self.getProperty("water.valves.shutoff.position")["properties"]["value"]["value"])
70
+
71
+ @handleNotSupported
72
+ def getShutoffMotorState(self) -> str:
73
+ return str(self.getProperty("water.valves.shutoff.motor")["properties"]["state"]["value"])
74
+
75
+ @handleNotSupported
76
+ def getHolidayModeActive(self) -> bool:
77
+ return bool(self.getProperty("water.valves.shutoff.holiday")["properties"]["active"]["value"])
78
+
79
+
80
+ class LeakSensor:
81
+ """Single wireless leakage sensor connected to a Vitoset Aqua."""
82
+
83
+ def __init__(self, device: WaterTreatment, slot: int) -> None:
84
+ self.service = device.service
85
+ self.slot = slot
86
+ self.device = device
87
+
88
+ @property
89
+ def id(self) -> int:
90
+ return self.slot
91
+
92
+ def getProperty(self, property_name: str) -> Any:
93
+ return self.device.getProperty(property_name)
94
+
95
+ @handleNotSupported
96
+ def getStatus(self) -> str:
97
+ return str(self.getProperty(f"water.leakDetection.sensors.leakage.{self.slot}")["properties"]["status"]["value"])
98
+
99
+ @handleNotSupported
100
+ def getLeakDetected(self) -> bool:
101
+ return bool(self.getProperty(f"water.leakDetection.sensors.leakage.{self.slot}")["properties"]["value"]["value"])
102
+
103
+ @handleNotSupported
104
+ def getSensorId(self) -> str:
105
+ return str(self.getProperty(f"water.leakDetection.sensors.leakage.{self.slot}.id")["properties"]["value"]["value"])
106
+
107
+ @handleNotSupported
108
+ def getName(self) -> str:
109
+ return str(self.getProperty(f"water.leakDetection.sensors.leakage.{self.slot}.name")["properties"]["name"]["value"])
110
+
111
+ @handleNotSupported
112
+ def getBatteryPercent(self) -> int:
113
+ return int(self.getProperty(f"water.leakDetection.sensors.leakage.{self.slot}.battery")["properties"]["level"]["value"])
114
+
115
+ @handleNotSupported
116
+ def getRssi(self) -> int:
117
+ return int(self.getProperty(f"water.leakDetection.sensors.leakage.{self.slot}.rssi")["properties"]["value"]["value"])
118
+
119
+ def getHardwareVersion(self) -> Optional[dict[str, int]]:
120
+ try:
121
+ feature = self.getProperty(f"water.leakDetection.sensors.leakage.{self.slot}.version.hardware")
122
+ except PyViCareNotSupportedFeatureError:
123
+ return None
124
+ return self._versionFields(feature.get("properties") or {})
125
+
126
+ def getSoftwareVersion(self) -> Optional[dict[str, int]]:
127
+ try:
128
+ feature = self.getProperty(f"water.leakDetection.sensors.leakage.{self.slot}.version.software")
129
+ except PyViCareNotSupportedFeatureError:
130
+ return None
131
+ return self._versionFields(feature.get("properties") or {})
132
+
133
+ @staticmethod
134
+ def _versionFields(props: dict[str, Any]) -> Optional[dict[str, int]]:
135
+ if not props:
136
+ return None
137
+ return {
138
+ field: int(props[field]["value"])
139
+ for field in ("build", "family", "revision", "version")
140
+ if field in props
141
+ }
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "PyViCare"
3
- version = "2.61.0"
3
+ version = "2.62.0"
4
4
  description = "Library to communicate with the Viessmann ViCare API."
5
5
  authors = [
6
6
  "Simon Gillet <mail+pyvicare@gillet.ninja>"
@@ -45,15 +45,15 @@ Changelog = "https://github.com/openviess/PyViCare/releases"
45
45
  authlib = ">1.2.0"
46
46
  python = "^3.10"
47
47
  requests = ">=2.31.0"
48
- deprecated = "^1.2.15"
48
+ deprecated = "^1.3.0"
49
49
 
50
50
  [tool.poetry.group.dev.dependencies]
51
- codespell = "2.4.2"
52
- mypy = "2.3.0"
53
- pylint = "4.0.6"
51
+ codespell = "2.4.3"
52
+ mypy = "2.3.1"
53
+ pylint = "4.0.7"
54
54
  pytest = "9.1.1"
55
55
  pytest-cov = "7.1.0"
56
- ruff = "0.15.21"
56
+ ruff = "0.15.22"
57
57
  types-deprecated = "^1.2.15.20241117"
58
58
  types-requests = ">=2.31"
59
59
 
File without changes
File without changes
File without changes