PyViCare 2.59.0__tar.gz → 2.59.1__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.
- {pyvicare-2.59.0 → pyvicare-2.59.1}/PKG-INFO +1 -1
- {pyvicare-2.59.0 → pyvicare-2.59.1}/PyViCare/PyViCareAbstractOAuthManager.py +34 -3
- {pyvicare-2.59.0 → pyvicare-2.59.1}/PyViCare/PyViCareDeviceConfig.py +18 -1
- {pyvicare-2.59.0 → pyvicare-2.59.1}/pyproject.toml +1 -1
- {pyvicare-2.59.0 → pyvicare-2.59.1}/LICENSE +0 -0
- {pyvicare-2.59.0 → pyvicare-2.59.1}/PyViCare/Feature.py +0 -0
- {pyvicare-2.59.0 → pyvicare-2.59.1}/PyViCare/PyViCare.py +0 -0
- {pyvicare-2.59.0 → pyvicare-2.59.1}/PyViCare/PyViCareBrowserOAuthManager.py +0 -0
- {pyvicare-2.59.0 → pyvicare-2.59.1}/PyViCare/PyViCareCachedService.py +0 -0
- {pyvicare-2.59.0 → pyvicare-2.59.1}/PyViCare/PyViCareDevice.py +0 -0
- {pyvicare-2.59.0 → pyvicare-2.59.1}/PyViCare/PyViCareElectricalEnergySystem.py +0 -0
- {pyvicare-2.59.0 → pyvicare-2.59.1}/PyViCare/PyViCareFloorHeating.py +0 -0
- {pyvicare-2.59.0 → pyvicare-2.59.1}/PyViCare/PyViCareFuelCell.py +0 -0
- {pyvicare-2.59.0 → pyvicare-2.59.1}/PyViCare/PyViCareGateway.py +0 -0
- {pyvicare-2.59.0 → pyvicare-2.59.1}/PyViCare/PyViCareGazBoiler.py +0 -0
- {pyvicare-2.59.0 → pyvicare-2.59.1}/PyViCare/PyViCareHeatCurveCalculation.py +0 -0
- {pyvicare-2.59.0 → pyvicare-2.59.1}/PyViCare/PyViCareHeatPump.py +0 -0
- {pyvicare-2.59.0 → pyvicare-2.59.1}/PyViCare/PyViCareHeatingDevice.py +0 -0
- {pyvicare-2.59.0 → pyvicare-2.59.1}/PyViCare/PyViCareHybrid.py +0 -0
- {pyvicare-2.59.0 → pyvicare-2.59.1}/PyViCare/PyViCareOAuthManager.py +0 -0
- {pyvicare-2.59.0 → pyvicare-2.59.1}/PyViCare/PyViCareOilBoiler.py +0 -0
- {pyvicare-2.59.0 → pyvicare-2.59.1}/PyViCare/PyViCarePelletsBoiler.py +0 -0
- {pyvicare-2.59.0 → pyvicare-2.59.1}/PyViCare/PyViCareRadiatorActuator.py +0 -0
- {pyvicare-2.59.0 → pyvicare-2.59.1}/PyViCare/PyViCareRepeater.py +0 -0
- {pyvicare-2.59.0 → pyvicare-2.59.1}/PyViCare/PyViCareRoomSensor.py +0 -0
- {pyvicare-2.59.0 → pyvicare-2.59.1}/PyViCare/PyViCareService.py +0 -0
- {pyvicare-2.59.0 → pyvicare-2.59.1}/PyViCare/PyViCareUtils.py +0 -0
- {pyvicare-2.59.0 → pyvicare-2.59.1}/PyViCare/PyViCareVentilationDevice.py +0 -0
- {pyvicare-2.59.0 → pyvicare-2.59.1}/PyViCare/__init__.py +0 -0
- {pyvicare-2.59.0 → pyvicare-2.59.1}/README.md +0 -0
|
@@ -44,7 +44,9 @@ class AbstractViCareOAuthManager:
|
|
|
44
44
|
def get(self, url: str) -> Any:
|
|
45
45
|
try:
|
|
46
46
|
logger.debug(self.__oauth)
|
|
47
|
-
|
|
47
|
+
raw_response = self.__oauth.get(f"{API_BASE_URL}{url}", timeout=31)
|
|
48
|
+
self.__raise_on_non_json_error(raw_response)
|
|
49
|
+
response = raw_response.json()
|
|
48
50
|
logger.debug("Response to get request: %s", response)
|
|
49
51
|
self.__handle_expired_token(response)
|
|
50
52
|
self.__handle_rate_limit(response)
|
|
@@ -58,6 +60,21 @@ class AbstractViCareOAuthManager:
|
|
|
58
60
|
except InvalidTokenError:
|
|
59
61
|
self.renewToken()
|
|
60
62
|
return self.get(url)
|
|
63
|
+
except OSError as e:
|
|
64
|
+
raise PyViCareInternalServerError(
|
|
65
|
+
{"statusCode": 0,
|
|
66
|
+
"message": str(e),
|
|
67
|
+
"viErrorId": "n/a"}) from e
|
|
68
|
+
|
|
69
|
+
def __raise_on_non_json_error(self, response):
|
|
70
|
+
"""Guard against non-JSON error responses (e.g. 502 HTML pages from API gateway)."""
|
|
71
|
+
if response.status_code >= 500:
|
|
72
|
+
content_type = response.headers.get('content-type', '')
|
|
73
|
+
if 'application/json' not in content_type:
|
|
74
|
+
raise PyViCareInternalServerError(
|
|
75
|
+
{"statusCode": response.status_code,
|
|
76
|
+
"message": f"Non-JSON {response.status_code} response",
|
|
77
|
+
"viErrorId": "n/a"})
|
|
61
78
|
|
|
62
79
|
def __handle_expired_token(self, response):
|
|
63
80
|
if ("error" in response and response["error"] == "EXPIRED TOKEN"):
|
|
@@ -82,6 +99,13 @@ class AbstractViCareOAuthManager:
|
|
|
82
99
|
if ("statusCode" in response and response["statusCode"] >= 500):
|
|
83
100
|
raise PyViCareInternalServerError(response)
|
|
84
101
|
|
|
102
|
+
extended = response.get("extendedPayload", {})
|
|
103
|
+
if isinstance(extended, dict) and extended.get("code") in ("500", "502", "503"):
|
|
104
|
+
raise PyViCareInternalServerError(
|
|
105
|
+
{"statusCode": int(extended["code"]),
|
|
106
|
+
"message": extended.get("reason", ""),
|
|
107
|
+
"viErrorId": response.get("viErrorId", "n/a")})
|
|
108
|
+
|
|
85
109
|
def __handle_command_error(self, response):
|
|
86
110
|
if not Feature.raise_exception_on_command_failure:
|
|
87
111
|
return
|
|
@@ -106,8 +130,10 @@ class AbstractViCareOAuthManager:
|
|
|
106
130
|
headers = {"Content-Type": "application/json",
|
|
107
131
|
"Accept": "application/vnd.siren+json"}
|
|
108
132
|
try:
|
|
109
|
-
|
|
110
|
-
f"{API_BASE_URL}{url}", data, headers=headers)
|
|
133
|
+
raw_response = self.__oauth.post(
|
|
134
|
+
f"{API_BASE_URL}{url}", data, headers=headers)
|
|
135
|
+
self.__raise_on_non_json_error(raw_response)
|
|
136
|
+
response = raw_response.json()
|
|
111
137
|
self.__handle_expired_token(response)
|
|
112
138
|
self.__handle_rate_limit(response)
|
|
113
139
|
self.__handle_command_error(response)
|
|
@@ -118,3 +144,8 @@ class AbstractViCareOAuthManager:
|
|
|
118
144
|
except InvalidTokenError:
|
|
119
145
|
self.renewToken()
|
|
120
146
|
return self.post(url, data)
|
|
147
|
+
except OSError as e:
|
|
148
|
+
raise PyViCareInternalServerError(
|
|
149
|
+
{"statusCode": 0,
|
|
150
|
+
"message": str(e),
|
|
151
|
+
"viErrorId": "n/a"}) from e
|
|
@@ -125,11 +125,28 @@ class PyViCareDeviceConfig:
|
|
|
125
125
|
for (creator_method, type_name, roles) in device_types:
|
|
126
126
|
if re.search(type_name, self.device_model) or self.service.hasRoles(roles):
|
|
127
127
|
logger.info("detected %s %s", self.device_model, creator_method.__name__)
|
|
128
|
-
|
|
128
|
+
device = creator_method()
|
|
129
|
+
if isinstance(device, (GazBoiler, HeatPump)) and not isinstance(device, Hybrid):
|
|
130
|
+
if self._isHybridByFeatures():
|
|
131
|
+
logger.info("upgrading %s to Hybrid based on API features", self.device_model)
|
|
132
|
+
return self.asHybridDevice()
|
|
133
|
+
return device
|
|
129
134
|
|
|
130
135
|
logger.info("Could not auto detect %s. Use generic device.", self.device_model)
|
|
131
136
|
return self.asGeneric()
|
|
132
137
|
|
|
138
|
+
def _isHybridByFeatures(self):
|
|
139
|
+
"""Check API features to detect hybrid devices (both burners and compressors)."""
|
|
140
|
+
try:
|
|
141
|
+
features = self.service.fetch_all_features()
|
|
142
|
+
feature_names = [f["feature"] for f in features.get("data", [])]
|
|
143
|
+
has_burners = any(f.startswith("heating.burners") for f in feature_names)
|
|
144
|
+
has_compressors = any(f.startswith("heating.compressors") for f in feature_names)
|
|
145
|
+
return has_burners and has_compressors
|
|
146
|
+
except (KeyError, TypeError, AttributeError, OSError):
|
|
147
|
+
logger.debug("Could not fetch features for hybrid detection of %s", self.device_model)
|
|
148
|
+
return False
|
|
149
|
+
|
|
133
150
|
def get_raw_json(self):
|
|
134
151
|
return self.service.fetch_all_features()
|
|
135
152
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|