dimplex-controller 0.5.0__tar.gz → 0.7.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.
@@ -0,0 +1,293 @@
1
+ Metadata-Version: 2.4
2
+ Name: dimplex-controller
3
+ Version: 0.7.0
4
+ Summary: Python client for Dimplex heating controllers (GDHV IoT)
5
+ License: MIT
6
+ License-File: LICENSE
7
+ Keywords: dimplex,heating,control,iot,asyncio
8
+ Author: Kieran Roper
9
+ Requires-Python: >=3.10,<4.0
10
+ Classifier: Development Status :: 5 - Production/Stable
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Natural Language :: English
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Programming Language :: Python :: 3.14
21
+ Classifier: Topic :: Home Automation
22
+ Requires-Dist: aiohttp (>=3.9.0,<4.0.0)
23
+ Requires-Dist: beautifulsoup4 (>=4.14.3,<5.0.0)
24
+ Requires-Dist: pydantic (>=2.0.0,<3.0.0)
25
+ Requires-Dist: python-dotenv (>=1.2.1,<2.0.0)
26
+ Project-URL: Homepage, https://github.com/KRoperUK/dimplex-controller-py
27
+ Project-URL: Repository, https://github.com/KRoperUK/dimplex-controller-py
28
+ Description-Content-Type: text/markdown
29
+
30
+ # Dimplex Controller Python Client
31
+
32
+ [![PyPI version](https://img.shields.io/pypi/v/dimplex-controller.svg)](https://pypi.org/project/dimplex-controller/)
33
+ [![Python versions](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/downloads/)
34
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
35
+ [![CI Tests](https://github.com/KRoperUK/dimplex-controller-py/actions/workflows/tests.yml/badge.svg)](https://github.com/KRoperUK/dimplex-controller-py/actions)
36
+ [![Downloads](https://img.shields.io/pypi/dm/dimplex-controller.svg)](https://pypi.org/project/dimplex-controller/)
37
+
38
+ <p align="center">
39
+ <strong>Async Python client for controlling Glen Dimplex Heating &amp; Ventilation (GDHV) appliances via the Dimplex cloud API.</strong>
40
+ </p>
41
+
42
+ ---
43
+
44
+ ## What does this do?
45
+
46
+ `dimplex-controller-py` is an asynchronous Python client that talks to the GDHV IoT cloud platform. It handles Azure B2C authentication (including automatic token refresh), discovers your Hubs, Zones and Appliances, and lets you read telemetry and send control commands — all from a script or a larger application.
47
+
48
+ It is the engine behind the [Dimplex Hub Home Assistant integration](https://github.com/KRoperUK/dimplex-controller-hass) and is published to PyPI as [`dimplex-controller`](https://pypi.org/project/dimplex-controller/).
49
+
50
+ > **Note:** This is an unofficial library and is not affiliated with or endorsed by Glen Dimplex Heating & Ventilation (GDHV). Use it at your own risk.
51
+
52
+ ## Contents
53
+
54
+ - [Features](#features)
55
+ - [Installation](#installation)
56
+ - [Quick start](#quick-start)
57
+ - [Usage guide](#usage-guide)
58
+ - [Authentication](#authentication)
59
+ - [Discovery](#discovery)
60
+ - [Reading status](#reading-status)
61
+ - [Sending control commands](#sending-control-commands)
62
+ - [Energy reports](#energy-reports)
63
+ - [Configuration](#configuration)
64
+ - [API reference](#api-reference)
65
+ - [Troubleshooting](#troubleshooting)
66
+ - [Contributing](#contributing)
67
+ - [Changelog](#changelog)
68
+
69
+ ## Features
70
+
71
+ - **Authentication** — Azure B2C login with automatic token refresh and secure token persistence.
72
+ - **Discovery** — List Hubs, Zones and Appliances linked to your account.
73
+ - **Real-time status** — Fetch room temperature, setpoints, comfort status, boost/away modes and EcoStart state.
74
+ - **Control** — Set operation modes, activate Boost and Away, toggle EcoStart and Open Window Detection, and programme timer schedules.
75
+ - **Energy telemetry** — Pull Time Series Insights (TSI) energy reports with a robust telemetry parser that adapts to varying firmware formats.
76
+
77
+ ## Installation
78
+
79
+ ### From PyPI (recommended)
80
+
81
+ ```bash
82
+ pip install dimplex-controller
83
+ ```
84
+
85
+ ### From source
86
+
87
+ ```bash
88
+ git clone https://github.com/KRoperUK/dimplex-controller-py.git
89
+ cd dimplex-controller-py
90
+ pip install .
91
+ ```
92
+
93
+ ### Development install
94
+
95
+ ```bash
96
+ git clone https://github.com/KRoperUK/dimplex-controller-py.git
97
+ cd dimplex-controller-py
98
+ pip install -e ".[dev]"
99
+ ```
100
+
101
+ > **Requires:** Python 3.10 or later.
102
+
103
+ ## Quick start
104
+
105
+ The library uses `asyncio` and `aiohttp`. Here is the smallest example that lists your Hubs and Zones:
106
+
107
+ ```python
108
+ import asyncio
109
+ from aiohttp import ClientSession
110
+ from dimplex_controller import DimplexControl
111
+
112
+ async def main() -> None:
113
+ async with ClientSession() as session:
114
+ client = DimplexControl(session, refresh_token="YOUR_REFRESH_TOKEN")
115
+
116
+ hubs = await client.get_hubs()
117
+ for hub in hubs:
118
+ print(f"Hub: {hub.Name}")
119
+ zones = await client.get_hub_zones(hub.HubId)
120
+ for zone in zones:
121
+ print(f" Zone: {zone.ZoneName}")
122
+
123
+ if __name__ == "__main__":
124
+ asyncio.run(main())
125
+ ```
126
+
127
+ ## Usage guide
128
+
129
+ ### Authentication
130
+
131
+ Dimplex uses Azure AD B2C. You cannot log in purely programmatically with just a username and password — the library must first capture a short-lived authorisation code.
132
+
133
+ The recommended approach is to run the included demo script once:
134
+
135
+ ```bash
136
+ python demo.py
137
+ ```
138
+
139
+ The script walks you through opening a browser, signing in, and pasting the resulting redirect URL back into the terminal. On success, it saves a `dimplex_tokens.json` file in the current working directory.
140
+
141
+ Subsequent runs read the refresh token from that file automatically. You can also pass the refresh token directly to `DimplexControl` if you prefer to manage storage yourself.
142
+
143
+ ### Discovery
144
+
145
+ ```python
146
+ hubs = await client.get_hubs()
147
+ for hub in hubs:
148
+ print(f"Hub: {hub.Name} ({hub.HubId})")
149
+ zones = await client.get_hub_zones(hub.HubId)
150
+ for zone in zones:
151
+ print(f" Zone: {zone.ZoneName} ({zone.ZoneId})")
152
+ appliances = zone.Appliances
153
+ for appliance in appliances:
154
+ print(f" Appliance: {appliance.ApplianceId}")
155
+ ```
156
+
157
+ ### Reading status
158
+
159
+ ```python
160
+ from dimplex_controller.models import ApplianceStatus
161
+
162
+ status_list = await client.get_appliance_overview(hub_id, [appliance_id_1, appliance_id_2])
163
+
164
+ for status in status_list:
165
+ print(f"Room temperature: {status.RoomTemperature}°C")
166
+ print(f"Target temperature: {status.ActiveSetPointTemperature}°C")
167
+ print(f"EcoStart enabled: {status.EcoStartEnabled}")
168
+ print(f"Comfort status: {status.ComfortStatus}")
169
+ ```
170
+
171
+ ### Sending control commands
172
+
173
+ ```python
174
+ from dimplex_controller.models import ApplianceModeSettings
175
+
176
+ # Enable EcoStart
177
+ await client.set_eco_start(hub_id, [appliance_id], True)
178
+
179
+ # Enable Open Window Detection
180
+ await client.set_open_window_detection(hub_id, [appliance_id], True)
181
+
182
+ # Activate Boost
183
+ await client.set_boost(hub_id, [appliance_id], temperature=25.0, duration_minutes=60)
184
+
185
+ # Set target temperature (rewrites timer period setpoints)
186
+ await client.set_target_temperature(hub_id, appliance_id, 21.5)
187
+ ```
188
+
189
+ ### Energy reports
190
+
191
+ ```python
192
+ from dimplex_controller import parse_telemetry_points, summarise_energy
193
+
194
+ report = await client.get_tsi_energy_report(hub_id)
195
+ for appliance_id, telemetry in report.ApplianceTelemetryData.items():
196
+ points = parse_telemetry_points(telemetry)
197
+ daily = summarise_energy(points, mode="daily")
198
+ lifetime = summarise_energy(points, mode="lifetime")
199
+ print(f"{appliance_id}: today={daily.total_kwh} kWh, lifetime={lifetime.total_kwh} kWh")
200
+ ```
201
+
202
+ `parse_telemetry_points` normalises firmware-varying point shapes. `summarise_energy` builds **daily** (local midnight) and **lifetime** totals. With `include_previous_period=True` the cloud often returns full history — filter client-side rather than trusting `days_back` alone.
203
+
204
+ ## Configuration
205
+
206
+ | Environment variable | Purpose |
207
+ |---------------------|---------|
208
+ | `DIMPLEX_TOKENS_FILE` | Path to the JSON token store. Defaults to `dimplex_tokens.json`. |
209
+
210
+ ## API reference
211
+
212
+ ### `DimplexControl`
213
+
214
+ Main client class. Construct with an `aiohttp.ClientSession` and a `refresh_token`.
215
+
216
+ | Method | Description |
217
+ |--------|-------------|
218
+ | `get_hubs()` | Returns `list[Hub]`. |
219
+ | `get_hub_zones(hub_id)` | Returns `list[Zone]` for a Hub. |
220
+ | `get_zone(hub_id, zone_id)` | Returns a single `Zone`. |
221
+ | `get_appliance_overview(hub_id, appliance_ids)` | Returns `list[ApplianceStatus]`. |
222
+ | `get_user_context()` | Returns `UserContext`. |
223
+ | `get_appliance_features(hub_id, appliance_ids)` | Returns raw appliance feature data. |
224
+ | `set_mode(hub_id, appliance_ids, mode, temperature)` | Set operation mode. |
225
+ | `set_target_temperature(...)` | Placeholder for future target temperature control. |
226
+ | `set_appliance_mode(hub_id, appliance_ids, settings)` | Send full mode settings. |
227
+ | `set_eco_start(hub_id, appliance_ids, enabled)` | Toggle EcoStart. |
228
+ | `set_open_window_detection(hub_id, appliance_ids, enabled)` | Toggle Open Window Detection. |
229
+ | `get_tsi_energy_report(hub_id)` | Returns `TsiEnergyReport`. |
230
+
231
+ ### Models
232
+
233
+ - **`Hub`** — Hub metadata.
234
+ - **`Zone`** — Zone metadata with linked Appliances.
235
+ - **`Appliance`** — Appliance metadata.
236
+ - **`ApplianceStatus`** — Live telemetry (room temperature, setpoints, comfort, etc.).
237
+ - **`ApplianceModeSettings`** — Payload for mode changes.
238
+ - **`TimerPeriod`** / **`TimerModeSettings`** — Timer schedule structures.
239
+ - **`UserContext`** — Authenticated user profile.
240
+ - **`TsiEnergyReport`** — Energy telemetry keyed by appliance.
241
+
242
+ ### Exceptions
243
+
244
+ - **`DimplexError`** — Base exception.
245
+ - **`DimplexAuthError`** — Authentication or token errors.
246
+ - **`DimplexApiError`** — API returned a non-success status. Contains `status` and `message`.
247
+ - **`DimplexConnectionError`** — Network-level failures.
248
+
249
+ ## Troubleshooting
250
+
251
+ ### Authentication failures
252
+
253
+ - Verify that the refresh token in `dimplex_tokens.json` has not expired. Delete the file and re-run `demo.py` to capture a fresh one.
254
+ - Ensure your network can reach `login.microsoftonline.com` and the Dimplex API endpoints.
255
+ - If you have multi-factor authentication (MFA) enabled on your Dimplex account, the headless flow should still work because it uses a browser session you control manually.
256
+
257
+ ### `DimplexAuthError`
258
+
259
+ This means the API rejected the token. Common causes:
260
+ - Token file is missing or corrupt.
261
+ - The refresh token has expired (Azure B2C refresh tokens typically last 90 days).
262
+ - The token was revoked from the Azure portal.
263
+
264
+ **Fix:** Delete `dimplex_tokens.json` and re-run the `demo.py` flow.
265
+
266
+ ### `DimplexConnectionError`
267
+
268
+ The library could not reach the GDHV API. Check:
269
+ - Internet connectivity.
270
+ - DNS resolution for `api.gdhv.io` (or whatever endpoint is configured in `const.py`).
271
+ - No corporate firewall or proxy is blocking `HTTPS` traffic.
272
+
273
+ ### Telemetry parsing errors
274
+
275
+ If `parse_telemetry_points` returns an empty list, the API likely returned an unexpected schema for your firmware version. Please open an issue with a redacted example of the raw response so the parser can be updated.
276
+
277
+ ### Rate limiting
278
+
279
+ The GDHV cloud API has rate limits. If you hit them, back off for a few minutes before retrying. The library does not currently implement automatic retries with back-off.
280
+
281
+ ## Contributing
282
+
283
+ Contributions are welcome! Please read the [contributing guidelines](CONTRIBUTING.md) before opening a pull request.
284
+
285
+ Key points:
286
+ - Use **Conventional Commits** (`feat:`, `fix:`, `chore:`, etc.) — this drives the automated changelog and PyPI releases.
287
+ - Run `ruff check`, `ruff format --check` and `pytest` locally before pushing.
288
+ - Pre-commit hooks are available — run `pre-commit install` once.
289
+
290
+ ## Changelog
291
+
292
+ See [CHANGELOG.md](CHANGELOG.md) for version history.
293
+
@@ -0,0 +1,263 @@
1
+ # Dimplex Controller Python Client
2
+
3
+ [![PyPI version](https://img.shields.io/pypi/v/dimplex-controller.svg)](https://pypi.org/project/dimplex-controller/)
4
+ [![Python versions](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/downloads/)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
6
+ [![CI Tests](https://github.com/KRoperUK/dimplex-controller-py/actions/workflows/tests.yml/badge.svg)](https://github.com/KRoperUK/dimplex-controller-py/actions)
7
+ [![Downloads](https://img.shields.io/pypi/dm/dimplex-controller.svg)](https://pypi.org/project/dimplex-controller/)
8
+
9
+ <p align="center">
10
+ <strong>Async Python client for controlling Glen Dimplex Heating &amp; Ventilation (GDHV) appliances via the Dimplex cloud API.</strong>
11
+ </p>
12
+
13
+ ---
14
+
15
+ ## What does this do?
16
+
17
+ `dimplex-controller-py` is an asynchronous Python client that talks to the GDHV IoT cloud platform. It handles Azure B2C authentication (including automatic token refresh), discovers your Hubs, Zones and Appliances, and lets you read telemetry and send control commands — all from a script or a larger application.
18
+
19
+ It is the engine behind the [Dimplex Hub Home Assistant integration](https://github.com/KRoperUK/dimplex-controller-hass) and is published to PyPI as [`dimplex-controller`](https://pypi.org/project/dimplex-controller/).
20
+
21
+ > **Note:** This is an unofficial library and is not affiliated with or endorsed by Glen Dimplex Heating & Ventilation (GDHV). Use it at your own risk.
22
+
23
+ ## Contents
24
+
25
+ - [Features](#features)
26
+ - [Installation](#installation)
27
+ - [Quick start](#quick-start)
28
+ - [Usage guide](#usage-guide)
29
+ - [Authentication](#authentication)
30
+ - [Discovery](#discovery)
31
+ - [Reading status](#reading-status)
32
+ - [Sending control commands](#sending-control-commands)
33
+ - [Energy reports](#energy-reports)
34
+ - [Configuration](#configuration)
35
+ - [API reference](#api-reference)
36
+ - [Troubleshooting](#troubleshooting)
37
+ - [Contributing](#contributing)
38
+ - [Changelog](#changelog)
39
+
40
+ ## Features
41
+
42
+ - **Authentication** — Azure B2C login with automatic token refresh and secure token persistence.
43
+ - **Discovery** — List Hubs, Zones and Appliances linked to your account.
44
+ - **Real-time status** — Fetch room temperature, setpoints, comfort status, boost/away modes and EcoStart state.
45
+ - **Control** — Set operation modes, activate Boost and Away, toggle EcoStart and Open Window Detection, and programme timer schedules.
46
+ - **Energy telemetry** — Pull Time Series Insights (TSI) energy reports with a robust telemetry parser that adapts to varying firmware formats.
47
+
48
+ ## Installation
49
+
50
+ ### From PyPI (recommended)
51
+
52
+ ```bash
53
+ pip install dimplex-controller
54
+ ```
55
+
56
+ ### From source
57
+
58
+ ```bash
59
+ git clone https://github.com/KRoperUK/dimplex-controller-py.git
60
+ cd dimplex-controller-py
61
+ pip install .
62
+ ```
63
+
64
+ ### Development install
65
+
66
+ ```bash
67
+ git clone https://github.com/KRoperUK/dimplex-controller-py.git
68
+ cd dimplex-controller-py
69
+ pip install -e ".[dev]"
70
+ ```
71
+
72
+ > **Requires:** Python 3.10 or later.
73
+
74
+ ## Quick start
75
+
76
+ The library uses `asyncio` and `aiohttp`. Here is the smallest example that lists your Hubs and Zones:
77
+
78
+ ```python
79
+ import asyncio
80
+ from aiohttp import ClientSession
81
+ from dimplex_controller import DimplexControl
82
+
83
+ async def main() -> None:
84
+ async with ClientSession() as session:
85
+ client = DimplexControl(session, refresh_token="YOUR_REFRESH_TOKEN")
86
+
87
+ hubs = await client.get_hubs()
88
+ for hub in hubs:
89
+ print(f"Hub: {hub.Name}")
90
+ zones = await client.get_hub_zones(hub.HubId)
91
+ for zone in zones:
92
+ print(f" Zone: {zone.ZoneName}")
93
+
94
+ if __name__ == "__main__":
95
+ asyncio.run(main())
96
+ ```
97
+
98
+ ## Usage guide
99
+
100
+ ### Authentication
101
+
102
+ Dimplex uses Azure AD B2C. You cannot log in purely programmatically with just a username and password — the library must first capture a short-lived authorisation code.
103
+
104
+ The recommended approach is to run the included demo script once:
105
+
106
+ ```bash
107
+ python demo.py
108
+ ```
109
+
110
+ The script walks you through opening a browser, signing in, and pasting the resulting redirect URL back into the terminal. On success, it saves a `dimplex_tokens.json` file in the current working directory.
111
+
112
+ Subsequent runs read the refresh token from that file automatically. You can also pass the refresh token directly to `DimplexControl` if you prefer to manage storage yourself.
113
+
114
+ ### Discovery
115
+
116
+ ```python
117
+ hubs = await client.get_hubs()
118
+ for hub in hubs:
119
+ print(f"Hub: {hub.Name} ({hub.HubId})")
120
+ zones = await client.get_hub_zones(hub.HubId)
121
+ for zone in zones:
122
+ print(f" Zone: {zone.ZoneName} ({zone.ZoneId})")
123
+ appliances = zone.Appliances
124
+ for appliance in appliances:
125
+ print(f" Appliance: {appliance.ApplianceId}")
126
+ ```
127
+
128
+ ### Reading status
129
+
130
+ ```python
131
+ from dimplex_controller.models import ApplianceStatus
132
+
133
+ status_list = await client.get_appliance_overview(hub_id, [appliance_id_1, appliance_id_2])
134
+
135
+ for status in status_list:
136
+ print(f"Room temperature: {status.RoomTemperature}°C")
137
+ print(f"Target temperature: {status.ActiveSetPointTemperature}°C")
138
+ print(f"EcoStart enabled: {status.EcoStartEnabled}")
139
+ print(f"Comfort status: {status.ComfortStatus}")
140
+ ```
141
+
142
+ ### Sending control commands
143
+
144
+ ```python
145
+ from dimplex_controller.models import ApplianceModeSettings
146
+
147
+ # Enable EcoStart
148
+ await client.set_eco_start(hub_id, [appliance_id], True)
149
+
150
+ # Enable Open Window Detection
151
+ await client.set_open_window_detection(hub_id, [appliance_id], True)
152
+
153
+ # Activate Boost
154
+ await client.set_boost(hub_id, [appliance_id], temperature=25.0, duration_minutes=60)
155
+
156
+ # Set target temperature (rewrites timer period setpoints)
157
+ await client.set_target_temperature(hub_id, appliance_id, 21.5)
158
+ ```
159
+
160
+ ### Energy reports
161
+
162
+ ```python
163
+ from dimplex_controller import parse_telemetry_points, summarise_energy
164
+
165
+ report = await client.get_tsi_energy_report(hub_id)
166
+ for appliance_id, telemetry in report.ApplianceTelemetryData.items():
167
+ points = parse_telemetry_points(telemetry)
168
+ daily = summarise_energy(points, mode="daily")
169
+ lifetime = summarise_energy(points, mode="lifetime")
170
+ print(f"{appliance_id}: today={daily.total_kwh} kWh, lifetime={lifetime.total_kwh} kWh")
171
+ ```
172
+
173
+ `parse_telemetry_points` normalises firmware-varying point shapes. `summarise_energy` builds **daily** (local midnight) and **lifetime** totals. With `include_previous_period=True` the cloud often returns full history — filter client-side rather than trusting `days_back` alone.
174
+
175
+ ## Configuration
176
+
177
+ | Environment variable | Purpose |
178
+ |---------------------|---------|
179
+ | `DIMPLEX_TOKENS_FILE` | Path to the JSON token store. Defaults to `dimplex_tokens.json`. |
180
+
181
+ ## API reference
182
+
183
+ ### `DimplexControl`
184
+
185
+ Main client class. Construct with an `aiohttp.ClientSession` and a `refresh_token`.
186
+
187
+ | Method | Description |
188
+ |--------|-------------|
189
+ | `get_hubs()` | Returns `list[Hub]`. |
190
+ | `get_hub_zones(hub_id)` | Returns `list[Zone]` for a Hub. |
191
+ | `get_zone(hub_id, zone_id)` | Returns a single `Zone`. |
192
+ | `get_appliance_overview(hub_id, appliance_ids)` | Returns `list[ApplianceStatus]`. |
193
+ | `get_user_context()` | Returns `UserContext`. |
194
+ | `get_appliance_features(hub_id, appliance_ids)` | Returns raw appliance feature data. |
195
+ | `set_mode(hub_id, appliance_ids, mode, temperature)` | Set operation mode. |
196
+ | `set_target_temperature(...)` | Placeholder for future target temperature control. |
197
+ | `set_appliance_mode(hub_id, appliance_ids, settings)` | Send full mode settings. |
198
+ | `set_eco_start(hub_id, appliance_ids, enabled)` | Toggle EcoStart. |
199
+ | `set_open_window_detection(hub_id, appliance_ids, enabled)` | Toggle Open Window Detection. |
200
+ | `get_tsi_energy_report(hub_id)` | Returns `TsiEnergyReport`. |
201
+
202
+ ### Models
203
+
204
+ - **`Hub`** — Hub metadata.
205
+ - **`Zone`** — Zone metadata with linked Appliances.
206
+ - **`Appliance`** — Appliance metadata.
207
+ - **`ApplianceStatus`** — Live telemetry (room temperature, setpoints, comfort, etc.).
208
+ - **`ApplianceModeSettings`** — Payload for mode changes.
209
+ - **`TimerPeriod`** / **`TimerModeSettings`** — Timer schedule structures.
210
+ - **`UserContext`** — Authenticated user profile.
211
+ - **`TsiEnergyReport`** — Energy telemetry keyed by appliance.
212
+
213
+ ### Exceptions
214
+
215
+ - **`DimplexError`** — Base exception.
216
+ - **`DimplexAuthError`** — Authentication or token errors.
217
+ - **`DimplexApiError`** — API returned a non-success status. Contains `status` and `message`.
218
+ - **`DimplexConnectionError`** — Network-level failures.
219
+
220
+ ## Troubleshooting
221
+
222
+ ### Authentication failures
223
+
224
+ - Verify that the refresh token in `dimplex_tokens.json` has not expired. Delete the file and re-run `demo.py` to capture a fresh one.
225
+ - Ensure your network can reach `login.microsoftonline.com` and the Dimplex API endpoints.
226
+ - If you have multi-factor authentication (MFA) enabled on your Dimplex account, the headless flow should still work because it uses a browser session you control manually.
227
+
228
+ ### `DimplexAuthError`
229
+
230
+ This means the API rejected the token. Common causes:
231
+ - Token file is missing or corrupt.
232
+ - The refresh token has expired (Azure B2C refresh tokens typically last 90 days).
233
+ - The token was revoked from the Azure portal.
234
+
235
+ **Fix:** Delete `dimplex_tokens.json` and re-run the `demo.py` flow.
236
+
237
+ ### `DimplexConnectionError`
238
+
239
+ The library could not reach the GDHV API. Check:
240
+ - Internet connectivity.
241
+ - DNS resolution for `api.gdhv.io` (or whatever endpoint is configured in `const.py`).
242
+ - No corporate firewall or proxy is blocking `HTTPS` traffic.
243
+
244
+ ### Telemetry parsing errors
245
+
246
+ If `parse_telemetry_points` returns an empty list, the API likely returned an unexpected schema for your firmware version. Please open an issue with a redacted example of the raw response so the parser can be updated.
247
+
248
+ ### Rate limiting
249
+
250
+ The GDHV cloud API has rate limits. If you hit them, back off for a few minutes before retrying. The library does not currently implement automatic retries with back-off.
251
+
252
+ ## Contributing
253
+
254
+ Contributions are welcome! Please read the [contributing guidelines](CONTRIBUTING.md) before opening a pull request.
255
+
256
+ Key points:
257
+ - Use **Conventional Commits** (`feat:`, `fix:`, `chore:`, etc.) — this drives the automated changelog and PyPI releases.
258
+ - Run `ruff check`, `ruff format --check` and `pytest` locally before pushing.
259
+ - Pre-commit hooks are available — run `pre-commit install` once.
260
+
261
+ ## Changelog
262
+
263
+ See [CHANGELOG.md](CHANGELOG.md) for version history.
@@ -0,0 +1,50 @@
1
+ """Dimplex Controller Client."""
2
+
3
+ from .auth import TokenBundle
4
+ from .client import DimplexControl
5
+ from .exceptions import DimplexApiError, DimplexAuthError, DimplexConnectionError, DimplexError
6
+ from .models import (
7
+ Appliance,
8
+ ApplianceModeFlag,
9
+ ApplianceModeSettings,
10
+ ApplianceStatus,
11
+ AutomaticProvisioning,
12
+ Hub,
13
+ TimerMode,
14
+ TimerModeSettings,
15
+ TimerPeriod,
16
+ TsiEnergyReport,
17
+ Zone,
18
+ )
19
+ from .telemetry import (
20
+ VALUE_KEY_T2,
21
+ EnergySummary,
22
+ filter_telemetry_points,
23
+ parse_telemetry_points,
24
+ summarise_energy,
25
+ )
26
+
27
+ __all__ = [
28
+ "DimplexControl",
29
+ "TokenBundle",
30
+ "Hub",
31
+ "Zone",
32
+ "Appliance",
33
+ "ApplianceStatus",
34
+ "ApplianceModeSettings",
35
+ "ApplianceModeFlag",
36
+ "TimerMode",
37
+ "TimerModeSettings",
38
+ "TimerPeriod",
39
+ "AutomaticProvisioning",
40
+ "TsiEnergyReport",
41
+ "parse_telemetry_points",
42
+ "filter_telemetry_points",
43
+ "summarise_energy",
44
+ "EnergySummary",
45
+ "VALUE_KEY_T2",
46
+ "DimplexError",
47
+ "DimplexApiError",
48
+ "DimplexAuthError",
49
+ "DimplexConnectionError",
50
+ ]