homebridge-dyson-solarcycle-morph 0.2.1 → 0.3.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/CHANGELOG.md +47 -0
- package/README.md +146 -196
- package/config.schema.json +8 -1
- package/dist/accessory.d.ts +1 -0
- package/dist/accessory.js +22 -0
- package/dist/accessory.js.map +1 -1
- package/dist/config.d.ts +6 -0
- package/dist/config.js.map +1 -1
- package/dist/dyson/lamp.d.ts +82 -10
- package/dist/dyson/lamp.js +269 -45
- package/dist/dyson/lamp.js.map +1 -1
- package/dist/dyson/pace.d.ts +40 -0
- package/dist/dyson/pace.js +53 -0
- package/dist/dyson/pace.js.map +1 -0
- package/dist/dyson/protocol.d.ts +45 -2
- package/dist/dyson/protocol.js +86 -2
- package/dist/dyson/protocol.js.map +1 -1
- package/docs/PROTOCOL.md +200 -10
- package/package.json +1 -1
- package/tools/daylight-test.js +531 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,53 @@ All notable changes to this project are documented here.
|
|
|
5
5
|
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.3.0] — 2026-09-18
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Daylight tracking is a switch. It reports what the lamp is actually doing,
|
|
13
|
+
following the button on the lamp's base and the MyDyson app as well as
|
|
14
|
+
HomeKit, and it can be turned on and off. Setting a colour temperature ends
|
|
15
|
+
the tracking, which is the lamp's own behaviour, and the switch follows that
|
|
16
|
+
too. Turn it off per light with `daylightSwitch`.
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- A write arriving immediately behind another was discarded, silently. HomeKit
|
|
21
|
+
sets brightness and colour temperature together whenever a scene is applied,
|
|
22
|
+
and the second of the two was being lost: on a CF06, colour temperature
|
|
23
|
+
reached its target in 1 of 8 attempts written back to back, and 8 of 8 when
|
|
24
|
+
spaced 100 ms apart. Control writes are now paced.
|
|
25
|
+
- The lamp is no longer taken out of daylight mode before every brightness and
|
|
26
|
+
colour-temperature write. It never needed to be — values land while the lamp
|
|
27
|
+
is tracking — and the write that was supposed to do it was never a valid
|
|
28
|
+
command in the first place. Brightness changes now leave daylight tracking
|
|
29
|
+
running; colour-temperature changes still end it, which is the lamp's doing.
|
|
30
|
+
|
|
31
|
+
## [0.2.2] — 2026-09-17
|
|
32
|
+
|
|
33
|
+
### Added
|
|
34
|
+
|
|
35
|
+
- The log carries signal strength: on the line reported when the lamp connects,
|
|
36
|
+
once a minute as an average with its range, and on the line reported when a
|
|
37
|
+
connection drops. The **range** is what shows interference — a lamp that is
|
|
38
|
+
not moving cannot swing by 20 dB on its own — while the average only says
|
|
39
|
+
whether it is too far away.
|
|
40
|
+
- Connecting on a later attempt is reported, since repeated attempts mean
|
|
41
|
+
collisions where connections are established.
|
|
42
|
+
|
|
43
|
+
### Fixed
|
|
44
|
+
|
|
45
|
+
- A lamp switched on at the device itself now reaches HomeKit. Subscribing to a
|
|
46
|
+
characteristic fails intermittently, which was caught and ignored, leaving
|
|
47
|
+
that value silent for the rest of the session. Subscriptions are retried, a
|
|
48
|
+
failure that sticks is warned about, and a slow poll backs them up.
|
|
49
|
+
|
|
50
|
+
### Changed
|
|
51
|
+
|
|
52
|
+
- The README is written for someone setting the plugin up, rather than for
|
|
53
|
+
someone reading the source.
|
|
54
|
+
|
|
8
55
|
## [0.2.1] — 2026-09-17
|
|
9
56
|
|
|
10
57
|
### Changed
|
package/README.md
CHANGED
|
@@ -1,259 +1,209 @@
|
|
|
1
|
-
|
|
1
|
+
<h1 align="center">homebridge-dyson-solarcycle-morph</h1>
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
<p align="center">
|
|
4
|
+
Control your <b>Dyson Solarcycle Morph</b> from the Apple Home app — over Bluetooth, with no cloud in the loop.
|
|
5
|
+
</p>
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
<p align="center">
|
|
8
|
+
<a href="https://github.com/rummeyer/homebridge-dyson-solarcycle-morph/releases"><img src="https://img.shields.io/github/v/release/rummeyer/homebridge-dyson-solarcycle-morph?label=release" alt="Release"></a>
|
|
9
|
+
<a href="https://github.com/rummeyer/homebridge-dyson-solarcycle-morph/blob/main/LICENSE"><img src="https://img.shields.io/badge/licence-MIT-blue" alt="Licence"></a>
|
|
10
|
+
<img src="https://img.shields.io/badge/homebridge-%E2%89%A5%202.0.0-purple" alt="Homebridge 2.0.0+">
|
|
11
|
+
<img src="https://img.shields.io/badge/node-22%20%7C%2024%20%7C%2026-green" alt="Node 22, 24 or 26">
|
|
12
|
+
</p>
|
|
9
13
|
|
|
10
|
-
|
|
11
|
-
(2700–6500 K), plus the lamp's built-in motion sensor if you want it.
|
|
14
|
+
---
|
|
12
15
|
|
|
13
|
-
##
|
|
16
|
+
## What you get
|
|
14
17
|
|
|
15
|
-
|
|
16
|
-
are going to log in with.** This is not optional and it is worth explaining why,
|
|
17
|
-
because it is the one thing that can stop you dead.
|
|
18
|
+
Your lamp appears in the Home app as a **light**, with the controls Apple provides for one:
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
- **On and off**
|
|
21
|
+
- **Brightness**
|
|
22
|
+
- **Colour temperature**, across the lamp's full 2700–6500 K range
|
|
23
|
+
- **Motion**, from the lamp's own sensor, as a separate sensor you can automate on
|
|
24
|
+
- **Siri**: *"Hey Siri, turn on the desk light"*, *"Hey Siri, set the desk light to 50%"*
|
|
24
25
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
there, then continue.
|
|
26
|
+
After setup nothing leaves your home. The lamp has no Wi-Fi at all: the plugin
|
|
27
|
+
talks to it over Bluetooth, and the one cloud request — fetching the lamp's key —
|
|
28
|
+
happens once, during pairing.
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
## Before you start
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
| | |
|
|
33
|
+
|---|---|
|
|
34
|
+
| **Lamp** | Dyson Solarcycle Morph or Lightcycle Morph, added to your MyDyson account |
|
|
35
|
+
| **Homebridge** | 2.0.0 or newer, on Linux |
|
|
36
|
+
| **Node.js** | 22.18, 24 or 26 |
|
|
37
|
+
| **Range** | The Homebridge host must be within Bluetooth range of the lamp |
|
|
33
38
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
+
**The lamp must already be in the MyDyson app.** This is not a formality. The
|
|
40
|
+
lamp ignores every command until it is handed a key that only Dyson can issue,
|
|
41
|
+
against the account the lamp is registered to — there is no way to read it from
|
|
42
|
+
the device. If the lamp is not on your account, pairing returns a 404 and there
|
|
43
|
+
is nothing the plugin can do about it.
|
|
39
44
|
|
|
40
|
-
|
|
41
|
-
|
|
45
|
+
**Linux only.** The plugin talks to BlueZ over D-Bus, so macOS and Windows will
|
|
46
|
+
not work. A Raspberry Pi running Homebridge is the usual home for it.
|
|
42
47
|
|
|
43
|
-
## 1
|
|
48
|
+
## Step 1 — Install the plugin
|
|
44
49
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
```
|
|
50
|
+
In the Homebridge UI, go to **Plugins**, search for
|
|
51
|
+
`homebridge-dyson-solarcycle-morph` and choose **Install**.
|
|
48
52
|
|
|
49
|
-
|
|
50
|
-
`ABC-EU-XXXXXXXX`. **The lamp advertises its serial number as its Bluetooth
|
|
51
|
-
name**, so this one command gives you both values you need:
|
|
53
|
+
## Step 2 — Give it a child bridge
|
|
52
54
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
```
|
|
55
|
+
In the plugin's **⋮** menu, choose **Bridge Settings** and turn the child bridge
|
|
56
|
+
on.
|
|
56
57
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
Name: ABC-EU-XXXXXXXX ← serial number
|
|
60
|
-
UUID: Vendor specific (2dd10010-1c37-452d-8979-d1b4a787d0a4)
|
|
61
|
-
```
|
|
58
|
+
This is worth doing. Bluetooth links drop and reconnect; in a child bridge that
|
|
59
|
+
churn stays in its own process instead of unsettling your other accessories.
|
|
62
60
|
|
|
63
|
-
|
|
64
|
-
this plugin implements.
|
|
61
|
+
## Step 3 — Connect your MyDyson account
|
|
65
62
|
|
|
66
|
-
|
|
63
|
+
Open the plugin's **Settings**. The page walks through it:
|
|
67
64
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
65
|
+
1. **Enter your MyDyson email, password and country.** The country picks the
|
|
66
|
+
Dyson server and the language its requests use.
|
|
67
|
+
2. **Request code**, then type the code Dyson emails you and press **Submit**.
|
|
68
|
+
The page then shows *✓ Authorised*, with when and how many devices your
|
|
69
|
+
account holds.
|
|
70
|
+
3. **Find my lights.** This lists the lights on your account and scans for their
|
|
71
|
+
Bluetooth addresses. Press **Add and pair** next to yours.
|
|
72
|
+
4. **Forget password** once you are done, then **Save**.
|
|
71
73
|
|
|
72
|
-
|
|
73
|
-
image and apt package), plugins live in the Homebridge storage directory rather
|
|
74
|
-
than in npm's global prefix. Install it there instead:
|
|
74
|
+
Restart Homebridge when the page says so.
|
|
75
75
|
|
|
76
|
-
|
|
77
|
-
cd /var/lib/homebridge
|
|
78
|
-
sudo -u homebridge /opt/homebridge/bin/npm install homebridge-dyson-solarcycle-morph
|
|
79
|
-
```
|
|
76
|
+
### What is stored, and where
|
|
80
77
|
|
|
81
|
-
|
|
82
|
-
|
|
78
|
+
The lamp's key and your account session go into the plugin's own storage
|
|
79
|
+
directory, not into `config.json`. The file you can open in the Homebridge UI —
|
|
80
|
+
and paste into a support thread — holds nothing sensitive.
|
|
83
81
|
|
|
84
|
-
|
|
82
|
+
Your password is only needed to exchange a code for a session. **Forget
|
|
83
|
+
password** removes it afterwards; the lamp keeps working, because its key does
|
|
84
|
+
not expire.
|
|
85
85
|
|
|
86
|
-
|
|
86
|
+
## Step 4 — Add the lamp to the Home app
|
|
87
87
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
this and type them by hand if you already did step 1.
|
|
91
|
-
2. **Authorise your MyDyson account.** Enter your email, password and country,
|
|
92
|
-
request a code, and enter the code Dyson emails you.
|
|
93
|
-
3. **Check the result.** Paired lights are listed with the date they were
|
|
94
|
-
authorised, and can be un-paired again.
|
|
88
|
+
The child bridge has its own QR code, under **Bridge Settings**. Scan it in the
|
|
89
|
+
Home app the same way you paired Homebridge itself.
|
|
95
90
|
|
|
96
|
-
|
|
91
|
+
## Everyday use
|
|
97
92
|
|
|
98
|
-
**
|
|
99
|
-
|
|
100
|
-
|
|
93
|
+
**Brightness will not always land exactly where you put it.** The lamp ramps
|
|
94
|
+
towards a value rather than jumping, and trims it to track daylight from its
|
|
95
|
+
location and the time of day. Asking for 100% and seeing 88% is the lamp working
|
|
96
|
+
as designed.
|
|
101
97
|
|
|
102
|
-
|
|
98
|
+
**Changes made at the lamp show up in the Home app**, usually at once and at
|
|
99
|
+
worst within a minute.
|
|
103
100
|
|
|
104
|
-
|
|
101
|
+
**When the lamp is out of reach**, the Home app shows it as *No Response* rather
|
|
102
|
+
than the last value it knew, and commands fail instead of being silently queued.
|
|
103
|
+
The plugin reconnects on its own and reads the lamp's real state when it does.
|
|
105
104
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
105
|
+
**Switching off is checked.** Control commands are unacknowledged, so one can be
|
|
106
|
+
dropped in transit. Power is read back shortly after and resent if it did not
|
|
107
|
+
take — a lamp still lit after being switched off is the one failure nobody can
|
|
108
|
+
work around. Brightness and colour temperature are deliberately not checked,
|
|
109
|
+
because the lamp adjusts them itself and a correction would fight it.
|
|
109
110
|
|
|
110
|
-
|
|
111
|
-
and `--debug` to see every request and response.
|
|
112
|
-
|
|
113
|
-
**Run it in a real terminal.** It reads from stdin interactively, so launching
|
|
114
|
-
it from an editor, an agent or a CI shell prints the prompts but never receives
|
|
115
|
-
your answers — every field arrives empty and Dyson rejects the login with a
|
|
116
|
-
misleading `401 Unable to authenticate user`.
|
|
117
|
-
|
|
118
|
-
## 4. Configure
|
|
119
|
-
|
|
120
|
-
Scanning writes this for you; by hand it looks like:
|
|
121
|
-
|
|
122
|
-
```json
|
|
123
|
-
{
|
|
124
|
-
"platform": "DysonSolarcycleMorph",
|
|
125
|
-
"lights": [
|
|
126
|
-
{
|
|
127
|
-
"name": "Desk Light",
|
|
128
|
-
"mac": "AA:BB:CC:DD:EE:FF",
|
|
129
|
-
"serial": "ABC-EU-XXXXXXXX",
|
|
130
|
-
"motionSensor": true
|
|
131
|
-
}
|
|
132
|
-
]
|
|
133
|
-
}
|
|
134
|
-
```
|
|
111
|
+
## Settings
|
|
135
112
|
|
|
136
|
-
|
|
137
|
-
|---|---|
|
|
138
|
-
| `name` | What the lamp is called in HomeKit. |
|
|
139
|
-
| `mac` | BLE address from the scan. |
|
|
140
|
-
| `serial` | Identifies the accessory to HomeKit and keys its stored credentials. |
|
|
141
|
-
| `motionSensor` | Expose the lamp's motion detector as a HomeKit sensor. Default `false`. |
|
|
113
|
+
Everything except the lights is optional.
|
|
142
114
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
115
|
+
| Setting | |
|
|
116
|
+
|---|---|
|
|
117
|
+
| **Name** | What the plugin is called in the Homebridge log |
|
|
118
|
+
| **Lights → Name** | What the lamp is called in the Home app |
|
|
119
|
+
| **Lights → BLE MAC address** | Filled in by the scan |
|
|
120
|
+
| **Lights → Serial number** | Filled in by the scan. Identifies the accessory, so changing it creates a new one |
|
|
121
|
+
| **Lights → Expose the motion sensor** | Adds the lamp's motion detector as a HomeKit sensor |
|
|
122
|
+
| **Bluetooth adapter** | Only needed if the host has more than one, e.g. `hci1` |
|
|
146
123
|
|
|
147
|
-
|
|
148
|
-
reconnect; in a child bridge that churn stays isolated from your other
|
|
149
|
-
accessories. In the Homebridge UI it is one toggle under the plugin's settings.
|
|
124
|
+
## If something goes wrong
|
|
150
125
|
|
|
151
|
-
|
|
126
|
+
**The lamp shows as *No Response*.** There is no Bluetooth link at that moment.
|
|
127
|
+
The plugin keeps reconnecting; if it does not recover, read on.
|
|
152
128
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
129
|
+
**The log repeats `le-connection-abort-by-local` and never connects.** The lamp's
|
|
130
|
+
Bluetooth stack can reach a state where it advertises perfectly well — a scan
|
|
131
|
+
finds it, at a good signal strength — but refuses every connection.
|
|
132
|
+
**Disconnecting the lamp from power for ten seconds clears it**, reliably.
|
|
133
|
+
Nothing on the Homebridge side does. The plugin thins its attempts out to five
|
|
134
|
+
minutes apart, so it will pick the lamp up again by itself once you have.
|
|
157
135
|
|
|
158
|
-
**It connects, drops
|
|
159
|
-
|
|
160
|
-
when it connects and warns below about −80 dBm; at that level BLE hits its
|
|
161
|
-
supervision timeout and the link dies. Check it directly with:
|
|
136
|
+
**It connects, drops, reconnects, over and over.** Look at the signal line in the
|
|
137
|
+
log:
|
|
162
138
|
|
|
163
|
-
```
|
|
164
|
-
|
|
139
|
+
```
|
|
140
|
+
Signal from F0:… : -73 dBm average, -80 to -67
|
|
165
141
|
```
|
|
166
142
|
|
|
167
|
-
|
|
168
|
-
|
|
143
|
+
The **average** answers one question — is the lamp too far away. Around −60 dBm
|
|
144
|
+
is comfortable, −75 workable, −85 will not hold.
|
|
169
145
|
|
|
170
|
-
**
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
nothing on the Homebridge side does. The plugin keeps retrying and thins the
|
|
175
|
-
attempts out to five minutes apart, so it will pick the lamp up again by itself
|
|
176
|
-
once you have done that.
|
|
146
|
+
The **spread** answers a different one. A lamp that is not moving cannot swing by
|
|
147
|
+
20 dB on its own, so a wide spread means something else is using the band. So do
|
|
148
|
+
frequent `Lost connection` lines, and `Connected … on attempt 3 of 4`, which
|
|
149
|
+
means collisions where connections are established.
|
|
177
150
|
|
|
178
|
-
|
|
179
|
-
|
|
151
|
+
On a Raspberry Pi, Wi-Fi and Bluetooth share one chip and often one antenna, so
|
|
152
|
+
an active Wi-Fi link is the first thing to rule out — particularly on channel 1,
|
|
153
|
+
which sits directly on a Bluetooth advertising channel. If the host is on
|
|
154
|
+
Ethernet anyway, `sudo nmcli radio wifi off` settles it. Zigbee is worth checking
|
|
155
|
+
next: channels 15, 20 and 25 sit in the gaps between Wi-Fi 1, 6 and 11.
|
|
180
156
|
|
|
181
157
|
**`… is not paired yet`.** No key is stored for that serial. Open the plugin's
|
|
182
|
-
settings
|
|
183
|
-
|
|
158
|
+
settings and authorise your account. Check the serial matches the lamp exactly —
|
|
159
|
+
it is what the key is filed under.
|
|
184
160
|
|
|
185
|
-
**`PayloadB failed HMAC verification`.** The stored
|
|
186
|
-
|
|
161
|
+
**`PayloadB failed HMAC verification`.** The stored key is wrong or corrupted.
|
|
162
|
+
Pair the lamp again.
|
|
187
163
|
|
|
188
|
-
**404 when fetching the
|
|
164
|
+
**404 when fetching the key.** The lamp is not registered to that Dyson account.
|
|
189
165
|
See *Before you start*.
|
|
190
166
|
|
|
191
|
-
**`Not authorized` from D-Bus
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
```xml
|
|
198
|
-
<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
|
|
199
|
-
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
|
|
200
|
-
<busconfig>
|
|
201
|
-
<policy user="homebridge">
|
|
202
|
-
<allow own="org.bluez"/>
|
|
203
|
-
<allow send_destination="org.bluez"/>
|
|
204
|
-
<allow send_interface="org.bluez.GattCharacteristic1"/>
|
|
205
|
-
<allow send_interface="org.bluez.GattDescriptor1"/>
|
|
206
|
-
<allow send_interface="org.freedesktop.DBus.ObjectManager"/>
|
|
207
|
-
<allow send_interface="org.freedesktop.DBus.Properties"/>
|
|
208
|
-
</policy>
|
|
209
|
-
</busconfig>
|
|
210
|
-
```
|
|
167
|
+
**`Not authorized` from D-Bus.** Only on distributions that restrict BlueZ
|
|
168
|
+
access — Debian and Raspberry Pi OS already allow it. If yours does not, create
|
|
169
|
+
`/etc/dbus-1/system.d/node-ble.conf` with a policy for the user Homebridge runs
|
|
170
|
+
as, then restart `dbus`. There is an example in
|
|
171
|
+
[the node-ble documentation](https://github.com/chrvadala/node-ble#provide-permissions).
|
|
211
172
|
|
|
212
|
-
|
|
213
|
-
right now. Commands are not queued while that is the case: they fail, so you
|
|
214
|
-
know they did not happen. The plugin reconnects on its own and reads the lamp's
|
|
215
|
-
real state when it does.
|
|
173
|
+
## Good to know
|
|
216
174
|
|
|
217
|
-
**
|
|
218
|
-
|
|
219
|
-
its location and the time. Only power is checked and resent, because it is the
|
|
220
|
-
only value whose outcome is unambiguous — see
|
|
221
|
-
[docs/PROTOCOL.md](docs/PROTOCOL.md).
|
|
175
|
+
**Bluetooth only.** These lamps have no Wi-Fi, so the Homebridge host has to be
|
|
176
|
+
within range. There is no cloud fallback when it is not.
|
|
222
177
|
|
|
223
|
-
**
|
|
224
|
-
|
|
225
|
-
|
|
178
|
+
**A few failed attempts at startup are normal.** Connecting to this lamp
|
|
179
|
+
succeeds roughly two times in three, whatever you do, so the plugin simply tries
|
|
180
|
+
several times in quick succession. Only a cycle that never succeeds is worth
|
|
181
|
+
looking at.
|
|
226
182
|
|
|
227
|
-
|
|
183
|
+
**Daylight tracking appears as its own switch.** It shows what the lamp is
|
|
184
|
+
actually doing, following the button on the lamp's base and the MyDyson app as
|
|
185
|
+
well as HomeKit. Setting a colour temperature ends the tracking — that is the
|
|
186
|
+
lamp's own behaviour, not the plugin's — so the switch turns itself off when you
|
|
187
|
+
do. Turn it off per light with `daylightSwitch` if you would rather not have it.
|
|
228
188
|
|
|
229
|
-
`
|
|
230
|
-
|
|
189
|
+
**Setup without the Homebridge UI.** `dyson-morph-pair` does the same pairing
|
|
190
|
+
from a terminal. See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
231
191
|
|
|
232
|
-
|
|
233
|
-
npm install node-ble
|
|
234
|
-
node tools/ble-probe.js AA:BB:CC:DD:EE:FF
|
|
235
|
-
```
|
|
236
|
-
|
|
237
|
-
Useful for checking a model this plugin has not been tried against.
|
|
192
|
+
## For developers
|
|
238
193
|
|
|
239
|
-
|
|
194
|
+
The protocol, including where a real lamp disagreed with the published notes, is
|
|
195
|
+
written up in [docs/PROTOCOL.md](docs/PROTOCOL.md). Building, testing and
|
|
196
|
+
releasing are in [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
240
197
|
|
|
241
|
-
|
|
242
|
-
framing and the handshake, including where a real lamp disagreed with the
|
|
243
|
-
published notes.
|
|
198
|
+
## Credits
|
|
244
199
|
|
|
245
|
-
|
|
200
|
+
The BLE protocol was reverse-engineered from the MyDyson Android app by
|
|
201
|
+
**S-Termi** and documented in
|
|
246
202
|
[cmgrayb/hass-dyson](https://github.com/cmgrayb/hass-dyson). This plugin is an
|
|
247
|
-
independent TypeScript implementation, cross-checked against that reference
|
|
248
|
-
`npm test`.
|
|
249
|
-
|
|
250
|
-
## Development
|
|
251
|
-
|
|
252
|
-
See [CONTRIBUTING.md](CONTRIBUTING.md) for the build, how to try a change on
|
|
253
|
-
real hardware, and how releases are published.
|
|
203
|
+
independent TypeScript implementation, cross-checked against that reference.
|
|
254
204
|
|
|
255
205
|
Not affiliated with or endorsed by Dyson.
|
|
256
206
|
|
|
257
|
-
##
|
|
207
|
+
## Licence
|
|
258
208
|
|
|
259
209
|
MIT — see [LICENSE](LICENSE).
|
package/config.schema.json
CHANGED
|
@@ -50,6 +50,12 @@
|
|
|
50
50
|
"type": "boolean",
|
|
51
51
|
"default": false,
|
|
52
52
|
"description": "Adds the lamp's built-in motion detector as a HomeKit motion sensor."
|
|
53
|
+
},
|
|
54
|
+
"daylightSwitch": {
|
|
55
|
+
"title": "Expose daylight tracking",
|
|
56
|
+
"type": "boolean",
|
|
57
|
+
"default": true,
|
|
58
|
+
"description": "Adds a switch for the lamp's daylight tracking, which adjusts colour temperature to the time of day. It reports the true state, following the button on the lamp base and the MyDyson app, and setting a colour temperature by hand ends the tracking as the lamp itself decides."
|
|
53
59
|
}
|
|
54
60
|
}
|
|
55
61
|
}
|
|
@@ -99,7 +105,8 @@
|
|
|
99
105
|
"lights[].name",
|
|
100
106
|
"lights[].mac",
|
|
101
107
|
"lights[].serial",
|
|
102
|
-
"lights[].motionSensor"
|
|
108
|
+
"lights[].motionSensor",
|
|
109
|
+
"lights[].daylightSwitch"
|
|
103
110
|
]
|
|
104
111
|
}
|
|
105
112
|
]
|
package/dist/accessory.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ export declare class MorphAccessory {
|
|
|
15
15
|
private readonly config;
|
|
16
16
|
private readonly lightbulb;
|
|
17
17
|
private readonly motion?;
|
|
18
|
+
private readonly daylight?;
|
|
18
19
|
private readonly lamp;
|
|
19
20
|
constructor(platform: MorphPlatform, accessory: PlatformAccessory, config: ResolvedLightConfig);
|
|
20
21
|
start(): Promise<void>;
|
package/dist/accessory.js
CHANGED
|
@@ -15,6 +15,7 @@ export class MorphAccessory {
|
|
|
15
15
|
config;
|
|
16
16
|
lightbulb;
|
|
17
17
|
motion;
|
|
18
|
+
daylight;
|
|
18
19
|
lamp;
|
|
19
20
|
constructor(platform, accessory, config) {
|
|
20
21
|
this.platform = platform;
|
|
@@ -57,7 +58,24 @@ export class MorphAccessory {
|
|
|
57
58
|
this.lightbulb.updateCharacteristic(Characteristic.On, state.on);
|
|
58
59
|
this.lightbulb.updateCharacteristic(Characteristic.Brightness, state.brightness);
|
|
59
60
|
this.lightbulb.updateCharacteristic(Characteristic.ColorTemperature, kelvinToMired(state.kelvin));
|
|
61
|
+
this.daylight?.updateCharacteristic(Characteristic.On, state.daylight);
|
|
60
62
|
});
|
|
63
|
+
if (config.daylightSwitch !== false) {
|
|
64
|
+
// A switch rather than a characteristic on the lightbulb: HomeKit renders
|
|
65
|
+
// only the characteristics it knows, so a custom one would be invisible in
|
|
66
|
+
// the Home app and reachable only from third-party clients.
|
|
67
|
+
this.daylight =
|
|
68
|
+
this.accessory.getService(Service.Switch) ??
|
|
69
|
+
this.accessory.addService(Service.Switch, `${config.name} Daylight`, 'daylight');
|
|
70
|
+
this.daylight.setCharacteristic(Characteristic.Name, `${config.name} Daylight`);
|
|
71
|
+
this.daylight
|
|
72
|
+
.getCharacteristic(Characteristic.On)
|
|
73
|
+
.onGet(() => this.live(() => this.lamp.getState().daylight))
|
|
74
|
+
// Reports the mode faithfully but cannot change it; see setDaylight.
|
|
75
|
+
// The rejection is what makes the Home app put the switch back rather
|
|
76
|
+
// than leave it showing a state the lamp is not in.
|
|
77
|
+
.onSet((value) => this.handleSet('daylight mode', () => this.lamp.setDaylight(value)));
|
|
78
|
+
}
|
|
61
79
|
if (config.motionSensor) {
|
|
62
80
|
this.motion =
|
|
63
81
|
this.accessory.getService(Service.MotionSensor) ??
|
|
@@ -73,6 +91,9 @@ export class MorphAccessory {
|
|
|
73
91
|
this.lightbulb.updateCharacteristic(Characteristic.On, state.on);
|
|
74
92
|
this.lightbulb.updateCharacteristic(Characteristic.Brightness, state.brightness);
|
|
75
93
|
this.lightbulb.updateCharacteristic(Characteristic.ColorTemperature, kelvinToMired(state.kelvin));
|
|
94
|
+
// Follows the lamp leaving daylight mode on its own, which it does as soon
|
|
95
|
+
// as colour temperature is set by hand.
|
|
96
|
+
this.daylight?.updateCharacteristic(Characteristic.On, state.daylight);
|
|
76
97
|
});
|
|
77
98
|
}
|
|
78
99
|
async start() {
|
|
@@ -108,6 +129,7 @@ export class MorphAccessory {
|
|
|
108
129
|
this.lightbulb.updateCharacteristic(characteristic, error);
|
|
109
130
|
}
|
|
110
131
|
this.motion?.updateCharacteristic(Characteristic.MotionDetected, error);
|
|
132
|
+
this.daylight?.updateCharacteristic(Characteristic.On, error);
|
|
111
133
|
}
|
|
112
134
|
/**
|
|
113
135
|
* Apply a write, reporting failure to HomeKit rather than swallowing it.
|
package/dist/accessory.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"accessory.js","sourceRoot":"","sources":["../src/accessory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAI5C,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAG3F;;;;;;;GAOG;AACH,MAAM,OAAO,cAAc;
|
|
1
|
+
{"version":3,"file":"accessory.js","sourceRoot":"","sources":["../src/accessory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAI5C,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAG3F;;;;;;;GAOG;AACH,MAAM,OAAO,cAAc;IAON;IACA;IACA;IARF,SAAS,CAAU;IACnB,MAAM,CAAW;IACjB,QAAQ,CAAW;IACnB,IAAI,CAAiB;IAEtC,YACmB,QAAuB,EACvB,SAA4B,EAC5B,MAA2B;QAF3B,aAAQ,GAAR,QAAQ,CAAe;QACvB,cAAS,GAAT,SAAS,CAAmB;QAC5B,WAAM,GAAN,MAAM,CAAqB;QAE5C,MAAM,EAAE,OAAO,EAAE,cAAc,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC;QAE1D,IAAI,CAAC,IAAI,GAAG,IAAI,cAAc,CAAC;YAC7B,GAAG,EAAE,MAAM,CAAC,GAAG;YACf,GAAG,EAAE,MAAM,CAAC,GAAG;YACf,SAAS,EAAE,MAAM,CAAC,SAAS;YAC3B,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,OAAO;YAChC,GAAG,EAAE,QAAQ,CAAC,GAAG;SAClB,CAAC,CAAC;QAEH,CAAC,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,OAAO,CAAC,oBAAoB,CAAC,IAAI,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,OAAO,CAAC,oBAAoB,CAAC,CAAC;aACjH,iBAAiB,CAAC,cAAc,CAAC,YAAY,EAAE,OAAO,CAAC;aACvD,iBAAiB,CAAC,cAAc,CAAC,KAAK,EAAE,kBAAkB,CAAC;aAC3D,iBAAiB,CAAC,cAAc,CAAC,YAAY,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;QAEjE,IAAI,CAAC,SAAS;YACZ,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,OAAO,CAAC,SAAS,CAAC,IAAI,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,OAAO,CAAC,SAAS,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;QAC5G,IAAI,CAAC,SAAS,CAAC,iBAAiB,CAAC,cAAc,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;QAEnE,IAAI,CAAC,SAAS;aACX,iBAAiB,CAAC,cAAc,CAAC,EAAE,CAAC;aACpC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAC;aACrD,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAgB,CAAC,CAAC,CAAC,CAAC;QAEzF,IAAI,CAAC,SAAS;aACX,iBAAiB,CAAC,cAAc,CAAC,UAAU,CAAC;aAC5C,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,UAAU,CAAC,CAAC;aAC7D,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,YAAY,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,KAAe,CAAC,CAAC,CAAC,CAAC;QAElG,IAAI,CAAC,SAAS;aACX,iBAAiB,CAAC,cAAc,CAAC,gBAAgB,CAAC;YACnD,wDAAwD;aACvD,QAAQ,CAAC,EAAE,QAAQ,EAAE,aAAa,CAAC,UAAU,CAAC,EAAE,QAAQ,EAAE,aAAa,CAAC,UAAU,CAAC,EAAE,CAAC;aACtF,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC;aACxE,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,oBAAoB,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,mBAAmB,CAAC,aAAa,CAAC,KAAe,CAAC,CAAC,CAAC,CAAC,CAAC;QAE/H,4EAA4E;QAC5E,wCAAwC;QACxC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,cAAc,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,eAAe,EAAE,CAAC,CAAC;QAC3D,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,WAAW,EAAE,GAAG,EAAE;YAC7B,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;YACnC,IAAI,CAAC,SAAS,CAAC,oBAAoB,CAAC,cAAc,CAAC,EAAE,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC;YACjE,IAAI,CAAC,SAAS,CAAC,oBAAoB,CAAC,cAAc,CAAC,UAAU,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;YACjF,IAAI,CAAC,SAAS,CAAC,oBAAoB,CAAC,cAAc,CAAC,gBAAgB,EAAE,aAAa,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;YAClG,IAAI,CAAC,QAAQ,EAAE,oBAAoB,CAAC,cAAc,CAAC,EAAE,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC;QACzE,CAAC,CAAC,CAAC;QAEH,IAAI,MAAM,CAAC,cAAc,KAAK,KAAK,EAAE,CAAC;YACpC,0EAA0E;YAC1E,2EAA2E;YAC3E,4DAA4D;YAC5D,IAAI,CAAC,QAAQ;gBACX,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,OAAO,CAAC,MAAM,CAAC;oBACzC,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,MAAM,CAAC,IAAI,WAAW,EAAE,UAAU,CAAC,CAAC;YACnF,IAAI,CAAC,QAAQ,CAAC,iBAAiB,CAAC,cAAc,CAAC,IAAI,EAAE,GAAG,MAAM,CAAC,IAAI,WAAW,CAAC,CAAC;YAChF,IAAI,CAAC,QAAQ;iBACV,iBAAiB,CAAC,cAAc,CAAC,EAAE,CAAC;iBACpC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,CAAC;gBAC5D,qEAAqE;gBACrE,sEAAsE;gBACtE,oDAAoD;iBACnD,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,eAAe,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,KAAgB,CAAC,CAAC,CAAC,CAAC;QACtG,CAAC;QAED,IAAI,MAAM,CAAC,YAAY,EAAE,CAAC;YACxB,IAAI,CAAC,MAAM;gBACT,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,OAAO,CAAC,YAAY,CAAC;oBAC/C,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,OAAO,CAAC,YAAY,EAAE,GAAG,MAAM,CAAC,IAAI,SAAS,CAAC,CAAC;YAC3E,IAAI,CAAC,MAAM;iBACR,iBAAiB,CAAC,cAAc,CAAC,cAAc,CAAC;iBAChD,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,iBAAiB,CAAC,cAAc,CAAC,cAAc,CAAC,CAAC,KAAK,IAAI,KAAK,CAAC,CAAC,CAAC;YAC9G,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,EAAE;gBAClC,IAAI,CAAC,MAAM,EAAE,oBAAoB,CAAC,cAAc,CAAC,cAAc,EAAE,QAAQ,CAAC,CAAC;YAC7E,CAAC,CAAC,CAAC;QACL,CAAC;QAED,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;YAC9B,IAAI,CAAC,SAAS,CAAC,oBAAoB,CAAC,cAAc,CAAC,EAAE,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC;YACjE,IAAI,CAAC,SAAS,CAAC,oBAAoB,CAAC,cAAc,CAAC,UAAU,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;YACjF,IAAI,CAAC,SAAS,CAAC,oBAAoB,CAAC,cAAc,CAAC,gBAAgB,EAAE,aAAa,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;YAClG,2EAA2E;YAC3E,wCAAwC;YACxC,IAAI,CAAC,QAAQ,EAAE,oBAAoB,CAAC,cAAc,CAAC,EAAE,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC;QACzE,CAAC,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,KAAK;QACT,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;QAC1B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,mCAAmC,IAAI,CAAC,MAAM,CAAC,IAAI,KAAK,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAC1G,CAAC;IACH,CAAC;IAED,KAAK,CAAC,IAAI;QACR,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;IACzB,CAAC;IAED;;;;;;;OAOG;IACK,IAAI,CAAI,IAAa;QAC3B,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC;YAC7B,MAAM,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,cAAc,4EAE7C,CAAC;QACJ,CAAC;QACD,OAAO,IAAI,EAAE,CAAC;IAChB,CAAC;IAED,0DAA0D;IAClD,eAAe;QACrB,MAAM,EAAE,cAAc,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC;QACjD,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,kCAAkC,CAAC,CAAC;QAC/E,KAAK,MAAM,cAAc,IAAI,CAAC,cAAc,CAAC,EAAE,EAAE,cAAc,CAAC,UAAU,EAAE,cAAc,CAAC,gBAAgB,CAAC,EAAE,CAAC;YAC7G,IAAI,CAAC,SAAS,CAAC,oBAAoB,CAAC,cAAc,EAAE,KAAK,CAAC,CAAC;QAC7D,CAAC;QACD,IAAI,CAAC,MAAM,EAAE,oBAAoB,CAAC,cAAc,CAAC,cAAc,EAAE,KAAK,CAAC,CAAC;QACxE,IAAI,CAAC,QAAQ,EAAE,oBAAoB,CAAC,cAAc,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;IAChE,CAAC;IAED;;;;;;OAMG;IACK,KAAK,CAAC,SAAS,CAAC,IAAY,EAAE,KAA0B;QAC9D,IAAI,CAAC;YACH,MAAM,KAAK,EAAE,CAAC;QAChB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,IAAI,OAAO,IAAI,CAAC,MAAM,CAAC,IAAI,YAAY,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YACjG,MAAM,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,cAAc,4EAE7C,CAAC;QACJ,CAAC;IACH,CAAC;CACF"}
|
package/dist/config.d.ts
CHANGED
|
@@ -17,6 +17,12 @@ export interface LightConfig {
|
|
|
17
17
|
accountId?: string;
|
|
18
18
|
/** Expose the built-in motion sensor as a separate HomeKit service. */
|
|
19
19
|
motionSensor?: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Expose daylight tracking as a switch. On by default: it is the lamp's
|
|
22
|
+
* defining feature and there is no other way to turn it back on from HomeKit
|
|
23
|
+
* once setting a colour temperature has ended it.
|
|
24
|
+
*/
|
|
25
|
+
daylightSwitch?: boolean;
|
|
20
26
|
}
|
|
21
27
|
/** A lamp whose credentials are known, from the config or the credential store. */
|
|
22
28
|
export type ResolvedLightConfig = LightConfig & {
|
package/dist/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAqDA;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,KAA2B,EAAE,KAAa;IAC5E,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,MAAM,KAAK,GAAG,UAAU,KAAK,GAAG,CAAC;IAEjC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC;QACxB,QAAQ,CAAC,IAAI,CAAC,GAAG,KAAK,mBAAmB,CAAC,CAAC;IAC7C,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,sCAAsC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QAC1E,QAAQ,CAAC,IAAI,CAAC,GAAG,KAAK,8CAA8C,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACpG,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE,CAAC;QAC1B,QAAQ,CAAC,IAAI,CAAC,GAAG,KAAK,qBAAqB,CAAC,CAAC;IAC/C,CAAC;IACD,yEAAyE;IACzE,8EAA8E;IAC9E,4CAA4C;IAC5C,IAAI,KAAK,CAAC,GAAG,KAAK,SAAS,IAAI,CAAC,CAAC,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC;QACjG,QAAQ,CAAC,IAAI,CAAC,GAAG,KAAK,wCAAwC,CAAC,CAAC;IAClE,CAAC;IACD,IAAI,KAAK,CAAC,SAAS,KAAK,SAAS,IAAI,CAAC,oBAAoB,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC;QACjF,QAAQ,CAAC,IAAI,CAAC,GAAG,KAAK,yCAAyC,CAAC,CAAC;IACnE,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,GAAG,KAAK,SAAS,CAAC,KAAK,CAAC,KAAK,CAAC,SAAS,KAAK,SAAS,CAAC,EAAE,CAAC;QAClE,QAAQ,CAAC,IAAI,CAAC,GAAG,KAAK,8FAA8F,CAAC,CAAC;IACxH,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC"}
|