daikin-local-control 0.4.0 → 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 +47 -4
- package/README.md +154 -46
- package/UNSUPPORTED.md +27 -2
- 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/client.d.ts +3 -2
- package/dist/clients/client.d.ts.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/tables.d.ts +2 -0
- package/dist/codecs/classic/tables.d.ts.map +1 -1
- package/dist/codecs/classic/tables.js +2 -0
- package/dist/codecs/classic/tables.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 +14 -2
- package/dist/codecs/device.js.map +1 -1
- package/dist/codecs/dsiot/tables.d.ts +3 -0
- package/dist/codecs/dsiot/tables.d.ts.map +1 -1
- package/dist/codecs/dsiot/tables.js +3 -0
- package/dist/codecs/dsiot/tables.js.map +1 -1
- package/dist/discovery.d.ts +20 -10
- package/dist/discovery.d.ts.map +1 -1
- package/dist/discovery.js +96 -30
- package/dist/discovery.js.map +1 -1
- package/dist/factory.d.ts +8 -5
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +13 -3
- package/dist/factory.js.map +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -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/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.map +1 -1
- package/dist/simulators/classic.js +2 -2
- package/dist/simulators/classic.js.map +1 -1
- package/dist/simulators/index.d.ts +1 -0
- package/dist/simulators/index.d.ts.map +1 -1
- package/dist/simulators/index.js +1 -0
- package/dist/simulators/index.js.map +1 -1
- package/dist/simulators/simulator.d.ts +11 -10
- package/dist/simulators/simulator.d.ts.map +1 -1
- package/dist/simulators/simulator.js +2 -2
- package/dist/simulators/simulator.js.map +1 -1
- package/dist/transport.d.ts +3 -2
- package/dist/transport.d.ts.map +1 -1
- package/dist/transport.js +4 -1
- package/dist/transport.js.map +1 -1
- package/package.json +7 -3
- package/src/clients/airbase.ts +274 -0
- package/src/clients/client.ts +3 -2
- 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/tables.ts +3 -0
- package/src/codecs/device.ts +18 -2
- package/src/codecs/dsiot/tables.ts +4 -0
- package/src/discovery.ts +101 -32
- package/src/factory.ts +21 -5
- package/src/index.ts +6 -0
- package/src/models/device.ts +3 -2
- package/src/models/zone.ts +8 -0
- package/src/simulators/airbase.ts +495 -0
- package/src/simulators/classic.ts +2 -1
- package/src/simulators/index.ts +1 -0
- package/src/simulators/simulator.ts +13 -12
- package/src/transport.ts +7 -3
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`:
|
|
@@ -154,8 +194,8 @@ Rejected: one list for the whole unit.
|
|
|
154
194
|
|
|
155
195
|
## Simulators
|
|
156
196
|
|
|
157
|
-
`startClassicSimulator` and `
|
|
158
|
-
server. A simulator shares the codecs' tables, `src/codecs/*/tables.ts`, and
|
|
197
|
+
`startClassicSimulator`, `startDsiotSimulator` and `startAirbaseSimulator`
|
|
198
|
+
each start an adapter as a server. A simulator shares the codecs' tables, `src/codecs/*/tables.ts`, and
|
|
159
199
|
none of their functions. A table holds what an adapter calls a thing: its
|
|
160
200
|
field and leaf names, and the code for each value. A function does something
|
|
161
201
|
with it: a codec parses a reply and builds a request, and a simulator answers
|
|
@@ -198,8 +238,11 @@ reply.
|
|
|
198
238
|
- A caller had to know the adapter's encodings to start a unit in a state: a
|
|
199
239
|
DSIOT setpoint of 24 °C is the leaf value `3000`.
|
|
200
240
|
|
|
201
|
-
A simulator takes what the unit has as a
|
|
202
|
-
in terms both protocols have flags for:
|
|
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.
|
|
203
246
|
|
|
204
247
|
Rejected: taking a `DaikinCapabilities`.
|
|
205
248
|
|
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
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,7 +122,9 @@ 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
129
|
**Terminal uuid (always).** It identifies your app to the adapter. Pass 32
|
|
115
130
|
lowercase hexadecimal digits, a UUID without its dashes: `createDaikinClient()`
|
|
@@ -128,8 +143,8 @@ const savedUuid =
|
|
|
128
143
|
adapters only answer terminals they have registered. Each adapter's KEY is
|
|
129
144
|
printed on its label, next to the SSID (also in the operation manual). Register
|
|
130
145
|
your uuid with it once. Until you do, every call fails with HTTP 401/403.
|
|
131
|
-
Each uuid is registered separately, so an adapter already set up in
|
|
132
|
-
|
|
146
|
+
Each uuid is registered separately, so an adapter already set up in a Daikin
|
|
147
|
+
app still needs registering here.
|
|
133
148
|
|
|
134
149
|
```ts
|
|
135
150
|
import { ClassicDaikinClient } from "daikin-local-control";
|
|
@@ -149,8 +164,8 @@ Some of these adapters also switch to HTTPS (`device.usesHttps`); see
|
|
|
149
164
|
|
|
150
165
|
**Child lock code (when `device.requiresLocalPassword` is `true`).** The protocol
|
|
151
166
|
calls it the local password (`lpw`). It is the 4-digit code set under Child lock
|
|
152
|
-
in the
|
|
153
|
-
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
|
|
154
169
|
`createDaikinClient`. If it's forgotten, the adapter has to be reset.
|
|
155
170
|
|
|
156
171
|
### State and capabilities
|
|
@@ -203,11 +218,16 @@ given, whether or not the unit lists it.
|
|
|
203
218
|
| `targetTemperatureInC` | In `fan` and `dehumidify` |
|
|
204
219
|
| `targetHumidity` | In `fan` |
|
|
205
220
|
| `fanSpeed` | In `dehumidify`, and on a DSIOT adapter in `cool` while a humidity setting is on: the fan runs on `auto` |
|
|
206
|
-
| `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`.
|
|
207
225
|
|
|
208
226
|
`setState()` takes no `specialModes`: turn one on or off with
|
|
209
227
|
`setSpecialMode()`, which sends a request of its own. Check the mode against
|
|
210
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.
|
|
211
231
|
|
|
212
232
|
```ts
|
|
213
233
|
await client.setSpecialMode("powerful", true);
|
|
@@ -271,6 +291,43 @@ what is not known about each.
|
|
|
271
291
|
`DsiotDaikinClient` has `reboot()` and the raw `multireqGet` / `multireqUpdate`
|
|
272
292
|
hatches for any `dgc_status` leaf.
|
|
273
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
|
+
|
|
274
331
|
### Errors and retries
|
|
275
332
|
|
|
276
333
|
A client method that fails rejects with a `DaikinError`; branch on
|
|
@@ -282,13 +339,15 @@ A client method that fails rejects with a `DaikinError`; branch on
|
|
|
282
339
|
| 502 | The adapter answered with an error: a `ret` or `rsc` other than OK, or a reply that does not parse |
|
|
283
340
|
| 503 | The adapter refused or dropped the connection (a `DaikinConnectionError`), or DSIOT answered `rsc` 503 |
|
|
284
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 |
|
|
285
343
|
| Any other | The adapter's own HTTP status, such as 403 |
|
|
286
344
|
|
|
287
|
-
|
|
345
|
+
Four failures are not a `DaikinError`: `discoverDevices()` rejects with the
|
|
288
346
|
socket's error, `createDaikinClient()` throws an `Error` for a
|
|
289
|
-
[terminal uuid](#credentials) in the wrong form,
|
|
347
|
+
[terminal uuid](#credentials) in the wrong form, `multireqUpdate()` throws
|
|
290
348
|
an `Error` for a path that names a resource without a leaf, such as
|
|
291
|
-
`dgc_status
|
|
349
|
+
`dgc_status`, and `setZone()` rejects with an `Error` for an index the unit
|
|
350
|
+
has no zone at.
|
|
292
351
|
|
|
293
352
|
Only a `DaikinConnectionError` is retried, up to five times by default.
|
|
294
353
|
[DESIGN.md](DESIGN.md#retries) says why.
|
|
@@ -330,9 +389,10 @@ await simulator.stop();
|
|
|
330
389
|
```
|
|
331
390
|
|
|
332
391
|
`startDsiotSimulator()` starts a DSIOT adapter and takes the same options:
|
|
333
|
-
pass `protocol: "dsiot"` to `createDaikinClient`.
|
|
334
|
-
|
|
335
|
-
|
|
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.
|
|
336
396
|
|
|
337
397
|
Describe the adapter and its unit in the types the clients resolve with:
|
|
338
398
|
|
|
@@ -356,6 +416,40 @@ Describe the adapter and its unit in the types the clients resolve with:
|
|
|
356
416
|
`setState()` throws an `Error` for a field the unit has nothing to hold, such
|
|
357
417
|
as a `verticalPosition` on a unit without fixed louver positions.
|
|
358
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
|
+
});
|
|
433
|
+
|
|
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
|
+
|
|
359
453
|
| To | Do |
|
|
360
454
|
| --- | --- |
|
|
361
455
|
| Check what the unit holds | Call `getState()` on a client |
|
|
@@ -379,8 +473,9 @@ simulator with `answersDiscovery` rejects with the socket's error.
|
|
|
379
473
|
For anything the options above do not cover, such as humidity settings or
|
|
380
474
|
fixed louver positions, set the adapter's own fields. A classic simulator
|
|
381
475
|
takes `basicInfo`, `modelInfo`, `controlInfo` and `sensorInfo`, the fields of
|
|
382
|
-
each reply, and holds them under the same names.
|
|
383
|
-
|
|
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
|
|
384
479
|
what `unit` sets, and `state` and `sensors` replace it in turn. The package
|
|
385
480
|
root exports the adapter's codes as tables, such as `CLASSIC_FAN_SPEED_CODES`
|
|
386
481
|
and `DSIOT_PATHS`.
|
|
@@ -411,20 +506,33 @@ clients' codecs, and why no more.
|
|
|
411
506
|
|
|
412
507
|
## Coverage & limitations
|
|
413
508
|
|
|
414
|
-
It supports the
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
|
418
|
-
|
|
|
419
|
-
|
|
|
420
|
-
|
|
|
421
|
-
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
(
|
|
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).
|
|
428
536
|
|
|
429
537
|
This package is still young, so expect some rough edges. If something doesn't
|
|
430
538
|
work with your unit, please
|
package/UNSUPPORTED.md
CHANGED
|
@@ -31,6 +31,20 @@ To add one, pin down the request and reply first, then delete its row.
|
|
|
31
31
|
| Classic network: `/common/get_network_setting`, `set_network_setting`, `start_wifi_scan`, `get_wifi_scan_result`, `start_wifi_connection`, `permit_wifi_connection`, `set_lpw` | Their parameters. | `ClassicDaikinClient.command()` |
|
|
32
32
|
| Classic firmware update: a POST to `/dkac/system/fwupdate` | The format of the upload. | Not reachable |
|
|
33
33
|
| A terminal uuid in another form: with dashes, uppercase, or not 32 hexadecimal digits | Whether the adapter accepts it. `createDaikinClient` throws an `Error` for one. | Not reachable |
|
|
34
|
+
| Telling an Airbase adapter from a classic one in a discovery reply | Whether any field of the reply differs: both report `type=aircon`. `discoverDevices` asks the adapter over HTTP instead, and takes HTTP 404 at `/common/basic_info` with `ret=OK` at `/skyfi/common/basic_info` for an Airbase adapter. What a classic adapter answers under `/skyfi` is not known, so it is never asked there. | Set `protocol` on the `DaikinDevice` by hand |
|
|
35
|
+
| An Airbase child lock: `lpw_flag` in its `/common/basic_info` | Whether an Airbase adapter has a child lock code, and how a request would carry one. `discoverDevices` reports `requiresLocalPassword: false` for an Airbase adapter whatever its `lpw_flag`, and `AirbaseDaikinClient` sends no `lpw`. | `AirbaseDaikinClient.command()` |
|
|
36
|
+
| Airbase special modes | Whether a unit takes any, and the request. `getState` reports none, and `AirbaseDaikinClient.setSpecialMode` rejects with status 501. | `AirbaseDaikinClient.command()` |
|
|
37
|
+
| Airbase humidity: `shum`, and `dh1` to `dhh` in `/aircon/get_control_info` | Whether a ducted unit has a humidity setting, and what each field holds. `getState` reports `null`, and `setState` sends `shum` back as the unit holds it and none of the `dh` fields. | `AirbaseDaikinClient.command()` |
|
|
38
|
+
| Airbase energy monitoring and demand control | Which field says a unit has either. `getCapabilities` reports `false` for both. | `AirbaseDaikinClient.command()` |
|
|
39
|
+
| Airbase `f_dir` values 4 and 7 | What each stands for. `getState` reports `swingMode: "off"`. | `AirbaseDaikinClient.command()` |
|
|
40
|
+
| Airbase `bk_auto` and `operate` in `/aircon/get_control_info`, on a unit that reports a `bk_auto` other than its mode while it runs in cool or heat | Which of the two modes such a unit remembers a fan speed for. `setState` writes it to the one `bk_auto` names. | `AirbaseDaikinClient.command()` |
|
|
41
|
+
| Airbase `/aircon/set_control_info` without the memory fields: `dt0` to `dt7`, `dfr1`, `dfr2`, `dfd1`, `dfd2`, `auto1`, `auto2`, `airside1` and `airside2` | Whether the adapter takes it, so `setState` sends each one a control changes. | `AirbaseDaikinClient.command()` |
|
|
42
|
+
| Every other Airbase endpoint: timers, programs, schedules, holiday, demand control, energy history, network and firmware | Their parameters and reply fields. | `AirbaseDaikinClient.command()` |
|
|
43
|
+
| A classic adapter sold in Europe: the capability fields of a unit there (`reg=eu`) | Whether such a unit reports them as a unit in the other markets does. `getCapabilities` reads them by the same rules. | `ClassicDaikinClient.command()` |
|
|
44
|
+
| A classic adapter sold in Europe that reports `adp_kind=3` and `en_secure=1` | Whether it speaks HTTPS, as such an adapter does in the other markets. `discoverDevices` sets `usesHttps` for it. | Set `usesHttps: false` on the `DaikinDevice` |
|
|
45
|
+
| A DSIOT adapter sold in Europe (BRP069C series) | Whether its `dgc_status` leaves hold the values a BRP084 holds. Its `adp_i`, `adp_d` and `dgc_status` resources are read with the same request. | `DsiotDaikinClient.multireqGet()` |
|
|
46
|
+
| A DSIOT adapter that answers `/dsiot/multireq` only once it is switched on, by the UDP datagram `DAIKIN_UDP/debug/en_multireq 1` sent to its port 30050 | Which adapters need it, what one answers to the datagram and to `/dsiot/multireq` before it, and how long it stays on. | Not reachable |
|
|
47
|
+
| An adapter whose discovery reply has a `type` other than `aircon` and `GPF`, such as `common_adp`, or none | Whether any is an air conditioner's. `discoverDevices` leaves it out. | Write the `DaikinDevice` by hand |
|
|
34
48
|
|
|
35
49
|
## Not modelled
|
|
36
50
|
|
|
@@ -43,6 +57,9 @@ These values are known, but the package's types don't represent them yet:
|
|
|
43
57
|
| DSIOT laundry mode (`dgc_status.e_1002.e_3003.p_25` = `01`) | The mode in `dgc_status.e_1002.e_3001.p_01` |
|
|
44
58
|
| Classic `f_dir=6` (heat with the special air outlet) | `swingMode: "off"` |
|
|
45
59
|
| Japanese units (`reg=jp`), which follow separate rules for limits, humidity and fan, and take a humidity percentage | The rules for other regions |
|
|
60
|
+
| Airbase zone temperatures: `lztemp_c` and `lztemp_h` in `/aircon/get_zone_setting`, a setpoint for each zone in cool and in heat | Nothing: `getZones` leaves them out, and `setZone` sends them back as the unit holds them |
|
|
61
|
+
| Airbase fan auto and airside | `fanSpeed` holds `f_rate` alone. `AirbaseDaikinClient.getFanAuto` and `getAirside` report the other two |
|
|
62
|
+
| Airbase modes outside `OperatingMode`, such as `HUM` | `mode: "auto"`. `setState` keeps the mode unless the control sets one |
|
|
46
63
|
|
|
47
64
|
These adapter codes pass through a model unchanged, because what each one
|
|
48
65
|
stands for is not known:
|
|
@@ -54,8 +71,9 @@ stands for is not known:
|
|
|
54
71
|
|
|
55
72
|
## Simulators
|
|
56
73
|
|
|
57
|
-
`startClassicSimulator` and `
|
|
58
|
-
request this table leaves them without an answer
|
|
74
|
+
`startClassicSimulator`, `startDsiotSimulator` and `startAirbaseSimulator`
|
|
75
|
+
answer HTTP 501 to every request this table leaves them without an answer
|
|
76
|
+
for:
|
|
59
77
|
|
|
60
78
|
| Left out | What is unknown |
|
|
61
79
|
| --- | --- |
|
|
@@ -66,6 +84,8 @@ request this table leaves them without an answer for:
|
|
|
66
84
|
| A `/dsiot/multireq` body that is not JSON, has no `requests`, or carries an `op` other than 2 in a POST and 3 in a PUT | What an adapter answers. |
|
|
67
85
|
| `setState` on a DSIOT simulator with a humidity percentage, or a fixed horizontal louver position: it throws an `Error` | The step a percentage is counted in, and which value each position is. Set the leaf in `leaves`. |
|
|
68
86
|
| HTTPS | The certificate an adapter presents is issued by Daikin's own CA. A simulator started with a `key` reports `adp_kind=4`, an adapter that registers terminals over HTTP. |
|
|
87
|
+
| An Airbase endpoint without a typed method on `AirbaseDaikinClient`, and any method but GET | The reply fields. |
|
|
88
|
+
| Airbase `/aircon/get_zone_setting` and `set_zone_setting` on a unit without zones | What such a unit answers. |
|
|
69
89
|
|
|
70
90
|
These answers are a simulator's own choice, where an adapter's is not known:
|
|
71
91
|
|
|
@@ -88,3 +108,8 @@ These answers are a simulator's own choice, where an adapter's is not known:
|
|
|
88
108
|
| Still answers `f_dir` in `/aircon/get_control_info` on a classic simulator started with a `unit` whose `swing` is `none` | Whether a classic unit without a louver reports the field. |
|
|
89
109
|
| Makes a request wait while it answers another | Whether an adapter also drops a connection while it is busy. |
|
|
90
110
|
| Answers a discovery probe from UDP port 30050 of its address | The port an adapter answers from. |
|
|
111
|
+
| Answers HTTP 404 to every path outside `/skyfi`, on an Airbase simulator | Which paths besides `/common/basic_info` an Airbase adapter answers 404. |
|
|
112
|
+
| Answers `ret=PARAM NG` to an Airbase `/aircon/set_control_info` without `pow`, `mode`, `stemp`, `shum`, `f_rate`, `f_auto`, `f_airside` or `f_dir`, and to a `set_zone_setting` without `zone_name` or `zone_onoff` | Which requests an Airbase adapter answers `PARAM NG`, and whether it takes a write without one of them. |
|
|
113
|
+
| Keeps what an Airbase unit remembers for cool or for heat equal to what it holds, and names that mode in `bk_auto` once the unit is set to it | What an Airbase unit remembers on its own, and when it changes `bk_auto`. |
|
|
114
|
+
| Reports no `shum`, no `adp_kind` and no `adv` on an Airbase simulator, and `model=NOTSUPPORT` | Which of them an Airbase adapter reports, and what its `model` holds on each unit. |
|
|
115
|
+
| Holds the lists of an Airbase `get_zone_setting` with `%3B` for each `;`, and as many names as the unit has zones | The letter case an adapter sends, and whether it names more zones than `en_zone` counts. |
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { DaikinCapabilities } from "../models/capabilities.js";
|
|
2
|
+
import type { DaikinDeviceInfo } from "../models/device.js";
|
|
3
|
+
import type { DaikinControl, DaikinSensors, DaikinState, DaikinStateAndSensors, SpecialMode } from "../models/state.js";
|
|
4
|
+
import type { DaikinZone } from "../models/zone.js";
|
|
5
|
+
import { type DaikinRequestOptions, type DaikinRetryOptions, type DaikinTransport } from "../transport.js";
|
|
6
|
+
import type { DaikinClient } from "./client.js";
|
|
7
|
+
/** Whether the adapter `transport` reaches is an Airbase adapter: one that
|
|
8
|
+
* answers HTTP 404 at `/common/basic_info`, where a classic adapter answers,
|
|
9
|
+
* and `ret=OK` at `/skyfi/common/basic_info`. It sends the second request
|
|
10
|
+
* only after that 404, and retries a refused or dropped connection as
|
|
11
|
+
* `withRetry` does by default: an adapter drops one while it answers another
|
|
12
|
+
* caller. It resolves with `false` when either request fails any other way.
|
|
13
|
+
* `discoverDevices` calls it, and `src/index.ts` does not export it. */
|
|
14
|
+
export declare function isAirbaseAdapter(transport: DaikinTransport): Promise<boolean>;
|
|
15
|
+
/** Airbase adapter (BRP15B61), on a ducted unit. Reads and writes are HTTP GET
|
|
16
|
+
* with query parameters, as on a classic adapter, at paths under `/skyfi`.
|
|
17
|
+
* It takes no terminal uuid and no local password. */
|
|
18
|
+
export declare class AirbaseDaikinClient implements DaikinClient {
|
|
19
|
+
private readonly transport;
|
|
20
|
+
private readonly retryOptions?;
|
|
21
|
+
private capabilityFields;
|
|
22
|
+
private readonly setStateCache;
|
|
23
|
+
/** `close` closes `transport`, so give each client a transport of its own.
|
|
24
|
+
* `maxStateAgeForSetStateInMs` defaults to 0: see
|
|
25
|
+
* `CreateDaikinClientOptions.maxStateAgeForSetStateInMs`. */
|
|
26
|
+
constructor(transport: DaikinTransport, retryOptions?: DaikinRetryOptions | undefined, maxStateAgeForSetStateInMs?: number);
|
|
27
|
+
close(): Promise<void>;
|
|
28
|
+
getDeviceInfo(): Promise<DaikinDeviceInfo>;
|
|
29
|
+
getCapabilities(): Promise<DaikinCapabilities>;
|
|
30
|
+
getState(): Promise<DaikinState>;
|
|
31
|
+
setState(control: DaikinControl): Promise<void>;
|
|
32
|
+
getSensors(): Promise<DaikinSensors>;
|
|
33
|
+
getStateAndSensors(): Promise<DaikinStateAndSensors>;
|
|
34
|
+
/** Rejects with a `DaikinError` whose status is 501: an Airbase adapter has
|
|
35
|
+
* no known request for a special mode. */
|
|
36
|
+
setSpecialMode(_mode: SpecialMode, _enabled: boolean): Promise<void>;
|
|
37
|
+
/** Whether fan auto is on: the unit then picks the fan speed itself, up to
|
|
38
|
+
* the one that is set. `null` on a unit that takes no fan auto. */
|
|
39
|
+
getFanAuto(): Promise<boolean | null>;
|
|
40
|
+
/** Switches fan auto on or off. It switches airside off, and keeps the fan
|
|
41
|
+
* speed. Check `getFanAuto` first: the client sends it to any unit. */
|
|
42
|
+
setFanAuto(enabled: boolean): Promise<void>;
|
|
43
|
+
/** Whether airside is on. `null` on a unit that takes no airside. */
|
|
44
|
+
getAirside(): Promise<boolean | null>;
|
|
45
|
+
/** Switches airside on or off. Switching it on switches fan auto off and
|
|
46
|
+
* sets the lowest fan speed. Check `getAirside` first: the client sends it
|
|
47
|
+
* to any unit. */
|
|
48
|
+
setAirside(enabled: boolean): Promise<void>;
|
|
49
|
+
/** The unit's zones, in the unit's order; empty on a unit without zones,
|
|
50
|
+
* which is not asked for any. */
|
|
51
|
+
getZones(): Promise<DaikinZone[]>;
|
|
52
|
+
/** Opens or closes the zone at `index` in `getZones`, counted from 0. It
|
|
53
|
+
* rejects with an `Error` for an index the unit has no zone at. */
|
|
54
|
+
setZone(index: number, enabled: boolean): Promise<void>;
|
|
55
|
+
/** Calls any endpoint by its path below `/skyfi`, such as
|
|
56
|
+
* `/aircon/get_control_info`, with query parameters, and returns the reply's
|
|
57
|
+
* `key=value` fields. It throws a `DaikinError` when the reply's `ret` is
|
|
58
|
+
* not `OK`. */
|
|
59
|
+
command(path: string, parameters?: Record<string, string>, { retry }?: DaikinRequestOptions): Promise<Record<string, string>>;
|
|
60
|
+
private readZoneSetting;
|
|
61
|
+
private readControlInfo;
|
|
62
|
+
private readCapabilityFields;
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=airbase.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"airbase.d.ts","sourceRoot":"","sources":["../../src/clients/airbase.ts"],"names":[],"mappings":"AAwBA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AACpE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,KAAK,EACV,aAAa,EACb,aAAa,EACb,WAAW,EACX,qBAAqB,EACrB,WAAW,EACZ,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EACL,KAAK,oBAAoB,EACzB,KAAK,kBAAkB,EACvB,KAAK,eAAe,EAErB,MAAM,iBAAiB,CAAC;AACzB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAmBhD;;;;;;wEAMwE;AACxE,wBAAsB,gBAAgB,CACpC,SAAS,EAAE,eAAe,GACzB,OAAO,CAAC,OAAO,CAAC,CAoBlB;AAED;;sDAEsD;AACtD,qBAAa,mBAAoB,YAAW,YAAY;IASpD,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC;IARhC,OAAO,CAAC,gBAAgB,CAA8C;IACtE,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAgB;IAE9C;;iEAE6D;IAC7D,YACmB,SAAS,EAAE,eAAe,EAC1B,YAAY,CAAC,EAAE,kBAAkB,YAAA,EAClD,0BAA0B,SAAI,EAG/B;IAED,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAErB;IAEK,aAAa,IAAI,OAAO,CAAC,gBAAgB,CAAC,CAE/C;IAEK,eAAe,IAAI,OAAO,CAAC,kBAAkB,CAAC,CAEnD;IAEK,QAAQ,IAAI,OAAO,CAAC,WAAW,CAAC,CAErC;IAEK,QAAQ,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAQpD;IAEK,UAAU,IAAI,OAAO,CAAC,aAAa,CAAC,CAIzC;IAEK,kBAAkB,IAAI,OAAO,CAAC,qBAAqB,CAAC,CAEzD;IAED;8CAC0C;IACpC,cAAc,CAAC,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAEzE;IAID;uEACmE;IAC7D,UAAU,IAAI,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAI1C;IAED;2EACuE;IACjE,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAQhD;IAED,qEAAqE;IAC/D,UAAU,IAAI,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAI1C;IAED;;sBAEkB;IACZ,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAQhD;IAED;qCACiC;IAC3B,QAAQ,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC,CAMtC;IAED;uEACmE;IAC7D,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAO5D;IAED;;;mBAGe;IACT,OAAO,CACX,IAAI,EAAE,MAAM,EACZ,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EACnC,EAAE,KAAY,EAAE,GAAE,oBAAyB,GAC1C,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAqBjC;YAIa,eAAe;YAQf,eAAe;IAM7B,OAAO,CAAC,oBAAoB;CAY7B"}
|