dimplex-controller 0.14.0__tar.gz → 0.15.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.
- {dimplex_controller-0.14.0 → dimplex_controller-0.15.0}/PKG-INFO +6 -4
- {dimplex_controller-0.14.0 → dimplex_controller-0.15.0}/README.md +3 -1
- {dimplex_controller-0.14.0 → dimplex_controller-0.15.0}/dimplex_controller/__init__.py +6 -0
- {dimplex_controller-0.14.0 → dimplex_controller-0.15.0}/dimplex_controller/capabilities.py +27 -13
- {dimplex_controller-0.14.0 → dimplex_controller-0.15.0}/dimplex_controller/client.py +49 -5
- {dimplex_controller-0.14.0 → dimplex_controller-0.15.0}/dimplex_controller/models.py +40 -0
- {dimplex_controller-0.14.0 → dimplex_controller-0.15.0}/dimplex_controller/telemetry.py +28 -0
- {dimplex_controller-0.14.0 → dimplex_controller-0.15.0}/pyproject.toml +3 -3
- {dimplex_controller-0.14.0 → dimplex_controller-0.15.0}/pyproject.toml.orig +15 -6
- {dimplex_controller-0.14.0 → dimplex_controller-0.15.0}/LICENSE +0 -0
- {dimplex_controller-0.14.0 → dimplex_controller-0.15.0}/dimplex_controller/auth.py +0 -0
- {dimplex_controller-0.14.0 → dimplex_controller-0.15.0}/dimplex_controller/cli.py +0 -0
- {dimplex_controller-0.14.0 → dimplex_controller-0.15.0}/dimplex_controller/const.py +0 -0
- {dimplex_controller-0.14.0 → dimplex_controller-0.15.0}/dimplex_controller/exceptions.py +0 -0
- {dimplex_controller-0.14.0 → dimplex_controller-0.15.0}/dimplex_controller/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dimplex-controller
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.15.0
|
|
4
4
|
Summary: Unofficial async Python client for Dimplex (GDHV IoT) heating appliances — not affiliated with Glen Dimplex
|
|
5
5
|
Keywords: dimplex,heating,control,iot,asyncio
|
|
6
6
|
Author: Kieran Roper
|
|
@@ -17,8 +17,8 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
17
17
|
Classifier: Programming Language :: Python :: 3.13
|
|
18
18
|
Classifier: Programming Language :: Python :: 3.14
|
|
19
19
|
Classifier: Topic :: Home Automation
|
|
20
|
-
Requires-Dist: aiohttp>=3.
|
|
21
|
-
Requires-Dist: pydantic>=2.
|
|
20
|
+
Requires-Dist: aiohttp>=3.9.0,<4
|
|
21
|
+
Requires-Dist: pydantic>=2.0.0,<3
|
|
22
22
|
Requires-Python: >=3.10, <4.0
|
|
23
23
|
Project-URL: Homepage, https://github.com/KRoperUK/dimplex-controller-py
|
|
24
24
|
Project-URL: Repository, https://github.com/KRoperUK/dimplex-controller-py
|
|
@@ -285,7 +285,9 @@ Main client class. Construct with an `aiohttp.ClientSession` and a `refresh_toke
|
|
|
285
285
|
| `set_setback_temperature(...)` | Write the setback temperature (untested). |
|
|
286
286
|
| `set_eco_start(hub_id, appliance_ids, enable)` | Toggle EcoStart. |
|
|
287
287
|
| `set_open_window_detection(hub_id, appliance_ids, enable)` | Toggle Open Window Detection. |
|
|
288
|
-
| `
|
|
288
|
+
| `set_hot_water_holiday(hub_id, appliance_ids, until, *, enable, heat_pump)` | Cylinder Holiday on/off (validated on an ASHW cylinder). |
|
|
289
|
+
| `get_heat_pump_hot_water_schedule(...)` | ASHW cylinder schedule as `HeatPumpHwcSchedule` (validated). Writing it answers 403. |
|
|
290
|
+
| `set_hot_water_*(...)` | Remaining cylinder controls (untested — no hardware). |
|
|
289
291
|
| `get_tsi_energy_report(hub_id, ...)` | Returns `TsiEnergyReport`. |
|
|
290
292
|
| `capabilities_for(appliance, *, status, product)` | Derive an `ApplianceCapabilities` matrix. |
|
|
291
293
|
| `export_tokens()` / `apply_tokens(bundle)` | Token persistence helpers. |
|
|
@@ -259,7 +259,9 @@ Main client class. Construct with an `aiohttp.ClientSession` and a `refresh_toke
|
|
|
259
259
|
| `set_setback_temperature(...)` | Write the setback temperature (untested). |
|
|
260
260
|
| `set_eco_start(hub_id, appliance_ids, enable)` | Toggle EcoStart. |
|
|
261
261
|
| `set_open_window_detection(hub_id, appliance_ids, enable)` | Toggle Open Window Detection. |
|
|
262
|
-
| `
|
|
262
|
+
| `set_hot_water_holiday(hub_id, appliance_ids, until, *, enable, heat_pump)` | Cylinder Holiday on/off (validated on an ASHW cylinder). |
|
|
263
|
+
| `get_heat_pump_hot_water_schedule(...)` | ASHW cylinder schedule as `HeatPumpHwcSchedule` (validated). Writing it answers 403. |
|
|
264
|
+
| `set_hot_water_*(...)` | Remaining cylinder controls (untested — no hardware). |
|
|
263
265
|
| `get_tsi_energy_report(hub_id, ...)` | Returns `TsiEnergyReport`. |
|
|
264
266
|
| `capabilities_for(appliance, *, status, product)` | Derive an `ApplianceCapabilities` matrix. |
|
|
265
267
|
| `export_tokens()` / `apply_tokens(bundle)` | Token persistence helpers. |
|
|
@@ -45,6 +45,8 @@ from .models import (
|
|
|
45
45
|
ApplianceModeStatus,
|
|
46
46
|
ApplianceStatus,
|
|
47
47
|
AutomaticProvisioning,
|
|
48
|
+
HeatPumpHwcPeriod,
|
|
49
|
+
HeatPumpHwcSchedule,
|
|
48
50
|
Hub,
|
|
49
51
|
HygieneFrequency,
|
|
50
52
|
ProductModel,
|
|
@@ -62,6 +64,7 @@ from .telemetry import (
|
|
|
62
64
|
EnergySummary,
|
|
63
65
|
filter_telemetry_points,
|
|
64
66
|
parse_telemetry_points,
|
|
67
|
+
reported_energy,
|
|
65
68
|
summarise_energy,
|
|
66
69
|
)
|
|
67
70
|
|
|
@@ -83,6 +86,8 @@ __all__ = [
|
|
|
83
86
|
"SetbackStatus",
|
|
84
87
|
"TimerMode",
|
|
85
88
|
"TimerModeSettings",
|
|
89
|
+
"HeatPumpHwcPeriod",
|
|
90
|
+
"HeatPumpHwcSchedule",
|
|
86
91
|
"TimerPeriod",
|
|
87
92
|
"AutomaticProvisioning",
|
|
88
93
|
"ProductModel",
|
|
@@ -98,6 +103,7 @@ __all__ = [
|
|
|
98
103
|
"NULL_DATETIME",
|
|
99
104
|
"parse_telemetry_points",
|
|
100
105
|
"filter_telemetry_points",
|
|
106
|
+
"reported_energy",
|
|
101
107
|
"summarise_energy",
|
|
102
108
|
"EnergySummary",
|
|
103
109
|
"VALUE_KEY_T1",
|
|
@@ -24,6 +24,14 @@ class ApplianceCapabilities:
|
|
|
24
24
|
Flags are best-effort: when status/product metadata is incomplete the
|
|
25
25
|
library prefers enabling known cloud control paths (boost/away/OWD) so
|
|
26
26
|
users are not locked out of working RPCs.
|
|
27
|
+
|
|
28
|
+
One flag is weaker than the rest and must not be used as a gate:
|
|
29
|
+
:attr:`energy_meter` is a *family hint* derived from the nameplate, so it is
|
|
30
|
+
true for essentially every appliance with a rated power. When the question is
|
|
31
|
+
"has this appliance ever reported energy?", ask the telemetry —
|
|
32
|
+
:func:`dimplex_controller.reported_energy`, or
|
|
33
|
+
:func:`dimplex_controller.summarise_energy` when the totals are wanted too
|
|
34
|
+
(dimplex-controller-py#113).
|
|
27
35
|
"""
|
|
28
36
|
|
|
29
37
|
boost: bool = True
|
|
@@ -39,6 +47,7 @@ class ApplianceCapabilities:
|
|
|
39
47
|
# setpoint path. Preferred over rewriting the schedule via SetTimerMode,
|
|
40
48
|
# which Quantum rejects with HTTP 403.
|
|
41
49
|
setpoint_write: bool = True
|
|
50
|
+
# Family hint, not proof of metering — see the class docstring before gating on it.
|
|
42
51
|
energy_meter: bool = False
|
|
43
52
|
storage: bool = False
|
|
44
53
|
hot_water: bool = False
|
|
@@ -162,15 +171,23 @@ def capabilities_for(
|
|
|
162
171
|
heat_pump = True
|
|
163
172
|
hot_water = True
|
|
164
173
|
|
|
165
|
-
|
|
166
|
-
|
|
174
|
+
if status is not None and status.AvailableHotWater is not None:
|
|
175
|
+
hot_water = True
|
|
176
|
+
|
|
177
|
+
# A cylinder reports its *water* target in ActiveSetPointTemperature (45 °C
|
|
178
|
+
# on a live ASHW cylinder), so only a room temperature makes it room climate.
|
|
179
|
+
# Without one, the room-heater features default to off — a live cylinder
|
|
180
|
+
# sends null for every one of them — unless its status reports them.
|
|
181
|
+
climate = not hot_water or (status is not None and status.RoomTemperature is not None)
|
|
182
|
+
|
|
183
|
+
boost = climate
|
|
184
|
+
away = climate
|
|
167
185
|
advance = True
|
|
168
|
-
open_window =
|
|
169
|
-
eco_start =
|
|
170
|
-
setback_read =
|
|
171
|
-
frost =
|
|
186
|
+
open_window = climate
|
|
187
|
+
eco_start = climate
|
|
188
|
+
setback_read = climate
|
|
189
|
+
frost = climate
|
|
172
190
|
timer = True
|
|
173
|
-
climate = not hot_water # cylinder-only appliances are not room climate
|
|
174
191
|
|
|
175
192
|
if status is not None:
|
|
176
193
|
if status.BoostDuration is not None or status.BoostTemperature is not None:
|
|
@@ -183,10 +200,6 @@ def capabilities_for(
|
|
|
183
200
|
eco_start = True
|
|
184
201
|
if status.SetbackEnabled is not None or status.SetbackTemperature is not None:
|
|
185
202
|
setback_read = True
|
|
186
|
-
if status.AvailableHotWater is not None:
|
|
187
|
-
hot_water = True
|
|
188
|
-
if status.RoomTemperature is not None or status.ActiveSetPointTemperature is not None:
|
|
189
|
-
climate = True
|
|
190
203
|
|
|
191
204
|
# Advance only means something for a scheduled room heater; a cylinder has
|
|
192
205
|
# no "next comfort period" to jump to.
|
|
@@ -200,10 +213,11 @@ def capabilities_for(
|
|
|
200
213
|
open_window=open_window,
|
|
201
214
|
eco_start=eco_start,
|
|
202
215
|
setback_read=setback_read,
|
|
203
|
-
setback_write=
|
|
216
|
+
setback_write=climate,
|
|
204
217
|
frost=frost,
|
|
205
218
|
timer=timer,
|
|
206
|
-
|
|
219
|
+
# The room setpoint endpoint; cylinders have their own (Set*TemperatureHwc).
|
|
220
|
+
setpoint_write=climate,
|
|
207
221
|
energy_meter=energy_meter,
|
|
208
222
|
storage=storage,
|
|
209
223
|
hot_water=hot_water,
|
|
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import json
|
|
5
5
|
import logging
|
|
6
|
+
import math
|
|
6
7
|
import random
|
|
7
8
|
from datetime import datetime, timedelta, timezone
|
|
8
9
|
from typing import Any
|
|
@@ -36,6 +37,7 @@ from .models import (
|
|
|
36
37
|
ApplianceModeSettings,
|
|
37
38
|
ApplianceModeStatus,
|
|
38
39
|
ApplianceStatus,
|
|
40
|
+
HeatPumpHwcSchedule,
|
|
39
41
|
Hub,
|
|
40
42
|
HygieneFrequency,
|
|
41
43
|
ProductModel,
|
|
@@ -840,6 +842,42 @@ class DimplexControl:
|
|
|
840
842
|
endpoint=endpoint,
|
|
841
843
|
)
|
|
842
844
|
|
|
845
|
+
async def set_hot_water_holiday(
|
|
846
|
+
self,
|
|
847
|
+
hub_id: str,
|
|
848
|
+
appliance_ids: list[str],
|
|
849
|
+
until: datetime | None = None,
|
|
850
|
+
*,
|
|
851
|
+
enable: bool = True,
|
|
852
|
+
heat_pump: bool = False,
|
|
853
|
+
) -> None:
|
|
854
|
+
"""Put a cylinder on Holiday (standby) until ``until``, or clear it.
|
|
855
|
+
|
|
856
|
+
Verified on a live ASHW cylinder (EDL200UK-630RF) with ``heat_pump=True``:
|
|
857
|
+
engaging moved ``ApplianceModes`` 8192 → 8200 and set
|
|
858
|
+
``HolidayNumOfDays``, heating stopped, and clearing restored 8192. The
|
|
859
|
+
cylinder counts holidays in whole days, so ``until`` is also sent as a
|
|
860
|
+
rounded-up day count; a caller that wants a time of day has to clear
|
|
861
|
+
Holiday itself at that time.
|
|
862
|
+
"""
|
|
863
|
+
days = 0
|
|
864
|
+
if enable:
|
|
865
|
+
if until is None:
|
|
866
|
+
raise ValueError("a holiday needs an end time")
|
|
867
|
+
end = until if until.tzinfo is not None else until.replace(tzinfo=timezone.utc)
|
|
868
|
+
seconds = (end - datetime.now(timezone.utc)).total_seconds()
|
|
869
|
+
days = max(1, math.ceil(seconds / 86400))
|
|
870
|
+
endpoint = "/RemoteControl/SetApplianceModeHeatPumpHwc" if heat_pump else "/RemoteControl/SetApplianceModeHwc"
|
|
871
|
+
await self.set_mode_flag(
|
|
872
|
+
hub_id,
|
|
873
|
+
appliance_ids,
|
|
874
|
+
ApplianceModeFlag.HOLIDAY,
|
|
875
|
+
enable=enable,
|
|
876
|
+
until=until,
|
|
877
|
+
number_of_days=days,
|
|
878
|
+
endpoint=endpoint,
|
|
879
|
+
)
|
|
880
|
+
|
|
843
881
|
async def set_hot_water_boost_temperature(
|
|
844
882
|
self, hub_id: str, appliance_ids: list[str], temperature: float, *, enable: bool = True
|
|
845
883
|
) -> None:
|
|
@@ -902,10 +940,12 @@ class DimplexControl:
|
|
|
902
940
|
endpoint=endpoint,
|
|
903
941
|
)
|
|
904
942
|
|
|
905
|
-
async def get_heat_pump_hot_water_schedule(self, hub_id: str, appliance_id: str) ->
|
|
906
|
-
"""Read an ASHW cylinder's schedule.
|
|
943
|
+
async def get_heat_pump_hot_water_schedule(self, hub_id: str, appliance_id: str) -> HeatPumpHwcSchedule:
|
|
944
|
+
"""Read an ASHW cylinder's weekly schedule.
|
|
907
945
|
|
|
908
|
-
|
|
946
|
+
Verified on a live ASHW cylinder (EDL200UK-630RF). The reply does not fit
|
|
947
|
+
:class:`TimerModeSettings` — there is no ``TimerMode`` and each period is
|
|
948
|
+
``{DayOfWeek, StartTime, Mode}`` — hence :class:`HeatPumpHwcSchedule`.
|
|
909
949
|
"""
|
|
910
950
|
payload = {"HubId": hub_id, "ApplianceId": appliance_id}
|
|
911
951
|
data = await self._request(
|
|
@@ -913,12 +953,16 @@ class DimplexControl:
|
|
|
913
953
|
"/RemoteControl/ApiGetTimerModeDetailsForHeatPumpHwcAppliance",
|
|
914
954
|
json=payload,
|
|
915
955
|
)
|
|
916
|
-
return
|
|
956
|
+
return HeatPumpHwcSchedule.model_validate(data)
|
|
917
957
|
|
|
918
958
|
async def set_heat_pump_hot_water_schedule(self, settings: TimerModeSettings) -> TimerModeSettings:
|
|
919
959
|
"""Write an ASHW cylinder's schedule periods.
|
|
920
960
|
|
|
921
|
-
.. warning::
|
|
961
|
+
.. warning:: Does not work yet. On a live ASHW cylinder this endpoint
|
|
962
|
+
answered HTTP 403 to every body tried — wrapped in ``TimerModeSettings``
|
|
963
|
+
or ``Settings``, with or without ``TimerMode``, integer or ``HH:MM:SS``
|
|
964
|
+
times, and app versions up to 3.0.0 — while unwrapped bodies got 400.
|
|
965
|
+
The request the official app sends has not been captured.
|
|
922
966
|
"""
|
|
923
967
|
payload = {"TimerModeSettings": settings.model_dump(mode="json")}
|
|
924
968
|
await self._request("POST", "/RemoteControl/UpdateHeatPumpHwcSchedulePeriods", json=payload)
|
|
@@ -255,6 +255,31 @@ class TimerModeSettings(BaseModel):
|
|
|
255
255
|
TimerPeriods: list[TimerPeriod] = Field(default_factory=list)
|
|
256
256
|
|
|
257
257
|
|
|
258
|
+
class HeatPumpHwcPeriod(BaseModel):
|
|
259
|
+
"""One period of an ASHW cylinder's weekly schedule.
|
|
260
|
+
|
|
261
|
+
A live cylinder returns one ``{DayOfWeek, StartTime: 0, Mode: 2}`` per day.
|
|
262
|
+
``DayOfWeek`` is .NET's (0 = Sunday). ``StartTime`` is an integer, presumed
|
|
263
|
+
minutes after midnight, and what each ``Mode`` value means is not yet known.
|
|
264
|
+
"""
|
|
265
|
+
|
|
266
|
+
DayOfWeek: int
|
|
267
|
+
StartTime: int
|
|
268
|
+
Mode: int
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
class HeatPumpHwcSchedule(BaseModel):
|
|
272
|
+
"""``ApiGetTimerModeDetailsForHeatPumpHwcAppliance`` reply.
|
|
273
|
+
|
|
274
|
+
Unlike the room-heater :class:`TimerModeSettings` it has no ``TimerMode``
|
|
275
|
+
and its periods carry a mode instead of an end time and temperature.
|
|
276
|
+
"""
|
|
277
|
+
|
|
278
|
+
HubId: str
|
|
279
|
+
ApplianceId: str
|
|
280
|
+
TimerPeriods: list[HeatPumpHwcPeriod] = Field(default_factory=list)
|
|
281
|
+
|
|
282
|
+
|
|
258
283
|
class UserContext(BaseModel):
|
|
259
284
|
Id: str
|
|
260
285
|
EmailAddress: str | None = None
|
|
@@ -286,6 +311,21 @@ class ApplianceStatus(BaseModel):
|
|
|
286
311
|
LockStatus: int | None = None
|
|
287
312
|
ErrorCode: str | None = None
|
|
288
313
|
WarningCode: str | None = None
|
|
314
|
+
# Hot-water cylinder fields, as a live ASHW cylinder (EDL200UK-630RF) sends
|
|
315
|
+
# them. AvailableHotWater above is litres of usable hot water (the app shows
|
|
316
|
+
# it as "336 L Hot water").
|
|
317
|
+
Capacity: int | None = None # tank size, litres
|
|
318
|
+
HeatingUp: bool | None = None
|
|
319
|
+
IsGreenEnergyActive: bool | None = None
|
|
320
|
+
HygieneFrequency: int | None = None # EFrequency, see HygieneFrequency
|
|
321
|
+
HygieneTemperature: float | None = None
|
|
322
|
+
HolidayNumOfDays: int | None = None
|
|
323
|
+
# Present in the same reply, always null on the cylinder seen so far.
|
|
324
|
+
DelayedStartTime: str | None = None
|
|
325
|
+
DsmStatus: int | None = None
|
|
326
|
+
ModifiedPower: float | None = None
|
|
327
|
+
ConnectedChargeProfiles: Any = None
|
|
328
|
+
StandaloneChargeProfiles: Any = None
|
|
289
329
|
|
|
290
330
|
@field_validator("ErrorCode", "WarningCode", mode="before")
|
|
291
331
|
@classmethod
|
|
@@ -222,6 +222,34 @@ def parse_telemetry_points(
|
|
|
222
222
|
return out
|
|
223
223
|
|
|
224
224
|
|
|
225
|
+
def reported_energy(
|
|
226
|
+
points: Any,
|
|
227
|
+
*,
|
|
228
|
+
value_keys: tuple[str, ...] | None = None,
|
|
229
|
+
) -> bool:
|
|
230
|
+
"""Return True when telemetry carries usable points for an appliance.
|
|
231
|
+
|
|
232
|
+
The evidence-based counterpart to
|
|
233
|
+
:attr:`ApplianceCapabilities.energy_meter <.capabilities.ApplianceCapabilities>`,
|
|
234
|
+
which is a family hint derived from the nameplate and is therefore true for
|
|
235
|
+
essentially every appliance — it cannot separate a genuinely metered one from an
|
|
236
|
+
appliance the hub never reports energy for (dimplex-controller-py#113).
|
|
237
|
+
|
|
238
|
+
Pass the appliance's entry from
|
|
239
|
+
:attr:`TsiEnergyReport.ApplianceTelemetryData
|
|
240
|
+
<.models.TsiEnergyReport>` (raw entries are normalised by
|
|
241
|
+
:func:`parse_telemetry_points`, so both raw and already-parsed points are
|
|
242
|
+
accepted). Nothing recognised, an empty list, or a value the parser skips all
|
|
243
|
+
answer ``False`` — which is the question a consumer gating entity creation
|
|
244
|
+
actually has.
|
|
245
|
+
|
|
246
|
+
Only presence is decided here. A caller that also wants the figures should call
|
|
247
|
+
:func:`parse_telemetry_points` or :func:`summarise_energy` once and test the
|
|
248
|
+
result, rather than parse a series twice.
|
|
249
|
+
"""
|
|
250
|
+
return bool(parse_telemetry_points(points, value_keys=value_keys))
|
|
251
|
+
|
|
252
|
+
|
|
225
253
|
def _ensure_aware(ts: datetime, default_tz: tzinfo) -> datetime:
|
|
226
254
|
"""Return ``ts`` with a timezone; naive datetimes are assumed ``default_tz``."""
|
|
227
255
|
if ts.tzinfo is None:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "dimplex-controller"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.15.0"
|
|
4
4
|
description = "Unofficial async Python client for Dimplex (GDHV IoT) heating appliances — not affiliated with Glen Dimplex"
|
|
5
5
|
license = "MIT"
|
|
6
6
|
license-files = ["LICENSE"]
|
|
@@ -27,8 +27,8 @@ classifiers = [
|
|
|
27
27
|
"Topic :: Home Automation",
|
|
28
28
|
]
|
|
29
29
|
dependencies = [
|
|
30
|
-
"aiohttp>=3.
|
|
31
|
-
"pydantic>=2.
|
|
30
|
+
"aiohttp>=3.9.0,<4",
|
|
31
|
+
"pydantic>=2.0.0,<3",
|
|
32
32
|
]
|
|
33
33
|
|
|
34
34
|
[[project.authors]]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "dimplex-controller"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.15.0"
|
|
4
4
|
description = "Unofficial async Python client for Dimplex (GDHV IoT) heating appliances — not affiliated with Glen Dimplex"
|
|
5
5
|
authors = [{ name = "Kieran Roper" }]
|
|
6
6
|
license = "MIT"
|
|
@@ -21,12 +21,21 @@ classifiers = [
|
|
|
21
21
|
"Programming Language :: Python :: 3.14",
|
|
22
22
|
"Topic :: Home Automation",
|
|
23
23
|
]
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
# `>=X,<next-major`), so consumers gain no surprise from the migration.
|
|
24
|
+
# The upper bounds keep the previous caret semantics (a caret range is
|
|
25
|
+
# `>=X,<next-major`), so consumers gain no surprise from the uv migration.
|
|
27
26
|
dependencies = [
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
# Lower bounds are what the code needs, not what uv.lock happens to resolve.
|
|
28
|
+
# They were raised to the locked versions when the project migrated to uv and
|
|
29
|
+
# bumped everything in one pass (#110), which made this library uninstallable
|
|
30
|
+
# alongside Home Assistant: the integration's test stack pins pydantic 2.13.4
|
|
31
|
+
# while this demanded >=2.13.5, so `dimplex-controller>=0.14.0` resolved to
|
|
32
|
+
# nothing and the HA integration could not adopt the 0.14.0 ErrorCode/WarningCode
|
|
33
|
+
# fix (dimplex-controller-hass#256).
|
|
34
|
+
#
|
|
35
|
+
# Nothing here needs either new API: plain ``BaseModel``, ``field_validator``,
|
|
36
|
+
# ``ConfigDict`` and an aiohttp ``ClientSession`` are all 2.0/3.9-era.
|
|
37
|
+
"aiohttp>=3.9.0,<4",
|
|
38
|
+
"pydantic>=2.0.0,<3",
|
|
30
39
|
]
|
|
31
40
|
|
|
32
41
|
[project.urls]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|