python-daikin-itm 0.1.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.
- python_daikin_itm-0.1.0/LICENSE +21 -0
- python_daikin_itm-0.1.0/PKG-INFO +410 -0
- python_daikin_itm-0.1.0/README.md +381 -0
- python_daikin_itm-0.1.0/daikin_itm/__init__.py +37 -0
- python_daikin_itm-0.1.0/daikin_itm/cli.py +306 -0
- python_daikin_itm-0.1.0/daikin_itm/client.py +964 -0
- python_daikin_itm-0.1.0/daikin_itm/exceptions.py +61 -0
- python_daikin_itm-0.1.0/daikin_itm/models.py +242 -0
- python_daikin_itm-0.1.0/daikin_itm/protocol.py +742 -0
- python_daikin_itm-0.1.0/daikin_itm/py.typed +0 -0
- python_daikin_itm-0.1.0/daikin_itm/state.py +73 -0
- python_daikin_itm-0.1.0/pyproject.toml +170 -0
- python_daikin_itm-0.1.0/pyproject.toml.orig +160 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Robbin Janssen
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: python-daikin-itm
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Asynchronous Python client for the Daikin intelligent Touch Manager (iTM).
|
|
5
|
+
Keywords: async,client,daikin,itm,intelligent touch manager,hvac
|
|
6
|
+
Author: Robbin Janssen, Robin Mulder
|
|
7
|
+
Author-email: Robbin Janssen <robbinjanssen@gmail.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Framework :: AsyncIO
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Natural Language :: English
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
19
|
+
Requires-Dist: aiohttp>=3.9.0
|
|
20
|
+
Maintainer: Robbin Janssen
|
|
21
|
+
Maintainer-email: Robbin Janssen <robbinjanssen@gmail.com>
|
|
22
|
+
Requires-Python: >=3.11
|
|
23
|
+
Project-URL: Homepage, https://github.com/robbinjanssen/python-daikin-itm
|
|
24
|
+
Project-URL: Repository, https://github.com/robbinjanssen/python-daikin-itm
|
|
25
|
+
Project-URL: Documentation, https://github.com/robbinjanssen/python-daikin-itm
|
|
26
|
+
Project-URL: Bug Tracker, https://github.com/robbinjanssen/python-daikin-itm/issues
|
|
27
|
+
Project-URL: Changelog, https://github.com/robbinjanssen/python-daikin-itm/releases
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
|
|
30
|
+
[![GitHub Release][releases-shield]][releases]
|
|
31
|
+
[![Python Versions][python-versions-shield]][pypi]
|
|
32
|
+
![Project Stage][project-stage-shield]
|
|
33
|
+
![Project Maintenance][maintenance-shield]
|
|
34
|
+
[![License][license-shield]](LICENSE)
|
|
35
|
+
|
|
36
|
+
[![GitHub Activity][commits-shield]][commits-url]
|
|
37
|
+
[![PyPi Downloads][downloads-shield]][downloads-url]
|
|
38
|
+
[![GitHub Last Commit][last-commit-shield]][commits-url]
|
|
39
|
+
[![Stargazers][stars-shield]][stars-url]
|
|
40
|
+
[![Issues][issues-shield]][issues-url]
|
|
41
|
+
|
|
42
|
+
[![Code Coverage][codecov-shield]][codecov-url]
|
|
43
|
+
|
|
44
|
+
[![Build Status][build-shield]][build-url]
|
|
45
|
+
|
|
46
|
+
Asynchronous Python client for the Daikin intelligent Touch Manager (iTM).
|
|
47
|
+
|
|
48
|
+
## About
|
|
49
|
+
|
|
50
|
+
A Python package to control the indoor units connected to a Daikin
|
|
51
|
+
**intelligent Touch Manager (iTM)** locally. It speaks the protocol of the iTM's
|
|
52
|
+
built-in web UI, so it needs no cloud service and no extra hardware: only
|
|
53
|
+
network access to the iTM and a user account on it.
|
|
54
|
+
|
|
55
|
+
Based on the original work by Robin Mulder (Exonet).
|
|
56
|
+
|
|
57
|
+
Used by the Home Assistant integration
|
|
58
|
+
[home-assistant-daikin-itm](https://github.com/robbinjanssen/home-assistant-daikin-itm).
|
|
59
|
+
|
|
60
|
+
## Installation
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
pip install python-daikin-itm
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Datasets
|
|
67
|
+
|
|
68
|
+
### Unit
|
|
69
|
+
|
|
70
|
+
This object represents the current state of an indoor unit, as shown on the
|
|
71
|
+
iTM's operation panel. `fetch_units()` returns them by unit name.
|
|
72
|
+
|
|
73
|
+
| Variable | Type | Description |
|
|
74
|
+
| :------- | :--- | :---------- |
|
|
75
|
+
| `name` | string | The name of the unit in the iTM, e.g. `1:1-04`. |
|
|
76
|
+
| `power` | string | The raw on/off option as the iTM shows it, e.g. `Start`, `Stop` or a setback option. `None` when unknown. |
|
|
77
|
+
| `running` | boolean | `True` when the unit is started; setback counts as not running. `None` when unknown. |
|
|
78
|
+
| `state_icon` | integer | The raw state icon value of the unit. |
|
|
79
|
+
| `setpoint` | integer | The current setpoint in °C. |
|
|
80
|
+
| `setpoint_range` | tuple | The setpoints the unit accepts, in °C. |
|
|
81
|
+
| `room_temperature` | float | The room temperature in °C, `None` when the unit doesn't report one. |
|
|
82
|
+
| `mode` | string | The normalized operation mode (see below), `None` when unknown. |
|
|
83
|
+
| `mode_label` | string | The operation mode as the iTM shows it (localized). |
|
|
84
|
+
| `fan_icon` | integer | The raw fan speed icon. |
|
|
85
|
+
| `fan_speed` | string | The fan speed (see below), `None` when unknown. |
|
|
86
|
+
| `error_code` | string | The malfunction code the iTM shows, `None` when there is none. |
|
|
87
|
+
| `filter_sign` | boolean | Whether the iTM shows the filter cleaning sign. |
|
|
88
|
+
| `setback` | boolean | Property: whether the unit is in setback. |
|
|
89
|
+
|
|
90
|
+
### Capabilities
|
|
91
|
+
|
|
92
|
+
This object represents the static facts about a unit: its identity (from the
|
|
93
|
+
iTM's Details screen) and what its settings dialog offers. Read it once with
|
|
94
|
+
`fetch_capabilities()`; it doesn't change while running.
|
|
95
|
+
|
|
96
|
+
| Variable | Type | Description |
|
|
97
|
+
| :------- | :--- | :---------- |
|
|
98
|
+
| `name` | string | The name of the unit in the iTM. |
|
|
99
|
+
| `mode_labels` | dict | Normalized mode to the label as the iTM shows it, in the dialog's order. |
|
|
100
|
+
| `fan_speed` | boolean | Whether the fan speed of this unit can be changed. |
|
|
101
|
+
| `point_id` | string | The iTM management point ID, e.g. `105`. |
|
|
102
|
+
| `address` | string | The bus address (`port:address`), e.g. `1:1-04`. Stable, survives renames. |
|
|
103
|
+
| `unit_type` | string | The unit type as the iTM shows it. |
|
|
104
|
+
| `description` | string | Free text set in the iTM for this unit. |
|
|
105
|
+
| `zones` | tuple | The zone paths the unit belongs to. |
|
|
106
|
+
| `modes` | tuple | Property: the normalized modes the unit offers. |
|
|
107
|
+
| `key` | string | Property: a stable identifier, the bus address when known, else the name. |
|
|
108
|
+
|
|
109
|
+
The fields from the Details screen (`point_id` to `zones`) are `None` (or empty)
|
|
110
|
+
when the iTM doesn't show them.
|
|
111
|
+
|
|
112
|
+
#### Operation modes
|
|
113
|
+
|
|
114
|
+
The iTM shows localized mode labels. The client normalizes the ones it knows to
|
|
115
|
+
`heat`, `cool`, `fan`, `dry` and `auto`; the original label stays available in
|
|
116
|
+
`mode_label` / `mode_labels`. Which modes a unit offers depends on the unit, see
|
|
117
|
+
`Capabilities.modes`.
|
|
118
|
+
|
|
119
|
+
#### Fan speeds
|
|
120
|
+
|
|
121
|
+
`FAN_SPEEDS` holds the fan speeds: `low`, `medium`, `high` and `auto`. Check
|
|
122
|
+
`Capabilities.fan_speed` to see whether a unit lets you change it.
|
|
123
|
+
|
|
124
|
+
## Usage
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
import asyncio
|
|
128
|
+
|
|
129
|
+
from daikin_itm import ItmClient
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
async def main() -> None:
|
|
133
|
+
"""Show example on using the iTM client."""
|
|
134
|
+
async with ItmClient(
|
|
135
|
+
"192.168.1.10",
|
|
136
|
+
"<your-username>",
|
|
137
|
+
"<your-password>",
|
|
138
|
+
zone_path=("Alles", "Binnendeel"),
|
|
139
|
+
) as client:
|
|
140
|
+
units = await client.fetch_units()
|
|
141
|
+
for unit in units.values():
|
|
142
|
+
print(f"{unit.name}: {unit.mode} {unit.setpoint} °C, room {unit.room_temperature} °C")
|
|
143
|
+
|
|
144
|
+
capabilities = await client.fetch_capabilities()
|
|
145
|
+
for name, caps in capabilities.items():
|
|
146
|
+
print(f"{name} ({caps.key}): modes {caps.modes}, fan control: {caps.fan_speed}")
|
|
147
|
+
|
|
148
|
+
# Each write returns the unit as read back once the change shows.
|
|
149
|
+
unit = await client.set_setpoint("1:1-04", 21)
|
|
150
|
+
|
|
151
|
+
# Power, mode and fan speed can be combined in one write.
|
|
152
|
+
unit = await client.apply("1:1-04", power=True, mode="heat", fan_speed="low")
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
if __name__ == "__main__":
|
|
156
|
+
asyncio.run(main())
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Client
|
|
160
|
+
|
|
161
|
+
The host may be `"host"`, `"host:port"` or a full `http://` / `https://` URL.
|
|
162
|
+
The other constructor arguments are keyword-only:
|
|
163
|
+
|
|
164
|
+
| Argument | Default | Description |
|
|
165
|
+
| :------- | :------ | :---------- |
|
|
166
|
+
| `session` | `None` | An `aiohttp.ClientSession` to use; the client creates (and closes) its own otherwise. |
|
|
167
|
+
| `store` | `MemorySessionStore()` | Where to keep the page token, see [Sessions](#sessions). |
|
|
168
|
+
| `zone_path` | `("Alles", "Binnendeel")` | The zone path in the iTM's icon view that contains the indoor units (`DEFAULT_ZONE_PATH`). |
|
|
169
|
+
| `commit_delay` | `3.0` | Seconds between a setpoint change and its commit, like the web UI. |
|
|
170
|
+
| `min_write_interval` | `5.0` | Minimum seconds between two writes to the same unit. |
|
|
171
|
+
| `confirm_timeout` | `30.0` | Seconds to wait for a fresh read to show a change. |
|
|
172
|
+
| `confirm_interval` | `2.0` | Seconds between those reads. |
|
|
173
|
+
| `timeout` | `30.0` | Timeout of each HTTP request in seconds. |
|
|
174
|
+
|
|
175
|
+
### Methods
|
|
176
|
+
|
|
177
|
+
| Method | Description |
|
|
178
|
+
| :----- | :---------- |
|
|
179
|
+
| `fetch_units()` | Read every unit in the zone path, returns `dict[str, Unit]` by name. |
|
|
180
|
+
| `fetch_unit(name)` | Read a single unit (cheaper than `fetch_units()`). |
|
|
181
|
+
| `fetch_capabilities()` | Read every unit's identity and the modes and fan control it offers, returns `dict[str, Capabilities]`. This costs four requests per unit, so do it once rather than on every poll. |
|
|
182
|
+
| `list_zones(path=())` | The names of the zones and units at a path, for discovery: `list_zones()` for the top level, `list_zones(("Alles",))` for what's inside `Alles`. |
|
|
183
|
+
| `set_setpoint(unit, value, *, confirm=True)` | Change a unit's setpoint. Refused while the unit is stopped (the iTM ignores it then) or when the value is outside `setpoint_range`. |
|
|
184
|
+
| `apply(unit, *, power=None, mode=None, fan_speed=None, confirm=True)` | Change power, mode and/or fan speed in one write. The request is compared with the unit as read in the write session: only what differs is sent, and a unit that already is in the requested state is returned without writing anything. |
|
|
185
|
+
| `set_power(unit, on, *, confirm=True)` | Start or stop a unit. |
|
|
186
|
+
| `set_mode(unit, mode, *, confirm=True)` | Change the operation mode. |
|
|
187
|
+
| `set_fan_speed(unit, fan_speed, *, confirm=True)` | Change the fan speed. |
|
|
188
|
+
| `release_saved_session()` | Log out a session a previous run left behind; returns `True` if there was one. Every call does this before logging in. A saved token the iTM answers with an HTTP error or an unreadable reply is dropped (with a warning) and left to the iTM's own timeout. |
|
|
189
|
+
|
|
190
|
+
By default, a write returns the `Unit` as read back once a fresh read shows the
|
|
191
|
+
change (or raises `ItmCommandError` after `confirm_timeout`). With
|
|
192
|
+
`confirm=False` it returns `None` right after the write; the next write to the
|
|
193
|
+
same unit first waits until that change shows, so it can't overtake it.
|
|
194
|
+
|
|
195
|
+
### Sessions
|
|
196
|
+
|
|
197
|
+
The iTM allows one web session at a time, so every call logs in, does its work
|
|
198
|
+
and logs out. The page token is handed to a `SessionStore` after every reply,
|
|
199
|
+
so a session left behind by a crash can be released on the next run. A store is
|
|
200
|
+
any object with two coroutines:
|
|
201
|
+
|
|
202
|
+
```python
|
|
203
|
+
class SessionStore(Protocol):
|
|
204
|
+
async def load(self) -> str | None: ...
|
|
205
|
+
async def save(self, token: str | None) -> None: ...
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
- `MemorySessionStore()` (the default) keeps the token in memory.
|
|
209
|
+
- `FileSessionStore(path, host)` keeps it in an owner-only file and only hands
|
|
210
|
+
it back for the host it belongs to, so a killed process can still release its
|
|
211
|
+
session on the next run.
|
|
212
|
+
|
|
213
|
+
### Exceptions
|
|
214
|
+
|
|
215
|
+
| Exception | Description |
|
|
216
|
+
| :-------- | :---------- |
|
|
217
|
+
| `ItmError` | Base class for everything this library raises. |
|
|
218
|
+
| `ProtocolError` | A reply could not be decoded (not an iTM reply, or an unknown layout). |
|
|
219
|
+
| `LoginRejected` | The iTM refused the login: wrong credentials, or another web session is active. |
|
|
220
|
+
| `SessionEnded` | The iTM ended the session (it answered with a LOGOUT update), e.g. because the token was stale. |
|
|
221
|
+
| `VersionMismatch` | The iTM expects another web UI version string (subclass of `SessionEnded`); carries it in `.version`. |
|
|
222
|
+
| `ItmCommandError` | The iTM answered a command with a dialog, the UI was not as expected, or a change was not confirmed in time. |
|
|
223
|
+
| `ZoneNotFound` | A zone of the zone path is not there (subclass of `ItmCommandError`); the names that are there are in `.available`. |
|
|
224
|
+
|
|
225
|
+
Network errors are raised as `aiohttp.ClientError` or `TimeoutError`.
|
|
226
|
+
|
|
227
|
+
## Things to know about the iTM
|
|
228
|
+
|
|
229
|
+
- **One web session at a time.** While a client is logged in, every other login
|
|
230
|
+
(including a browser) is refused. The library therefore never keeps a session
|
|
231
|
+
open: every call logs in, works and logs out, also on errors and
|
|
232
|
+
cancellation. If a logout isn't confirmed, the latest page token is kept in the
|
|
233
|
+
`SessionStore` so the next run can release the session. The CLI writes the
|
|
234
|
+
token to a file after every reply, so a session left behind by a crash is
|
|
235
|
+
released on the next run. The Home Assistant integration writes it to
|
|
236
|
+
`.storage` only once a session has lasted 10 s, and then after every reply, to
|
|
237
|
+
spare SD cards: a crash during a normal 1-2 s poll leaves the session to the
|
|
238
|
+
iTM's own timeout.
|
|
239
|
+
- **Replies are deltas** against what the iTM believes the client shows, and it
|
|
240
|
+
never echoes a value the client picked itself. Verify a change in a *fresh*
|
|
241
|
+
session (the client does this for every write).
|
|
242
|
+
- **Setpoint changes are ignored while a unit is stopped** (the client refuses
|
|
243
|
+
them).
|
|
244
|
+
- **Changes take 3-8 s to reach a unit** over the Daikin bus. Every write waits
|
|
245
|
+
until a fresh read shows the new value (up to `confirm_timeout`), so the next
|
|
246
|
+
change can't overtake it; a change sent while the previous one is still
|
|
247
|
+
pending can get lost.
|
|
248
|
+
- Power, mode and fan speed go through the unit's settings dialog (tick the
|
|
249
|
+
item, set it, OK, confirm "Instelling uitvoeren?" with *Ja*), so they can be
|
|
250
|
+
combined in one write. The setpoint uses the operation panel's spinner plus a
|
|
251
|
+
`TIMER` commit. All four are verified on the real device.
|
|
252
|
+
- Indoor units store settings in memory with a limited number of writes
|
|
253
|
+
(Daikin: max ~7000 changes per unit per year). Don't automate setpoints in
|
|
254
|
+
tight loops; the client spaces writes per unit (`min_write_interval`, 5 s).
|
|
255
|
+
- If the iTM's web UI version changes after a firmware update, the client adopts
|
|
256
|
+
the version the iTM reports and logs a warning.
|
|
257
|
+
|
|
258
|
+
## Security notes
|
|
259
|
+
|
|
260
|
+
- **The iTM's login is weak by design.** Its web UI sends the username plus an
|
|
261
|
+
unsalted SHA-256 of the password, without a server nonce, and over plain HTTP
|
|
262
|
+
by default. Anyone who can see the traffic can replay it to log in. This
|
|
263
|
+
client can't change the device's protocol, and it has to log in for every
|
|
264
|
+
call (the iTM allows only one session, so it can't stay logged in). Put the
|
|
265
|
+
iTM and the machine running this client on an isolated network segment, use a
|
|
266
|
+
dedicated iTM account for it, and use an `https://…` address if your iTM
|
|
267
|
+
offers HTTPS (the host accepts a full URL).
|
|
268
|
+
- Replies are treated as untrusted: the decoder rejects anything that doesn't
|
|
269
|
+
fit the reply (hostile element counts, reads past the end) and replies over
|
|
270
|
+
1 MB.
|
|
271
|
+
- The CLI keeps the session token in `~/.cache/daikin-itm/` (owner-only, per
|
|
272
|
+
host) and strips control characters from everything it prints.
|
|
273
|
+
|
|
274
|
+
## CLI
|
|
275
|
+
|
|
276
|
+
The package installs an `itm` command for debugging. Credentials come from the
|
|
277
|
+
environment, or from `--creds` pointing at a JSON file with `username` and
|
|
278
|
+
`password` fields (a 1Password JSON export). Set the iTM's address with `--host`
|
|
279
|
+
or `ITM_HOST`.
|
|
280
|
+
|
|
281
|
+
```bash
|
|
282
|
+
export ITM_HOST=192.168.1.10
|
|
283
|
+
export ITM_USERNAME=... ITM_PASSWORD=... # or: --creds creds.json
|
|
284
|
+
itm zones # top-level zones
|
|
285
|
+
itm zones Alles # zones/units inside "Alles"
|
|
286
|
+
itm status # all units in Alles/Binnendeel (--zone to change); descriptions are
|
|
287
|
+
# cached in ~/.cache/daikin-itm/units.json (--refresh to re-read them)
|
|
288
|
+
itm caps # modes / fan control per unit
|
|
289
|
+
itm setpoint 1:1-04 21 # each write returns once the unit shows the change
|
|
290
|
+
itm power 1:1-09 on
|
|
291
|
+
itm mode 1:1-04 heat # heat / cool / fan / dry / auto, as offered by the unit
|
|
292
|
+
itm fan 1:1-04 low # low / medium / high / auto
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
## Protocol notes
|
|
296
|
+
|
|
297
|
+
`POST /cmd/`, body `snD=<hex>`: 32-char page token, then screen ID, part ID,
|
|
298
|
+
event type, data type (shorts), a number (int), an int array and a string.
|
|
299
|
+
Numbers are big-endian hex, strings are a length plus raw characters. Login:
|
|
300
|
+
screen 1203, part 4, string `%04d len(user) + user + sha256hex(password)`.
|
|
301
|
+
Replies: new token, then screen updates with part updates (labels, combobox
|
|
302
|
+
values, icon panels…). Screens used: 1 icon view (part 6 icon panel, 2 = go
|
|
303
|
+
down), 4 operation panel (9 on/off, 11 setpoint). A setpoint change is a
|
|
304
|
+
selection on screen 4 followed by a `TIMER` event on screen 4. Power/mode/fan
|
|
305
|
+
use the settings dialog (screen 7): tick 2/17/28, radio 3/4, mode combobox 18,
|
|
306
|
+
fan up 29 (icon 31: 3009 low, 3011 medium, 3013 high, 3015 auto), OK 5, then
|
|
307
|
+
CLICK on the confirm dialog's ID. See `daikin_itm/protocol.py`.
|
|
308
|
+
|
|
309
|
+
## Contributing
|
|
310
|
+
|
|
311
|
+
This is an active open-source project. We are always open to people who want to
|
|
312
|
+
use the code or contribute to it.
|
|
313
|
+
|
|
314
|
+
We've set up a separate document for our
|
|
315
|
+
[contribution guidelines](CONTRIBUTING.md).
|
|
316
|
+
|
|
317
|
+
Thank you for being involved! :heart_eyes:
|
|
318
|
+
|
|
319
|
+
## Setting up development environment
|
|
320
|
+
|
|
321
|
+
This Python project is fully managed using the [uv][uv] package manager.
|
|
322
|
+
|
|
323
|
+
You need at least:
|
|
324
|
+
|
|
325
|
+
- Python 3.11+
|
|
326
|
+
- [uv][uv-install]
|
|
327
|
+
|
|
328
|
+
Install all packages, including all development requirements:
|
|
329
|
+
|
|
330
|
+
```bash
|
|
331
|
+
uv sync
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
uv creates a virtual environment in `.venv` and installs all necessary
|
|
335
|
+
packages in it. Run commands in it with `uv run`.
|
|
336
|
+
|
|
337
|
+
Set up the pre-commit checks with [prek][prek]:
|
|
338
|
+
|
|
339
|
+
```bash
|
|
340
|
+
uv run prek install
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
*Now you're all set to get started!*
|
|
344
|
+
|
|
345
|
+
All changes are linted and tested with each commit. You can run all checks
|
|
346
|
+
and tests manually, using the following command:
|
|
347
|
+
|
|
348
|
+
```bash
|
|
349
|
+
uv run prek run --all-files
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
To run just the Python tests:
|
|
353
|
+
|
|
354
|
+
```bash
|
|
355
|
+
uv run pytest
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
No iTM is needed for the tests: `tests/fake_itm.py` simulates the behaviour that
|
|
359
|
+
matters (single session, delta replies, ignored setpoints on stopped units),
|
|
360
|
+
and `tests/fixtures/` holds replies captured from a real device.
|
|
361
|
+
|
|
362
|
+
## License
|
|
363
|
+
|
|
364
|
+
MIT License
|
|
365
|
+
|
|
366
|
+
Copyright (c) 2026 Robbin Janssen
|
|
367
|
+
|
|
368
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
369
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
370
|
+
in the Software without restriction, including without limitation the rights
|
|
371
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
372
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
373
|
+
furnished to do so, subject to the following conditions:
|
|
374
|
+
|
|
375
|
+
The above copyright notice and this permission notice shall be included in all
|
|
376
|
+
copies or substantial portions of the Software.
|
|
377
|
+
|
|
378
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
379
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
380
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
381
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
382
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
383
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
384
|
+
SOFTWARE.
|
|
385
|
+
|
|
386
|
+
<!-- MARKDOWN LINKS & IMAGES -->
|
|
387
|
+
[build-shield]: https://github.com/robbinjanssen/python-daikin-itm/actions/workflows/tests.yaml/badge.svg
|
|
388
|
+
[build-url]: https://github.com/robbinjanssen/python-daikin-itm/actions/workflows/tests.yaml
|
|
389
|
+
[commits-shield]: https://img.shields.io/github/commit-activity/y/robbinjanssen/python-daikin-itm.svg
|
|
390
|
+
[commits-url]: https://github.com/robbinjanssen/python-daikin-itm/commits/main
|
|
391
|
+
[codecov-shield]: https://codecov.io/gh/robbinjanssen/python-daikin-itm/branch/main/graph/badge.svg
|
|
392
|
+
[codecov-url]: https://codecov.io/gh/robbinjanssen/python-daikin-itm
|
|
393
|
+
[downloads-shield]: https://img.shields.io/pypi/dm/python-daikin-itm
|
|
394
|
+
[downloads-url]: https://pypistats.org/packages/python-daikin-itm
|
|
395
|
+
[issues-shield]: https://img.shields.io/github/issues/robbinjanssen/python-daikin-itm.svg
|
|
396
|
+
[issues-url]: https://github.com/robbinjanssen/python-daikin-itm/issues
|
|
397
|
+
[license-shield]: https://img.shields.io/github/license/robbinjanssen/python-daikin-itm.svg
|
|
398
|
+
[last-commit-shield]: https://img.shields.io/github/last-commit/robbinjanssen/python-daikin-itm.svg
|
|
399
|
+
[maintenance-shield]: https://img.shields.io/maintenance/yes/2026.svg
|
|
400
|
+
[project-stage-shield]: https://img.shields.io/badge/project%20stage-experimental-yellow.svg
|
|
401
|
+
[pypi]: https://pypi.org/project/python-daikin-itm/
|
|
402
|
+
[python-versions-shield]: https://img.shields.io/pypi/pyversions/python-daikin-itm
|
|
403
|
+
[releases-shield]: https://img.shields.io/github/release/robbinjanssen/python-daikin-itm.svg
|
|
404
|
+
[releases]: https://github.com/robbinjanssen/python-daikin-itm/releases
|
|
405
|
+
[stars-shield]: https://img.shields.io/github/stars/robbinjanssen/python-daikin-itm.svg
|
|
406
|
+
[stars-url]: https://github.com/robbinjanssen/python-daikin-itm/stargazers
|
|
407
|
+
|
|
408
|
+
[prek]: https://prek.j178.dev
|
|
409
|
+
[uv]: https://docs.astral.sh/uv/
|
|
410
|
+
[uv-install]: https://docs.astral.sh/uv/getting-started/installation/
|