daikin-local-control 0.3.1 → 0.5.0
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.
- package/DESIGN.md +156 -2
- package/README.md +274 -48
- package/UNSUPPORTED.md +66 -3
- package/dist/clients/airbase.d.ts +64 -0
- package/dist/clients/airbase.d.ts.map +1 -0
- package/dist/clients/airbase.js +175 -0
- package/dist/clients/airbase.js.map +1 -0
- package/dist/clients/classic.d.ts +2 -2
- package/dist/clients/classic.d.ts.map +1 -1
- package/dist/clients/classic.js +4 -4
- package/dist/clients/classic.js.map +1 -1
- package/dist/clients/client.d.ts +7 -2
- package/dist/clients/client.d.ts.map +1 -1
- package/dist/clients/dsiot.d.ts +3 -1
- package/dist/clients/dsiot.d.ts.map +1 -1
- package/dist/clients/dsiot.js +7 -2
- package/dist/clients/dsiot.js.map +1 -1
- package/dist/codecs/airbase/capabilities.d.ts +23 -0
- package/dist/codecs/airbase/capabilities.d.ts.map +1 -0
- package/dist/codecs/airbase/capabilities.js +108 -0
- package/dist/codecs/airbase/capabilities.js.map +1 -0
- package/dist/codecs/airbase/state.d.ts +29 -0
- package/dist/codecs/airbase/state.d.ts.map +1 -0
- package/dist/codecs/airbase/state.js +166 -0
- package/dist/codecs/airbase/state.js.map +1 -0
- package/dist/codecs/airbase/tables.d.ts +7 -0
- package/dist/codecs/airbase/tables.d.ts.map +1 -0
- package/dist/codecs/airbase/tables.js +27 -0
- package/dist/codecs/airbase/tables.js.map +1 -0
- package/dist/codecs/airbase/zone.d.ts +12 -0
- package/dist/codecs/airbase/zone.d.ts.map +1 -0
- package/dist/codecs/airbase/zone.js +50 -0
- package/dist/codecs/airbase/zone.js.map +1 -0
- package/dist/codecs/classic/capabilities.d.ts.map +1 -1
- package/dist/codecs/classic/capabilities.js +5 -11
- package/dist/codecs/classic/capabilities.js.map +1 -1
- package/dist/codecs/classic/extendedControl.d.ts.map +1 -1
- package/dist/codecs/classic/extendedControl.js +10 -19
- package/dist/codecs/classic/extendedControl.js.map +1 -1
- package/dist/codecs/classic/state.d.ts.map +1 -1
- package/dist/codecs/classic/state.js +36 -95
- package/dist/codecs/classic/state.js.map +1 -1
- package/dist/codecs/classic/tables.d.ts +34 -0
- package/dist/codecs/classic/tables.d.ts.map +1 -0
- package/dist/codecs/classic/tables.js +96 -0
- package/dist/codecs/classic/tables.js.map +1 -0
- package/dist/codecs/credentials.d.ts +3 -2
- package/dist/codecs/credentials.d.ts.map +1 -1
- package/dist/codecs/credentials.js +8 -3
- package/dist/codecs/credentials.js.map +1 -1
- package/dist/codecs/device.d.ts +7 -1
- package/dist/codecs/device.d.ts.map +1 -1
- package/dist/codecs/device.js +15 -3
- package/dist/codecs/device.js.map +1 -1
- package/dist/codecs/dsiot/capabilities.d.ts.map +1 -1
- package/dist/codecs/dsiot/capabilities.js +8 -15
- package/dist/codecs/dsiot/capabilities.js.map +1 -1
- package/dist/codecs/dsiot/device.d.ts.map +1 -1
- package/dist/codecs/dsiot/device.js +4 -8
- package/dist/codecs/dsiot/device.js.map +1 -1
- package/dist/codecs/dsiot/multireq.d.ts +0 -19
- package/dist/codecs/dsiot/multireq.d.ts.map +1 -1
- package/dist/codecs/dsiot/multireq.js +2 -25
- package/dist/codecs/dsiot/multireq.js.map +1 -1
- package/dist/codecs/dsiot/state.d.ts +6 -62
- package/dist/codecs/dsiot/state.d.ts.map +1 -1
- package/dist/codecs/dsiot/state.js +29 -148
- package/dist/codecs/dsiot/state.js.map +1 -1
- package/dist/codecs/dsiot/tables.d.ts +119 -0
- package/dist/codecs/dsiot/tables.d.ts.map +1 -0
- package/dist/codecs/dsiot/tables.js +188 -0
- package/dist/codecs/dsiot/tables.js.map +1 -0
- package/dist/discovery.d.ts +20 -9
- package/dist/discovery.d.ts.map +1 -1
- package/dist/discovery.js +102 -32
- package/dist/discovery.js.map +1 -1
- package/dist/factory.d.ts +13 -7
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +16 -4
- package/dist/factory.js.map +1 -1
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/models/device.d.ts +3 -2
- package/dist/models/device.d.ts.map +1 -1
- package/dist/models/device.js +1 -1
- package/dist/models/device.js.map +1 -1
- package/dist/models/state.d.ts +2 -3
- package/dist/models/state.d.ts.map +1 -1
- package/dist/models/zone.d.ts +9 -0
- package/dist/models/zone.d.ts.map +1 -0
- package/dist/models/zone.js +2 -0
- package/dist/models/zone.js.map +1 -0
- package/dist/simulators/airbase.d.ts +82 -0
- package/dist/simulators/airbase.d.ts.map +1 -0
- package/dist/simulators/airbase.js +352 -0
- package/dist/simulators/airbase.js.map +1 -0
- package/dist/simulators/classic.d.ts +61 -0
- package/dist/simulators/classic.d.ts.map +1 -0
- package/dist/simulators/classic.js +473 -0
- package/dist/simulators/classic.js.map +1 -0
- package/dist/simulators/dsiot.d.ts +49 -0
- package/dist/simulators/dsiot.d.ts.map +1 -0
- package/dist/simulators/dsiot.js +519 -0
- package/dist/simulators/dsiot.js.map +1 -0
- package/dist/simulators/index.d.ts +5 -0
- package/dist/simulators/index.d.ts.map +1 -0
- package/dist/simulators/index.js +4 -0
- package/dist/simulators/index.js.map +1 -0
- package/dist/simulators/simulator.d.ts +117 -0
- package/dist/simulators/simulator.d.ts.map +1 -0
- package/dist/simulators/simulator.js +141 -0
- package/dist/simulators/simulator.js.map +1 -0
- package/dist/transport.d.ts +8 -3
- package/dist/transport.d.ts.map +1 -1
- package/dist/transport.js +10 -2
- package/dist/transport.js.map +1 -1
- package/package.json +11 -4
- package/src/clients/airbase.ts +274 -0
- package/src/clients/classic.ts +8 -8
- package/src/clients/client.ts +8 -1
- package/src/clients/dsiot.ts +8 -1
- package/src/codecs/airbase/capabilities.ts +150 -0
- package/src/codecs/airbase/state.ts +223 -0
- package/src/codecs/airbase/tables.ts +32 -0
- package/src/codecs/airbase/zone.ts +62 -0
- package/src/codecs/classic/capabilities.ts +8 -11
- package/src/codecs/classic/extendedControl.ts +14 -25
- package/src/codecs/classic/state.ts +49 -101
- package/src/codecs/classic/tables.ts +126 -0
- package/src/codecs/credentials.ts +11 -3
- package/src/codecs/device.ts +19 -3
- package/src/codecs/dsiot/capabilities.ts +10 -17
- package/src/codecs/dsiot/device.ts +4 -9
- package/src/codecs/dsiot/multireq.ts +2 -25
- package/src/codecs/dsiot/state.ts +57 -193
- package/src/codecs/dsiot/tables.ts +253 -0
- package/src/discovery.ts +107 -34
- package/src/factory.ts +26 -7
- package/src/index.ts +8 -0
- package/src/models/device.ts +3 -2
- package/src/models/state.ts +2 -3
- package/src/models/zone.ts +8 -0
- package/src/simulators/airbase.ts +495 -0
- package/src/simulators/classic.ts +636 -0
- package/src/simulators/dsiot.ts +736 -0
- package/src/simulators/index.ts +10 -0
- package/src/simulators/simulator.ts +282 -0
- package/src/transport.ts +15 -4
package/DESIGN.md
CHANGED
|
@@ -53,6 +53,46 @@ the read. A `DaikinState` holds the current mode's values alone: it has neither
|
|
|
53
53
|
the values a classic adapter remembers for the other modes nor the `md` of a
|
|
54
54
|
DSIOT leaf, so the client would still need the reply.
|
|
55
55
|
|
|
56
|
+
## Discovery
|
|
57
|
+
|
|
58
|
+
`discoverDevices` sends its probes from the first free UDP port from 30000 to
|
|
59
|
+
30050. An adapter answers the port a probe came from, so a reply reaches the
|
|
60
|
+
call that sent the probe.
|
|
61
|
+
|
|
62
|
+
Rejected: port 30000 alone, bound with `reuseAddr`.
|
|
63
|
+
|
|
64
|
+
- Two calls at the same time both bound the port, and each reply reached one
|
|
65
|
+
of the two sockets alone. One call resolved with every adapter and the other
|
|
66
|
+
with none, and neither rejected.
|
|
67
|
+
|
|
68
|
+
`discoverDevices` asks each adapter that replies as a classic one, over HTTP,
|
|
69
|
+
whether it is an Airbase adapter: `isAirbaseAdapter` takes HTTP 404 at
|
|
70
|
+
`/common/basic_info` with `ret=OK` at `/skyfi/common/basic_info` for one.
|
|
71
|
+
|
|
72
|
+
Rejected: the protocol from the reply alone.
|
|
73
|
+
|
|
74
|
+
- An Airbase adapter reports `type=aircon` as a classic adapter does, and no
|
|
75
|
+
field of the reply is known to differ. Discovery reported it as `classic`,
|
|
76
|
+
and it answered HTTP 404 to every request of the client built from that.
|
|
77
|
+
|
|
78
|
+
Rejected: asking in `createDaikinClient`, for a device written by hand as
|
|
79
|
+
well.
|
|
80
|
+
|
|
81
|
+
- `createDaikinClient` returns a client at once and sends nothing. Asking
|
|
82
|
+
there would make it asynchronous for every caller, to settle what a caller
|
|
83
|
+
who writes a device by hand already knows.
|
|
84
|
+
|
|
85
|
+
`discoverDevices` resolves with the adapters of air conditioners alone:
|
|
86
|
+
`isAirConditioner` passes a reply whose `type` is `aircon`, or `GPF` with a
|
|
87
|
+
`cdev` that is not an air cleaner's.
|
|
88
|
+
|
|
89
|
+
Rejected: a device for every adapter that answers.
|
|
90
|
+
|
|
91
|
+
- The adapter of an air cleaner, a water heater, a floor heater or a
|
|
92
|
+
circulator answers the probe as well. `parseDevice` read each as a classic
|
|
93
|
+
air conditioner, a `DaikinDevice` had nothing to tell them apart by, and no
|
|
94
|
+
method of the client built from one worked.
|
|
95
|
+
|
|
56
96
|
## Capabilities
|
|
57
97
|
|
|
58
98
|
`DaikinCapabilities` lists what a unit accepts in the vocabulary of `DaikinState`:
|
|
@@ -130,8 +170,11 @@ Rejected: positions as the numbers 0 to 25, typed `number`, with
|
|
|
130
170
|
## Special modes
|
|
131
171
|
|
|
132
172
|
`DaikinState.specialModes` lists the special modes that are on, and `getState`
|
|
133
|
-
reads it on both protocols. `DaikinControl` leaves it out
|
|
134
|
-
|
|
173
|
+
reads it on both protocols. `DaikinControl` leaves it out: `setSpecialMode`
|
|
174
|
+
turns one mode on or off, with a request of its own on both protocols. A
|
|
175
|
+
classic adapter takes it at `/aircon/set_special_mode`, beside
|
|
176
|
+
`set_control_info`, and a DSIOT adapter takes one leaf, on the outdoor edge
|
|
177
|
+
for powerful where every leaf `setState` writes is on the indoor edge.
|
|
135
178
|
|
|
136
179
|
Rejected: a `getSpecialModes` on `ClassicDaikinClient`, beside `setSpecialMode`.
|
|
137
180
|
|
|
@@ -148,3 +191,114 @@ Rejected: one list for the whole unit.
|
|
|
148
191
|
|
|
149
192
|
- Econo is not known to be taken in fan mode, so the one list offered it in a
|
|
150
193
|
mode where UNSUPPORTED.md lists it as unknown.
|
|
194
|
+
|
|
195
|
+
## Simulators
|
|
196
|
+
|
|
197
|
+
`startClassicSimulator`, `startDsiotSimulator` and `startAirbaseSimulator`
|
|
198
|
+
each start an adapter as a server. A simulator shares the codecs' tables, `src/codecs/*/tables.ts`, and
|
|
199
|
+
none of their functions. A table holds what an adapter calls a thing: its
|
|
200
|
+
field and leaf names, and the code for each value. A function does something
|
|
201
|
+
with it: a codec parses a reply and builds a request, and a simulator answers
|
|
202
|
+
the request, holds what it was sent, and builds the reply.
|
|
203
|
+
|
|
204
|
+
Rejected: a simulator that builds its replies with the codecs' functions.
|
|
205
|
+
|
|
206
|
+
- A simulator built from a codec agrees with it by construction. A codec that
|
|
207
|
+
reads a field wrongly would meet a simulator that writes it wrongly the same
|
|
208
|
+
way, and the test of one against the other would pass.
|
|
209
|
+
|
|
210
|
+
Rejected: a simulator with tables of its own, sharing nothing with the codecs.
|
|
211
|
+
|
|
212
|
+
- Every code was written twice, in two files that had to be kept alike, and a
|
|
213
|
+
caller setting a field of a simulator by hand had no table to read it from.
|
|
214
|
+
- The second writing caught a wrong code only where the simulator encoded a
|
|
215
|
+
value itself. `tables.test.ts` writes every entry out again, and catches
|
|
216
|
+
one wherever it is used.
|
|
217
|
+
|
|
218
|
+
`package.json` exports the simulators as `daikin-local-control/simulators`,
|
|
219
|
+
an entry of their own beside the package root.
|
|
220
|
+
|
|
221
|
+
Rejected: exporting them from the package root.
|
|
222
|
+
|
|
223
|
+
- A caller's production code imports the root. With the simulators in it, that
|
|
224
|
+
import loads two servers the code never starts, and a bundler has to work
|
|
225
|
+
out that it can drop them.
|
|
226
|
+
|
|
227
|
+
A simulator holds its state in the adapter's terms, `controlInfo.stemp` on a
|
|
228
|
+
classic simulator and a leaf's `pv` by its address on a DSIOT one, and takes
|
|
229
|
+
it in the package's models: a `DaikinState`, a `DaikinSensors` and a
|
|
230
|
+
`DaikinDeviceInfo`. It turns a model into the adapter's fields with functions
|
|
231
|
+
of its own, in `src/simulators/`, so a client that reads the state back
|
|
232
|
+
checks the codec that parses it against a second writing of the same
|
|
233
|
+
encoding.
|
|
234
|
+
|
|
235
|
+
Rejected: options in the adapter's terms alone, a record of strings for each
|
|
236
|
+
reply.
|
|
237
|
+
|
|
238
|
+
- A caller had to know the adapter's encodings to start a unit in a state: a
|
|
239
|
+
DSIOT setpoint of 24 °C is the leaf value `3000`.
|
|
240
|
+
|
|
241
|
+
A classic or a DSIOT simulator takes what the unit has as a
|
|
242
|
+
`DaikinSimulatorUnit`, which says it in terms both protocols have flags for:
|
|
243
|
+
`coolingOnly`, `swing`, `fan`. An Airbase simulator takes an
|
|
244
|
+
`AirbaseSimulatorUnit` of its own: an Airbase unit has zones and airside, and
|
|
245
|
+
neither a cooling-only kind nor a special mode.
|
|
246
|
+
|
|
247
|
+
Rejected: taking a `DaikinCapabilities`.
|
|
248
|
+
|
|
249
|
+
- Most values of it are ones no adapter reports. Fan speeds of `auto`,
|
|
250
|
+
`level1` and `level3` have no `frate_steps`, and a DSIOT adapter reports
|
|
251
|
+
every fan speed or none, so the simulator would throw for most of what the
|
|
252
|
+
type allows.
|
|
253
|
+
|
|
254
|
+
A simulator answers HTTP 501 to a request whose answer from an adapter is not
|
|
255
|
+
known, such as a path it does not serve.
|
|
256
|
+
|
|
257
|
+
Rejected: answering such a request the way an adapter plausibly would, with
|
|
258
|
+
HTTP 404 or `ret=PARAM NG`.
|
|
259
|
+
|
|
260
|
+
- A caller's test would then pass or fail on an answer no adapter is known to
|
|
261
|
+
give. No adapter is known to answer 501, so a caller cannot take it for an
|
|
262
|
+
adapter's reply.
|
|
263
|
+
|
|
264
|
+
A simulator answers one request at a time, and a request that arrives
|
|
265
|
+
meanwhile waits for its turn: an adapter makes concurrent requests wait.
|
|
266
|
+
`replyDelayInMs` makes the wait long enough for a test to see. Whether an
|
|
267
|
+
adapter also drops a connection while it is busy is not known, so a simulator
|
|
268
|
+
never does.
|
|
269
|
+
|
|
270
|
+
A simulator answers the discovery probe only when started with
|
|
271
|
+
`answersDiscovery`.
|
|
272
|
+
|
|
273
|
+
Rejected: answering it always.
|
|
274
|
+
|
|
275
|
+
- An adapter answers on UDP port 30050, a fixed port, so a simulator that
|
|
276
|
+
always bound it could not start beside another program that holds it, in a
|
|
277
|
+
test that never discovers anything.
|
|
278
|
+
|
|
279
|
+
Each simulator is at one `address` of the machine, `127.0.0.1` unless it is
|
|
280
|
+
given another. It answers the probe from that address, and the simulators
|
|
281
|
+
share port 30050: each hears the probe on a socket bound to every address,
|
|
282
|
+
and answers from a second socket bound to its own.
|
|
283
|
+
|
|
284
|
+
Rejected: one socket for each simulator, bound to port 30050 of its own
|
|
285
|
+
address.
|
|
286
|
+
|
|
287
|
+
- The probe is a broadcast to `255.255.255.255`, and a socket bound to one
|
|
288
|
+
address receives none of it: two simulators bound that way answered no
|
|
289
|
+
probe.
|
|
290
|
+
|
|
291
|
+
Rejected: one socket bound to every address, answering from it.
|
|
292
|
+
|
|
293
|
+
- The machine then picks the address a reply leaves from, its address on the
|
|
294
|
+
LAN, so `discoverDevices` reports the simulator there, and the simulator
|
|
295
|
+
has to listen for HTTP on the LAN where a test needs loopback alone.
|
|
296
|
+
|
|
297
|
+
`discoverDevices` keeps one device for each `host`, the address a reply came
|
|
298
|
+
from with the `api_port` it announces, so it finds every simulator on a
|
|
299
|
+
machine: they answer from one address and announce a port each.
|
|
300
|
+
|
|
301
|
+
Rejected: one device for each address.
|
|
302
|
+
|
|
303
|
+
- Simulators on one machine share its addresses, so a caller could have as
|
|
304
|
+
many discovered as the machine has addresses, two on a laptop.
|
package/README.md
CHANGED
|
@@ -1,26 +1,32 @@
|
|
|
1
1
|
# daikin-local-control
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/daikin-local-control)
|
|
4
|
+
[](https://www.npmjs.com/package/daikin-local-control)
|
|
4
5
|
[](https://github.com/cheziyi/daikin-local-control/actions/workflows/ci.yml?query=branch%3Amain)
|
|
5
6
|
[](package.json)
|
|
7
|
+
[](package.json)
|
|
6
8
|
[](LICENSE)
|
|
9
|
+
[](https://biomejs.dev)
|
|
7
10
|
|
|
8
11
|
A TypeScript and Node.js client for controlling Daikin air conditioners **over
|
|
9
12
|
the local network** (LAN), through their Wi-Fi adapters and with no cloud
|
|
10
|
-
dependency. It implements the
|
|
11
|
-
Controller**
|
|
12
|
-
|
|
13
|
-
BRP072A42, BRP072C42
|
|
14
|
-
[markets where
|
|
13
|
+
dependency. It implements the local-control protocols of the **DAIKIN Mobile
|
|
14
|
+
Controller**, **Daikin Airbase** and **ONECTA** apps, so it works with the units
|
|
15
|
+
and Wi-Fi adapters those apps support — split / multi-split, SkyAir and ducted
|
|
16
|
+
units fitted with adapters such as the BRP072A42, BRP072C42, BRP084, BRP069 and
|
|
17
|
+
BRP15B61 — in the [markets where those apps are used](#coverage--limitations).
|
|
15
18
|
|
|
16
|
-
Daikin Wi-Fi adapters
|
|
17
|
-
each:
|
|
19
|
+
Daikin Wi-Fi adapters speak one of three protocols, and the package has a
|
|
20
|
+
client for each:
|
|
18
21
|
|
|
19
22
|
- **`ClassicDaikinClient`** — classic adapters (BRP072A42, BRP072C42, BRP069).
|
|
20
23
|
Full core control plus typed [extended controls](#extended-controls).
|
|
21
24
|
- **`DsiotDaikinClient`** — newer DSIOT adapters (BRP084). Power, mode,
|
|
22
|
-
setpoint, humidity, fan, swing and
|
|
25
|
+
setpoint, humidity, fan, swing, louver positions and special modes;
|
|
23
26
|
[UNSUPPORTED.md](UNSUPPORTED.md) lists what is not mapped yet.
|
|
27
|
+
- **`AirbaseDaikinClient`** — Airbase adapters (BRP15B61) on ducted units.
|
|
28
|
+
Power, mode, setpoint, fan and direction, plus [zones](#airbase), fan auto
|
|
29
|
+
and airside.
|
|
24
30
|
|
|
25
31
|
You normally don't pick one yourself: `createDaikinClient` chooses the right
|
|
26
32
|
client for each discovered device.
|
|
@@ -47,9 +53,9 @@ if (device === undefined) {
|
|
|
47
53
|
throw new Error("No adapter answered");
|
|
48
54
|
}
|
|
49
55
|
|
|
50
|
-
// 2. Build the right client: createDaikinClient picks the classic
|
|
51
|
-
// client from the device. Pass localPassword only when the
|
|
52
|
-
// child lock code (see Credentials below).
|
|
56
|
+
// 2. Build the right client: createDaikinClient picks the classic, the DSIOT
|
|
57
|
+
// or the Airbase client from the device. Pass localPassword only when the
|
|
58
|
+
// adapter has a child lock code (see Credentials below).
|
|
53
59
|
const client = createDaikinClient(device, {
|
|
54
60
|
terminalUuid: savedUuid, // generated once and stored (see Credentials below)
|
|
55
61
|
localPassword: device.requiresLocalPassword ? "<child lock code>" : undefined,
|
|
@@ -73,17 +79,19 @@ await client.close();
|
|
|
73
79
|
|
|
74
80
|
### Discovery
|
|
75
81
|
|
|
76
|
-
`discoverDevices()` resolves with one `DaikinDevice` for each
|
|
77
|
-
answered, and with an empty array when none did. Each carries the
|
|
78
|
-
`macAddress`, such as `A4:08:EA:CC:91:D4`, and `firmwareVersion`,
|
|
79
|
-
`1.2.51`, when the adapter reports them: key a stored device on
|
|
80
|
-
because `host` changes when the adapter is given a new address.
|
|
82
|
+
`discoverDevices()` resolves with one `DaikinDevice` for each air conditioner's
|
|
83
|
+
adapter that answered, and with an empty array when none did. Each carries the
|
|
84
|
+
adapter's `macAddress`, such as `A4:08:EA:CC:91:D4`, and `firmwareVersion`,
|
|
85
|
+
such as `1.2.51`, when the adapter reports them: key a stored device on
|
|
86
|
+
`macAddress`, because `host` changes when the adapter is given a new address.
|
|
81
87
|
|
|
82
88
|
| It | So |
|
|
83
89
|
| --- | --- |
|
|
84
90
|
| Resolves after `timeoutInMs`, never earlier | Pass `discoverDevices({ timeoutInMs: 1000 })` to wait less than the default 3000 |
|
|
85
91
|
| Broadcasts to `255.255.255.255` | It does not find an adapter on another subnet or VLAN |
|
|
86
|
-
|
|
|
92
|
+
| Keeps the adapters of air conditioners alone | It leaves out the adapter of an air cleaner, a water heater or another appliance, which answers the same broadcast |
|
|
93
|
+
| Asks each classic adapter over HTTP whether it is an Airbase adapter, which replies to the broadcast as a classic one does | It resolves later than `timeoutInMs` while an adapter is slow to answer, by 3000 ms at most for each request after up to 750 ms of retrying a dropped connection, and reports as `classic` one that does not answer. An Airbase device requires no registration and no child lock code, whatever the adapter's reply says |
|
|
94
|
+
| Binds the first free UDP port from 30000 to 30050 | Calls made at the same time each get every reply. It rejects with the socket's error when none of the ports is free, or when the socket fails |
|
|
87
95
|
|
|
88
96
|
Write the device by hand when you already know its address, or when discovery
|
|
89
97
|
cannot reach it:
|
|
@@ -98,8 +106,13 @@ const client = createDaikinClient(device, { terminalUuid: savedUuid });
|
|
|
98
106
|
A device written by hand has no `name`, `macAddress` or `firmwareVersion`: read
|
|
99
107
|
them from the adapter with `await client.getDeviceInfo()`.
|
|
100
108
|
|
|
101
|
-
|
|
102
|
-
|
|
109
|
+
To tell an Airbase adapter from a classic one by its address, request
|
|
110
|
+
`/common/basic_info`: an Airbase adapter answers HTTP 404 there, and `ret=OK`
|
|
111
|
+
at `/skyfi/common/basic_info`.
|
|
112
|
+
|
|
113
|
+
Set `protocol` to `dsiot` for a BRP084, to `airbase` for a BRP15B61 and to
|
|
114
|
+
`classic` for every other adapter: `createDaikinClient()` goes by it and asks
|
|
115
|
+
the adapter nothing. A device without flags is an adapter that requires neither
|
|
103
116
|
registration nor a child lock code. If yours requires either, set
|
|
104
117
|
`requiresRegistration: true` / `requiresLocalPassword: true` and set up
|
|
105
118
|
[credentials](#credentials) to match; add `usesHttps: true` if it also speaks
|
|
@@ -109,25 +122,29 @@ for you.
|
|
|
109
122
|
### Credentials
|
|
110
123
|
|
|
111
124
|
An adapter can need up to three things. Discovery tells you which ones apply
|
|
112
|
-
(`device.requiresRegistration`, `device.requiresLocalPassword`).
|
|
125
|
+
(`device.requiresRegistration`, `device.requiresLocalPassword`). An Airbase
|
|
126
|
+
adapter takes none of them: `createDaikinClient()` still takes a
|
|
127
|
+
`terminalUuid`, and sends it nothing of the three.
|
|
113
128
|
|
|
114
|
-
**Terminal uuid (always).** It identifies your app to the adapter.
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
129
|
+
**Terminal uuid (always).** It identifies your app to the adapter. Pass 32
|
|
130
|
+
lowercase hexadecimal digits, a UUID without its dashes: `createDaikinClient()`
|
|
131
|
+
throws an `Error` for any other form, and sends the uuid as given. Generate it
|
|
132
|
+
**once**, store it, and reuse it on every run: an adapter that requires
|
|
133
|
+
registration remembers it, so a new uuid means registering again.
|
|
118
134
|
|
|
119
135
|
```ts
|
|
120
136
|
import { randomUUID } from "node:crypto";
|
|
121
137
|
|
|
122
|
-
const savedUuid =
|
|
138
|
+
const savedUuid =
|
|
139
|
+
loadFromConfig() ?? saveToConfig(randomUUID().replaceAll("-", ""));
|
|
123
140
|
```
|
|
124
141
|
|
|
125
142
|
**KEY (when `device.requiresRegistration` is `true`).** These
|
|
126
143
|
adapters only answer terminals they have registered. Each adapter's KEY is
|
|
127
144
|
printed on its label, next to the SSID (also in the operation manual). Register
|
|
128
145
|
your uuid with it once. Until you do, every call fails with HTTP 401/403.
|
|
129
|
-
Each uuid is registered separately, so an adapter already set up in
|
|
130
|
-
|
|
146
|
+
Each uuid is registered separately, so an adapter already set up in a Daikin
|
|
147
|
+
app still needs registering here.
|
|
131
148
|
|
|
132
149
|
```ts
|
|
133
150
|
import { ClassicDaikinClient } from "daikin-local-control";
|
|
@@ -147,8 +164,8 @@ Some of these adapters also switch to HTTPS (`device.usesHttps`); see
|
|
|
147
164
|
|
|
148
165
|
**Child lock code (when `device.requiresLocalPassword` is `true`).** The protocol
|
|
149
166
|
calls it the local password (`lpw`). It is the 4-digit code set under Child lock
|
|
150
|
-
in the
|
|
151
|
-
is not your Wi-Fi or Out-of-Home password. Pass it as `localPassword` to
|
|
167
|
+
in the Daikin app the adapter was set up with. It is **not** printed on the
|
|
168
|
+
label, and it is not your Wi-Fi or Out-of-Home password. Pass it as `localPassword` to
|
|
152
169
|
`createDaikinClient`. If it's forgotten, the adapter has to be reset.
|
|
153
170
|
|
|
154
171
|
### State and capabilities
|
|
@@ -201,10 +218,20 @@ given, whether or not the unit lists it.
|
|
|
201
218
|
| `targetTemperatureInC` | In `fan` and `dehumidify` |
|
|
202
219
|
| `targetHumidity` | In `fan` |
|
|
203
220
|
| `fanSpeed` | In `dehumidify`, and on a DSIOT adapter in `cool` while a humidity setting is on: the fan runs on `auto` |
|
|
204
|
-
| `verticalPosition`, `horizontalPosition` | On a classic adapter, when the unit has no 3D airflow. On a DSIOT adapter, a fixed vertical position the unit lacks, and every fixed horizontal position |
|
|
221
|
+
| `verticalPosition`, `horizontalPosition` | On a classic adapter, when the unit has no 3D airflow. On a DSIOT adapter, a fixed vertical position the unit lacks, and every fixed horizontal position. On an Airbase adapter, always |
|
|
222
|
+
|
|
223
|
+
An Airbase adapter also drops every `targetHumidity`, and a
|
|
224
|
+
`targetTemperatureInC` in `fan` as well as in `dehumidify`.
|
|
205
225
|
|
|
206
226
|
`setState()` takes no `specialModes`: turn one on or off with
|
|
207
|
-
|
|
227
|
+
`setSpecialMode()`, which sends a request of its own. Check the mode against
|
|
228
|
+
`getCapabilities().specialModes` first: the client sends any mode as given.
|
|
229
|
+
An Airbase adapter has no request for one, so `AirbaseDaikinClient` rejects
|
|
230
|
+
with a `DaikinError` whose `httpStatusCode` is 501.
|
|
231
|
+
|
|
232
|
+
```ts
|
|
233
|
+
await client.setSpecialMode("powerful", true);
|
|
234
|
+
```
|
|
208
235
|
|
|
209
236
|
Pass `maxStateAgeForSetStateInMs` to `createDaikinClient` when you poll the
|
|
210
237
|
state anyway: `setState()` reads the unit before each write, and with it builds
|
|
@@ -238,7 +265,6 @@ if (client instanceof ClassicDaikinClient) {
|
|
|
238
265
|
maxPowerInPercent: 70,
|
|
239
266
|
mode: "manual",
|
|
240
267
|
});
|
|
241
|
-
await client.setSpecialMode("powerful", true);
|
|
242
268
|
await client.setOutOfHomeAccess(false); // home network only
|
|
243
269
|
await client.setEnergyPrice(0.3); // per kWh
|
|
244
270
|
await client.setMetadata({
|
|
@@ -265,6 +291,43 @@ what is not known about each.
|
|
|
265
291
|
`DsiotDaikinClient` has `reboot()` and the raw `multireqGet` / `multireqUpdate`
|
|
266
292
|
hatches for any `dgc_status` leaf.
|
|
267
293
|
|
|
294
|
+
### Airbase
|
|
295
|
+
|
|
296
|
+
`AirbaseDaikinClient` adds the controls a ducted unit has. Narrow to the
|
|
297
|
+
concrete class to reach them:
|
|
298
|
+
|
|
299
|
+
```ts
|
|
300
|
+
import { AirbaseDaikinClient } from "daikin-local-control";
|
|
301
|
+
|
|
302
|
+
if (client instanceof AirbaseDaikinClient) {
|
|
303
|
+
const zones = await client.getZones(); // [{ name: "Living", enabled: true }, ...]
|
|
304
|
+
await client.setZone(1, true); // open the second zone
|
|
305
|
+
|
|
306
|
+
if ((await client.getFanAuto()) !== null) {
|
|
307
|
+
await client.setFanAuto(true);
|
|
308
|
+
}
|
|
309
|
+
if ((await client.getAirside()) !== null) {
|
|
310
|
+
await client.setAirside(true);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Any other endpoint, by its path below /skyfi.
|
|
314
|
+
const holiday = await client.command("/common/get_holiday");
|
|
315
|
+
}
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
| Method | Does |
|
|
319
|
+
| --- | --- |
|
|
320
|
+
| `getZones()` | Resolves with `{ name, enabled }` for each zone, in the unit's order, and with an empty array on a unit without zones |
|
|
321
|
+
| `setZone(index, enabled)` | Opens or closes the zone at that place in `getZones()`, counted from 0 |
|
|
322
|
+
| `getFanAuto()`, `getAirside()` | Resolve with `true` or `false`, or `null` on a unit that does not take the setting |
|
|
323
|
+
| `setFanAuto(enabled)` | Lets the unit pick the fan speed itself, up to the `fanSpeed` that is set. It switches airside off |
|
|
324
|
+
| `setAirside(enabled)` | Switches airside on or off. Switching it on switches fan auto off and sets `fanSpeed` to `level1` |
|
|
325
|
+
|
|
326
|
+
A `fanSpeed` in `setState()` switches airside off and keeps fan auto.
|
|
327
|
+
|
|
328
|
+
Only a unit with zones takes fan auto and airside, and it takes no `auto` fan
|
|
329
|
+
speed and no `swingMode`: `getCapabilities()` lists what a unit takes.
|
|
330
|
+
|
|
268
331
|
### Errors and retries
|
|
269
332
|
|
|
270
333
|
A client method that fails rejects with a `DaikinError`; branch on
|
|
@@ -276,11 +339,15 @@ A client method that fails rejects with a `DaikinError`; branch on
|
|
|
276
339
|
| 502 | The adapter answered with an error: a `ret` or `rsc` other than OK, or a reply that does not parse |
|
|
277
340
|
| 503 | The adapter refused or dropped the connection (a `DaikinConnectionError`), or DSIOT answered `rsc` 503 |
|
|
278
341
|
| 504 | The request timed out, or the TLS handshake failed |
|
|
342
|
+
| 501 | `AirbaseDaikinClient.setSpecialMode()`: an Airbase adapter has no request for a special mode |
|
|
279
343
|
| Any other | The adapter's own HTTP status, such as 403 |
|
|
280
344
|
|
|
281
|
-
|
|
282
|
-
socket's error,
|
|
283
|
-
|
|
345
|
+
Four failures are not a `DaikinError`: `discoverDevices()` rejects with the
|
|
346
|
+
socket's error, `createDaikinClient()` throws an `Error` for a
|
|
347
|
+
[terminal uuid](#credentials) in the wrong form, `multireqUpdate()` throws
|
|
348
|
+
an `Error` for a path that names a resource without a leaf, such as
|
|
349
|
+
`dgc_status`, and `setZone()` rejects with an `Error` for an index the unit
|
|
350
|
+
has no zone at.
|
|
284
351
|
|
|
285
352
|
Only a `DaikinConnectionError` is retried, up to five times by default.
|
|
286
353
|
[DESIGN.md](DESIGN.md#retries) says why.
|
|
@@ -291,22 +358,181 @@ Only a `DaikinConnectionError` is retried, up to five times by default.
|
|
|
291
358
|
| Stop a client retrying | `retry: { delaysInMs: [] }` to `createDaikinClient` |
|
|
292
359
|
| Send one request that is not safe to repeat | `{ retry: false }` as the last argument of `command()`, `multireqGet()` or `multireqUpdate()` |
|
|
293
360
|
|
|
294
|
-
|
|
361
|
+
### Simulators
|
|
362
|
+
|
|
363
|
+
Test your code against a simulated adapter instead of a real one: start a
|
|
364
|
+
simulator, and point a client at its `host`. Import the simulators from
|
|
365
|
+
`daikin-local-control/simulators`: the package root does not export them.
|
|
366
|
+
|
|
367
|
+
```ts
|
|
368
|
+
import { createDaikinClient } from "daikin-local-control";
|
|
369
|
+
import { startClassicSimulator } from "daikin-local-control/simulators";
|
|
370
|
+
|
|
371
|
+
const simulator = await startClassicSimulator({
|
|
372
|
+
deviceInfo: { name: "Bedroom", macAddress: "A4:08:EA:CC:91:D4" },
|
|
373
|
+
unit: { coolingOnly: true, swing: "vertical" },
|
|
374
|
+
state: { power: true, targetTemperatureInC: 22 },
|
|
375
|
+
sensors: { indoorTemperatureInC: 27 },
|
|
376
|
+
});
|
|
377
|
+
const client = createDaikinClient(
|
|
378
|
+
{ host: simulator.host, protocol: "classic" },
|
|
379
|
+
{ terminalUuid: savedUuid },
|
|
380
|
+
);
|
|
381
|
+
|
|
382
|
+
await client.getState(); // power: true, mode: "cool", targetTemperatureInC: 22
|
|
383
|
+
|
|
384
|
+
simulator.setState({ power: false }); // someone uses the remote control
|
|
385
|
+
simulator.setSensors({ indoorTemperatureInC: 24 }); // the room cools down
|
|
386
|
+
|
|
387
|
+
await client.close();
|
|
388
|
+
await simulator.stop();
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
`startDsiotSimulator()` starts a DSIOT adapter and takes the same options:
|
|
392
|
+
pass `protocol: "dsiot"` to `createDaikinClient`.
|
|
393
|
+
`startAirbaseSimulator()` starts an [Airbase adapter](#airbase-simulator):
|
|
394
|
+
pass `protocol: "airbase"`. A simulator speaks plain HTTP on a free port of
|
|
395
|
+
`127.0.0.1`, which nothing outside the machine reaches.
|
|
396
|
+
|
|
397
|
+
Describe the adapter and its unit in the types the clients resolve with:
|
|
398
|
+
|
|
399
|
+
| Option | Holds | Defaults to |
|
|
400
|
+
| --- | --- | --- |
|
|
401
|
+
| `deviceInfo` | `name`, `macAddress` and `firmwareVersion`, as `getDeviceInfo()` reads them | A name of `Simulator` |
|
|
402
|
+
| `unit` | What the unit has: see below | A heat pump with every fan speed, swing on both axes and all three special modes |
|
|
403
|
+
| `state` | Any fields of a `DaikinState`, `specialModes` included | Off, in cool at 24 °C, on the auto fan speed |
|
|
404
|
+
| `sensors` | Any fields of a `DaikinSensors`; `null` is a reading the unit lacks | 26 °C and 55 % indoors, 30 °C outdoors |
|
|
405
|
+
|
|
406
|
+
| `unit` field | Holds | Defaults to |
|
|
407
|
+
| --- | --- | --- |
|
|
408
|
+
| `coolingOnly` | `true` for a unit that offers dehumidify, cool and fan alone | `false` |
|
|
409
|
+
| `fan` | `false` for a unit whose fan speed cannot be set | `true` |
|
|
410
|
+
| `fanLevels` | 2, 3 or 5 fan levels. Classic simulator only | 5 |
|
|
411
|
+
| `swing` | `none`, `vertical` or `both` | `both` |
|
|
412
|
+
| `specialModes` | Those of `powerful`, `econo` and `streamer` the unit takes | All three |
|
|
413
|
+
| `coolTemperaturesInC`, `heatTemperaturesInC` | `{ min, max }` | 18 to 32 and 10 to 30 |
|
|
414
|
+
|
|
415
|
+
`setState()` and `setSensors()` change a running simulator the same way.
|
|
416
|
+
`setState()` throws an `Error` for a field the unit has nothing to hold, such
|
|
417
|
+
as a `verticalPosition` on a unit without fixed louver positions.
|
|
418
|
+
|
|
419
|
+
#### Airbase simulator
|
|
420
|
+
|
|
421
|
+
`startAirbaseSimulator()` takes `deviceInfo`, `state` and `sensors` as the
|
|
422
|
+
other two do, and a `unit` of its own. It reports no humidity unless
|
|
423
|
+
`sensors` gives one, and its `setState()` throws an `Error` for a
|
|
424
|
+
`targetHumidity`, a louver position or a special mode.
|
|
425
|
+
|
|
426
|
+
```ts
|
|
427
|
+
import { startAirbaseSimulator } from "daikin-local-control/simulators";
|
|
428
|
+
|
|
429
|
+
const simulator = await startAirbaseSimulator({
|
|
430
|
+
unit: { zones: ["Living", "Bedrooms"], airside: true },
|
|
431
|
+
state: { power: true, fanSpeed: "level3" },
|
|
432
|
+
});
|
|
295
433
|
|
|
296
|
-
|
|
297
|
-
|
|
434
|
+
simulator.setZone(1, false); // someone closes a zone on the wall controller
|
|
435
|
+
simulator.controlInfo.f_auto = "1"; // and switches fan auto on
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
| `unit` field | Holds | Defaults to |
|
|
439
|
+
| --- | --- | --- |
|
|
440
|
+
| `zones` | The names of the unit's zones, each open at the start | None: a unit without zones |
|
|
441
|
+
| `auto`, `dehumidify` | `false` for a unit that does not offer the mode | `true` |
|
|
442
|
+
| `fan` | `false` for a unit whose fan speed cannot be set | `true` |
|
|
443
|
+
| `fanLevels` | 2 or 3 fan levels | 3 |
|
|
444
|
+
| `airside` | `true` for a unit with zones that takes airside | `false` |
|
|
445
|
+
| `swing` | `false` for a unit without zones that takes no direction | `true` |
|
|
446
|
+
| `coolTemperaturesInC`, `heatTemperaturesInC` | `{ min, max }`, in whole degrees | None reported, so 18 to 32 and 10 to 30 |
|
|
447
|
+
|
|
448
|
+
A unit with zones takes fan auto, and no `auto` fan speed and no direction.
|
|
449
|
+
It answers HTTP 404 to a path outside `/skyfi`, as an Airbase adapter does,
|
|
450
|
+
so `discoverDevices()` reports it as `airbase` when it is started with
|
|
451
|
+
`answersDiscovery`.
|
|
452
|
+
|
|
453
|
+
| To | Do |
|
|
454
|
+
| --- | --- |
|
|
455
|
+
| Check what the unit holds | Call `getState()` on a client |
|
|
456
|
+
| Check what your code sent | Read `simulator.requests`: each has `method`, `path`, `query`, `headers` and `body` |
|
|
457
|
+
| Require registration (classic) | Pass `key`: the simulator answers HTTP 403 until `registerTerminal()` is given it |
|
|
458
|
+
| Require the child lock code | Pass `localPassword` |
|
|
459
|
+
| Make the adapter slow | Pass `replyDelayInMs`: it answers one request at a time, so a second request waits for the first |
|
|
460
|
+
| Make the adapter unreachable | `await simulator.stop()` |
|
|
461
|
+
| Reach it from another machine | Pass `address`, an address of this machine other than the default `127.0.0.1` |
|
|
462
|
+
| Have `discoverDevices()` find it | Pass `answersDiscovery: true` |
|
|
463
|
+
|
|
464
|
+
Start as many discoverable simulators as you like, in one process or several:
|
|
465
|
+
`discoverDevices()` resolves with one device for each, at `127.0.0.1` and the
|
|
466
|
+
simulator's own port.
|
|
467
|
+
|
|
468
|
+
`discoverDevices()` broadcasts the probe, so it also finds the real adapters
|
|
469
|
+
on the LAN, and it needs a network to broadcast on. Only a program that is
|
|
470
|
+
not a simulator gets in the way: while one holds UDP port 30050, starting a
|
|
471
|
+
simulator with `answersDiscovery` rejects with the socket's error.
|
|
472
|
+
|
|
473
|
+
For anything the options above do not cover, such as humidity settings or
|
|
474
|
+
fixed louver positions, set the adapter's own fields. A classic simulator
|
|
475
|
+
takes `basicInfo`, `modelInfo`, `controlInfo` and `sensorInfo`, the fields of
|
|
476
|
+
each reply, and holds them under the same names. An Airbase simulator takes
|
|
477
|
+
those four and `zoneSetting`. A DSIOT simulator takes `leaves`, and holds each
|
|
478
|
+
leaf by its address. A field given this way replaces
|
|
479
|
+
what `unit` sets, and `state` and `sensors` replace it in turn. The package
|
|
480
|
+
root exports the adapter's codes as tables, such as `CLASSIC_FAN_SPEED_CODES`
|
|
481
|
+
and `DSIOT_PATHS`.
|
|
482
|
+
|
|
483
|
+
```ts
|
|
484
|
+
// A classic unit with 3D airflow: both louvers, and five fixed positions.
|
|
485
|
+
const classic = await startClassicSimulator({
|
|
486
|
+
modelInfo: { en_fdir2: "1", s_fdir_ud: "81", s_fdir_ud_step: "5", s_fdir_lr: "1" },
|
|
487
|
+
controlInfo: { f_dir_ud: "0", f_dir_lr: "0" },
|
|
488
|
+
});
|
|
489
|
+
classic.setState({ verticalPosition: "position3" });
|
|
298
490
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
491
|
+
// A DSIOT unit that takes a humidity setting in cool.
|
|
492
|
+
const dsiot = await startDsiotSimulator({
|
|
493
|
+
leaves: {
|
|
494
|
+
"/dsiot/edge/adr_0100.dgc_status.e_1002.e_3001.p_0C": { pv: "00", pt: 2 },
|
|
495
|
+
},
|
|
496
|
+
});
|
|
497
|
+
dsiot.setState({ targetHumidity: "auto" });
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
A simulator answers HTTP 501 to a request it has no answer for, so a client
|
|
501
|
+
rejects with a `DaikinError` whose `httpStatusCode` is 501.
|
|
502
|
+
[UNSUPPORTED.md](UNSUPPORTED.md#simulators) lists what a simulator leaves out,
|
|
503
|
+
and each answer that is not known to be a real adapter's.
|
|
504
|
+
[DESIGN.md](DESIGN.md#simulators) says what a simulator shares with the
|
|
505
|
+
clients' codecs, and why no more.
|
|
506
|
+
|
|
507
|
+
## Coverage & limitations
|
|
304
508
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
509
|
+
It supports the Wi-Fi adapters of three apps, in the markets where each is
|
|
510
|
+
used, so it is **not valid for every Daikin region or app**.
|
|
511
|
+
|
|
512
|
+
| App | Markets | Adapters | Client |
|
|
513
|
+
| --- | --- | --- | --- |
|
|
514
|
+
| DAIKIN Mobile Controller | Thailand, Singapore, the Philippines, Hong Kong, Taiwan, Australia, New Zealand | BRP072A42, BRP072C42, BRP069 | `ClassicDaikinClient` |
|
|
515
|
+
| DAIKIN Mobile Controller | The same | BRP084 | `DsiotDaikinClient` |
|
|
516
|
+
| Daikin Airbase | Australia | BRP15B61, on a ducted unit | `AirbaseDaikinClient` |
|
|
517
|
+
| ONECTA, once Daikin Online Controller | Europe | BRP069A and BRP069B series | `ClassicDaikinClient` |
|
|
518
|
+
|
|
519
|
+
Each app may be available in more countries than the table lists: if your
|
|
520
|
+
local Daikin site points you to one of the three, this package should apply.
|
|
521
|
+
|
|
522
|
+
Three limits apply, and [UNSUPPORTED.md](UNSUPPORTED.md) says what is not known
|
|
523
|
+
about each:
|
|
524
|
+
|
|
525
|
+
- **Europe**: power, mode, setpoint, fan, swing and special modes go to a
|
|
526
|
+
classic adapter there as they do elsewhere. `getCapabilities()` follows the
|
|
527
|
+
rules of the other markets, and which adapters there speak HTTPS is not
|
|
528
|
+
known.
|
|
529
|
+
- **A DSIOT adapter in Europe** (BRP069C series) is not covered: it may not
|
|
530
|
+
answer on the LAN until it is switched on.
|
|
531
|
+
- **An Airbase adapter** is told from a classic one by how it answers over
|
|
532
|
+
HTTP: no field of its discovery reply is known to differ.
|
|
533
|
+
|
|
534
|
+
Units sold for other apps likely use a different protocol, e.g. GO DAIKIN
|
|
535
|
+
(Malaysia) or Daikin Comfort Control / Daikin One (North America).
|
|
310
536
|
|
|
311
537
|
This package is still young, so expect some rough edges. If something doesn't
|
|
312
538
|
work with your unit, please
|