homebridge-dyson-solarcycle-morph 0.4.0 → 1.0.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 +26 -0
- package/README.md +117 -79
- package/config.schema.json +9 -9
- package/dist/accessory.d.ts +17 -1
- package/dist/accessory.js +62 -18
- package/dist/accessory.js.map +1 -1
- package/dist/config.d.ts +4 -2
- package/dist/config.js.map +1 -1
- package/dist/dyson/lamp.d.ts +15 -1
- package/dist/dyson/lamp.js +40 -12
- package/dist/dyson/lamp.js.map +1 -1
- package/dist/dyson/protocol.d.ts +28 -5
- package/dist/dyson/protocol.js +40 -5
- package/dist/dyson/protocol.js.map +1 -1
- package/dist/platform.js.map +1 -1
- package/docs/PROTOCOL.md +109 -29
- package/package.json +1 -1
- package/tools/daylight-test.js +8 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,32 @@ 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
|
+
## [1.0.0] — 2026-09-18
|
|
9
|
+
|
|
10
|
+
Everything the lamp will tell us over Bluetooth is now in HomeKit, and the
|
|
11
|
+
protocol behind it is written down rather than inherited. Stable enough to call
|
|
12
|
+
it that.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- **The lamp's preset modes, as switches.** Study, Relax and Precision apply a
|
|
17
|
+
set of values and spring back, the way a scene does. Turn them off per light
|
|
18
|
+
with `presetSwitches`.
|
|
19
|
+
|
|
20
|
+
### Removed
|
|
21
|
+
|
|
22
|
+
- **The motion sensor.** It reported motion permanently. The characteristic it
|
|
23
|
+
read holds a constant on this lamp — it never changed across the lamp being
|
|
24
|
+
on, off, and movement mode being switched either way — so there was nothing
|
|
25
|
+
behind it, and anything automated on it would have fired forever. The lamp's
|
|
26
|
+
movement mode, which is what most people want it for, is a switch and works.
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
|
|
30
|
+
- Every switch showed in the Home app under the accessory's own name, so a lamp
|
|
31
|
+
with three of them had three controls all called the same thing. They now
|
|
32
|
+
carry their own names.
|
|
33
|
+
|
|
8
34
|
## [0.4.0] — 2026-09-18
|
|
9
35
|
|
|
10
36
|
### Added
|
package/README.md
CHANGED
|
@@ -15,17 +15,43 @@
|
|
|
15
15
|
|
|
16
16
|
## What you get
|
|
17
17
|
|
|
18
|
-
Your lamp appears in the Home app as a **light**, with the controls Apple
|
|
18
|
+
Your lamp appears in the Home app as a **light**, with the controls Apple
|
|
19
|
+
provides for one:
|
|
19
20
|
|
|
20
21
|
- **On and off**
|
|
21
22
|
- **Brightness**
|
|
22
23
|
- **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
24
|
- **Siri**: *"Hey Siri, turn on the desk light"*, *"Hey Siri, set the desk light to 50%"*
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
Alongside it, the three modes that otherwise live only on the lamp's base or in
|
|
27
|
+
the MyDyson app — each a switch you can automate on, and each showing what the
|
|
28
|
+
lamp is really doing rather than what was last asked of it:
|
|
29
|
+
|
|
30
|
+
| switch | what it does |
|
|
31
|
+
| --- | --- |
|
|
32
|
+
| **Daylight** | shifts colour temperature through the day, warm in the evening and cool at midday |
|
|
33
|
+
| **Auto Brightness** | trims the level to suit the room as the light around it changes |
|
|
34
|
+
| **Movement** | lights when it sees movement, goes out once the room has been still |
|
|
35
|
+
|
|
36
|
+
Plus the lamp's three preset modes — **Study**, **Relax** and **Precision** — as
|
|
37
|
+
switches that apply a set of values and spring back, the way a scene does. They
|
|
38
|
+
are what the buttons in the MyDyson app do, and the lamp allows only one at a
|
|
39
|
+
time.
|
|
40
|
+
|
|
41
|
+
| preset | where it puts the lamp |
|
|
42
|
+
| --- | --- |
|
|
43
|
+
| Study | 4741 K, fairly bright |
|
|
44
|
+
| Relax | 2900 K, dim and warm |
|
|
45
|
+
| Precision | 4600 K, full output |
|
|
46
|
+
|
|
47
|
+
**It follows the lamp, not just the other way round.** Press a button on the
|
|
48
|
+
base, change something in the MyDyson app, or let the lamp end daylight tracking
|
|
49
|
+
by itself, and the Home app keeps up — usually within a second. A lamp that is
|
|
50
|
+
out of range says so, rather than showing you the last thing it knew.
|
|
51
|
+
|
|
52
|
+
**After setup nothing leaves your home.** The lamp has no Wi-Fi at all: the
|
|
53
|
+
plugin talks to it over Bluetooth, and the one cloud request — fetching the
|
|
54
|
+
lamp's key — happens once, during pairing.
|
|
29
55
|
|
|
30
56
|
## Before you start
|
|
31
57
|
|
|
@@ -34,7 +60,7 @@ happens once, during pairing.
|
|
|
34
60
|
| **Lamp** | Dyson Solarcycle Morph or Lightcycle Morph, added to your MyDyson account |
|
|
35
61
|
| **Homebridge** | 2.0.0 or newer, on Linux |
|
|
36
62
|
| **Node.js** | 22.18, 24 or 26 |
|
|
37
|
-
| **Range** | The Homebridge host must be within Bluetooth range of the lamp |
|
|
63
|
+
| **Range** | The Homebridge host must be within Bluetooth range of the lamp, and stay there — these lamps have no Wi-Fi, so there is no cloud fallback |
|
|
38
64
|
|
|
39
65
|
**The lamp must already be in the MyDyson app.** This is not a formality. The
|
|
40
66
|
lamp ignores every command until it is handed a key that only Dyson can issue,
|
|
@@ -42,8 +68,9 @@ against the account the lamp is registered to — there is no way to read it fro
|
|
|
42
68
|
the device. If the lamp is not on your account, pairing returns a 404 and there
|
|
43
69
|
is nothing the plugin can do about it.
|
|
44
70
|
|
|
45
|
-
**Linux only.**
|
|
46
|
-
not
|
|
71
|
+
**Linux only.** macOS and Windows will not work, for Bluetooth reasons that are
|
|
72
|
+
not worth going into here. A Raspberry Pi running Homebridge is the usual home
|
|
73
|
+
for it.
|
|
47
74
|
|
|
48
75
|
## Step 1 — Install the plugin
|
|
49
76
|
|
|
@@ -73,40 +100,37 @@ Open the plugin's **Settings**. The page walks through it:
|
|
|
73
100
|
|
|
74
101
|
Restart Homebridge when the page says so.
|
|
75
102
|
|
|
76
|
-
### What is stored, and where
|
|
77
|
-
|
|
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.
|
|
81
|
-
|
|
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
|
-
|
|
86
103
|
## Step 4 — Add the lamp to the Home app
|
|
87
104
|
|
|
88
105
|
The child bridge has its own QR code, under **Bridge Settings**. Scan it in the
|
|
89
106
|
Home app the same way you paired Homebridge itself.
|
|
90
107
|
|
|
91
|
-
##
|
|
108
|
+
## Using it
|
|
109
|
+
|
|
110
|
+
**Turning it on, dimming it, changing the colour** — all the usual Home app
|
|
111
|
+
controls, and Siri.
|
|
112
|
+
|
|
113
|
+
**The three switches can be automated like anything else.** A few that people
|
|
114
|
+
tend to want:
|
|
115
|
+
|
|
116
|
+
- Movement on when you leave for work, off when you get home
|
|
117
|
+
- Daylight on in the morning, so the lamp cools towards midday and warms again by evening on its own
|
|
118
|
+
- Auto Brightness off when you sit down to watch a film
|
|
92
119
|
|
|
93
|
-
**Brightness will not always land exactly where you put it.** The lamp
|
|
94
|
-
towards a
|
|
95
|
-
|
|
96
|
-
|
|
120
|
+
**Brightness will not always land exactly where you put it.** The lamp eases
|
|
121
|
+
towards a new level rather than jumping, and with Auto Brightness on it keeps
|
|
122
|
+
adjusting to suit the room. Asking for 100% and seeing 88% is the lamp doing its
|
|
123
|
+
job — turn Auto Brightness off if you would rather it stayed put.
|
|
97
124
|
|
|
98
|
-
**
|
|
99
|
-
|
|
125
|
+
**Setting a colour temperature ends daylight tracking**, and the Daylight switch
|
|
126
|
+
turns itself off when you do. That is the lamp's own behaviour, not something
|
|
127
|
+
the plugin decides. Flip the switch back on to resume.
|
|
100
128
|
|
|
101
|
-
**
|
|
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.
|
|
129
|
+
**Changes made at the lamp appear in the Home app**, usually straight away.
|
|
104
130
|
|
|
105
|
-
**
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
work around. Brightness and colour temperature are deliberately not checked,
|
|
109
|
-
because the lamp adjusts them itself and a correction would fight it.
|
|
131
|
+
**Out of range, the lamp shows as *No Response*** rather than the last thing it
|
|
132
|
+
knew, and commands fail rather than being quietly dropped. It comes back on its
|
|
133
|
+
own once the link does.
|
|
110
134
|
|
|
111
135
|
## Settings
|
|
112
136
|
|
|
@@ -118,30 +142,51 @@ Everything except the lights is optional.
|
|
|
118
142
|
| **Lights → Name** | What the lamp is called in the Home app |
|
|
119
143
|
| **Lights → BLE MAC address** | Filled in by the scan |
|
|
120
144
|
| **Lights → Serial number** | Filled in by the scan. Identifies the accessory, so changing it creates a new one |
|
|
121
|
-
| **Lights → Expose
|
|
145
|
+
| **Lights → Expose daylight tracking** | The Daylight switch. On by default |
|
|
146
|
+
| **Lights → Expose auto brightness** | The Auto Brightness switch. On by default |
|
|
147
|
+
| **Lights → Expose movement mode** | The Movement switch. On by default |
|
|
148
|
+
| **Lights → Expose the preset modes** | The Study, Relax and Precision switches. On by default |
|
|
122
149
|
| **Bluetooth adapter** | Only needed if the host has more than one, e.g. `hci1` |
|
|
123
150
|
|
|
124
151
|
## If something goes wrong
|
|
125
152
|
|
|
126
153
|
**The lamp shows as *No Response*.** There is no Bluetooth link at that moment.
|
|
127
|
-
The plugin keeps
|
|
154
|
+
The plugin keeps trying; it usually sorts itself out.
|
|
155
|
+
|
|
156
|
+
**It never connects at all, however long you leave it.** Unplug the lamp for ten
|
|
157
|
+
seconds. Its Bluetooth can get into a state where it looks perfectly healthy but
|
|
158
|
+
refuses every connection, and a power cycle is the only thing that clears it.
|
|
159
|
+
The plugin will pick the lamp up again by itself afterwards.
|
|
160
|
+
|
|
161
|
+
**It connects, drops, reconnects, over and over.** Usually either the lamp is too
|
|
162
|
+
far from the Homebridge host, or something nearby is using the same airwaves —
|
|
163
|
+
Wi-Fi especially. [Reading the signal](#reading-the-signal) below shows which.
|
|
164
|
+
|
|
165
|
+
**It says the lamp is not paired yet.** No key is stored for that serial. Open
|
|
166
|
+
the plugin's settings and authorise your account again, and check the serial
|
|
167
|
+
matches the lamp exactly — that is what the key is filed under.
|
|
168
|
+
|
|
169
|
+
**Pairing returns a 404.** The lamp is not on that Dyson account. It has to be
|
|
170
|
+
added in the MyDyson app first; there is no way round this.
|
|
171
|
+
|
|
172
|
+
**A few failed attempts at startup are normal.** Connecting to this lamp works
|
|
173
|
+
about two times in three whatever anyone does, so the plugin simply tries again.
|
|
174
|
+
Only a cycle that never succeeds is worth looking at.
|
|
128
175
|
|
|
129
|
-
|
|
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.
|
|
176
|
+
## Under the hood
|
|
135
177
|
|
|
136
|
-
|
|
137
|
-
|
|
178
|
+
Not needed to use the plugin, but useful when something looks odd.
|
|
179
|
+
|
|
180
|
+
### Reading the signal
|
|
181
|
+
|
|
182
|
+
Once a minute the log carries a line like:
|
|
138
183
|
|
|
139
184
|
```
|
|
140
185
|
Signal from F0:… : -73 dBm average, -80 to -67
|
|
141
186
|
```
|
|
142
187
|
|
|
143
|
-
The **average** answers one question
|
|
144
|
-
|
|
188
|
+
The **average** answers one question: is the lamp too far away. Around −60 dBm is
|
|
189
|
+
comfortable, −75 workable, −85 will not hold.
|
|
145
190
|
|
|
146
191
|
The **spread** answers a different one. A lamp that is not moving cannot swing by
|
|
147
192
|
20 dB on its own, so a wide spread means something else is using the band. So do
|
|
@@ -154,51 +199,44 @@ which sits directly on a Bluetooth advertising channel. If the host is on
|
|
|
154
199
|
Ethernet anyway, `sudo nmcli radio wifi off` settles it. Zigbee is worth checking
|
|
155
200
|
next: channels 15, 20 and 25 sit in the gaps between Wi-Fi 1, 6 and 11.
|
|
156
201
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
it is what the key is filed under.
|
|
202
|
+
Moving the host closer is worth more than any of it. One measured move took a
|
|
203
|
+
link from −76 dBm with two drops an hour to −50 dBm with none.
|
|
160
204
|
|
|
161
|
-
|
|
162
|
-
Pair the lamp again.
|
|
205
|
+
### Why switching off is double-checked
|
|
163
206
|
|
|
164
|
-
|
|
165
|
-
|
|
207
|
+
Commands to this lamp are unacknowledged: the plugin sends one and the lamp says
|
|
208
|
+
nothing back, so a command can be lost with nothing to show for it. Power is
|
|
209
|
+
therefore read back shortly after and resent if it did not take — a lamp still
|
|
210
|
+
lit after being switched off is the one failure nobody can work around.
|
|
166
211
|
|
|
167
|
-
|
|
168
|
-
|
|
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).
|
|
212
|
+
Brightness and colour temperature are deliberately *not* checked, because the
|
|
213
|
+
lamp moves them itself and a correction would fight it.
|
|
172
214
|
|
|
173
|
-
|
|
215
|
+
### What is stored, and where
|
|
174
216
|
|
|
175
|
-
|
|
176
|
-
|
|
217
|
+
The lamp's key and your account session go into the plugin's own storage
|
|
218
|
+
directory, not into `config.json`. The file you can open in the Homebridge UI —
|
|
219
|
+
and paste into a support thread — holds nothing sensitive.
|
|
177
220
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
looking at.
|
|
221
|
+
Your password is only needed to exchange a code for a session. **Forget
|
|
222
|
+
password** removes it afterwards; the lamp keeps working, because its key does
|
|
223
|
+
not expire.
|
|
182
224
|
|
|
183
|
-
|
|
225
|
+
### Setting up without the Homebridge UI
|
|
184
226
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
| Daylight | shifts colour temperature through the day | `daylightSwitch` |
|
|
188
|
-
| Auto Brightness | trims the level to suit the room | `autoBrightnessSwitch` |
|
|
189
|
-
| Movement | lights on movement, goes out once the room is still | `movementSwitch` |
|
|
227
|
+
`dyson-morph-pair` does the same pairing from a terminal. See
|
|
228
|
+
[CONTRIBUTING.md](CONTRIBUTING.md).
|
|
190
229
|
|
|
191
|
-
|
|
192
|
-
base and the MyDyson app as well as HomeKit. Daylight has one behaviour worth
|
|
193
|
-
knowing: setting a colour temperature ends the tracking, so its switch turns
|
|
194
|
-
itself off when you do. That is the lamp's own doing, not the plugin's.
|
|
230
|
+
### Rarer errors
|
|
195
231
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
lamp acts on it. It is off by default — `motionSensor` adds it.
|
|
232
|
+
**`PayloadB failed HMAC verification`.** The stored key is wrong or corrupted.
|
|
233
|
+
Pair the lamp again.
|
|
199
234
|
|
|
200
|
-
|
|
201
|
-
|
|
235
|
+
**`Not authorized` from D-Bus.** Only on distributions that restrict BlueZ
|
|
236
|
+
access — Debian and Raspberry Pi OS already allow it. If yours does not, create
|
|
237
|
+
`/etc/dbus-1/system.d/node-ble.conf` with a policy for the user Homebridge runs
|
|
238
|
+
as, then restart `dbus`. There is an example in
|
|
239
|
+
[the node-ble documentation](https://github.com/chrvadala/node-ble#provide-permissions).
|
|
202
240
|
|
|
203
241
|
## For developers
|
|
204
242
|
|
package/config.schema.json
CHANGED
|
@@ -45,12 +45,6 @@
|
|
|
45
45
|
"required": true,
|
|
46
46
|
"description": "Printed on the lamp base and shown in the MyDyson app."
|
|
47
47
|
},
|
|
48
|
-
"motionSensor": {
|
|
49
|
-
"title": "Expose the motion sensor",
|
|
50
|
-
"type": "boolean",
|
|
51
|
-
"default": false,
|
|
52
|
-
"description": "Adds the lamp's built-in motion detector as a HomeKit motion sensor."
|
|
53
|
-
},
|
|
54
48
|
"autoBrightnessSwitch": {
|
|
55
49
|
"title": "Expose auto brightness",
|
|
56
50
|
"type": "boolean",
|
|
@@ -61,7 +55,13 @@
|
|
|
61
55
|
"title": "Expose movement mode",
|
|
62
56
|
"type": "boolean",
|
|
63
57
|
"default": true,
|
|
64
|
-
"description": "Adds a switch for the lamp's movement mode, where it lights on movement and goes out once the room has been still.
|
|
58
|
+
"description": "Adds a switch for the lamp's movement mode, where it lights on movement and goes out once the room has been still."
|
|
59
|
+
},
|
|
60
|
+
"presetSwitches": {
|
|
61
|
+
"title": "Expose the preset modes",
|
|
62
|
+
"type": "boolean",
|
|
63
|
+
"default": true,
|
|
64
|
+
"description": "Adds the lamp's Study, Relax and Precision modes as switches. Only one can be on at a time, which the lamp itself enforces."
|
|
65
65
|
},
|
|
66
66
|
"daylightSwitch": {
|
|
67
67
|
"title": "Expose daylight tracking",
|
|
@@ -117,10 +117,10 @@
|
|
|
117
117
|
"lights[].name",
|
|
118
118
|
"lights[].mac",
|
|
119
119
|
"lights[].serial",
|
|
120
|
-
"lights[].motionSensor",
|
|
121
120
|
"lights[].daylightSwitch",
|
|
122
121
|
"lights[].autoBrightnessSwitch",
|
|
123
|
-
"lights[].movementSwitch"
|
|
122
|
+
"lights[].movementSwitch",
|
|
123
|
+
"lights[].presetSwitches"
|
|
124
124
|
]
|
|
125
125
|
}
|
|
126
126
|
]
|
package/dist/accessory.d.ts
CHANGED
|
@@ -14,10 +14,10 @@ export declare class MorphAccessory {
|
|
|
14
14
|
private readonly accessory;
|
|
15
15
|
private readonly config;
|
|
16
16
|
private readonly lightbulb;
|
|
17
|
-
private readonly motion?;
|
|
18
17
|
private readonly daylight?;
|
|
19
18
|
private readonly autoBrightness?;
|
|
20
19
|
private readonly movement?;
|
|
20
|
+
private readonly presets;
|
|
21
21
|
private readonly lamp;
|
|
22
22
|
constructor(platform: MorphPlatform, accessory: PlatformAccessory, config: ResolvedLightConfig);
|
|
23
23
|
start(): Promise<void>;
|
|
@@ -30,6 +30,22 @@ export declare class MorphAccessory {
|
|
|
30
30
|
* than saying nothing. Throwing a communication failure is how an accessory
|
|
31
31
|
* reports itself as unavailable, and the Home app shows "No Response".
|
|
32
32
|
*/
|
|
33
|
+
/**
|
|
34
|
+
* Let a momentary switch spring back.
|
|
35
|
+
*
|
|
36
|
+
* HomeKit has no stateless switch that appears as one, so this is the usual
|
|
37
|
+
* shape: report the press, then push the characteristic back to off shortly
|
|
38
|
+
* after so it does not sit there claiming to be a state.
|
|
39
|
+
*/
|
|
40
|
+
private release;
|
|
41
|
+
/**
|
|
42
|
+
* Name a service so the Home app shows it.
|
|
43
|
+
*
|
|
44
|
+
* `Name` alone is not enough: Home reads `ConfiguredName`, and without it
|
|
45
|
+
* every switch on an accessory shows under the accessory's own name — six
|
|
46
|
+
* controls all called "Desk Light".
|
|
47
|
+
*/
|
|
48
|
+
private name;
|
|
33
49
|
private live;
|
|
34
50
|
/** Push "No Response" to every characteristic at once. */
|
|
35
51
|
private markUnreachable;
|
package/dist/accessory.js
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import { describeError } from './errors.js';
|
|
2
2
|
import { DysonMorphLamp } from './dyson/lamp.js';
|
|
3
|
-
import { kelvinToMired, miredToKelvin, MAX_KELVIN, MIN_KELVIN } from './dyson/protocol.js';
|
|
3
|
+
import { kelvinToMired, miredToKelvin, MAX_KELVIN, MIN_KELVIN, PRESETS } from './dyson/protocol.js';
|
|
4
|
+
/**
|
|
5
|
+
* How long a momentary switch stays on before springing back.
|
|
6
|
+
*
|
|
7
|
+
* Long enough that the Home app has drawn it as pressed, short enough that
|
|
8
|
+
* nobody reads it as a state the lamp is in.
|
|
9
|
+
*/
|
|
10
|
+
const RELEASE_MS = 1_000;
|
|
4
11
|
/**
|
|
5
12
|
* Bridges one lamp's BLE session to its HomeKit services.
|
|
6
13
|
*
|
|
@@ -14,10 +21,10 @@ export class MorphAccessory {
|
|
|
14
21
|
accessory;
|
|
15
22
|
config;
|
|
16
23
|
lightbulb;
|
|
17
|
-
motion;
|
|
18
24
|
daylight;
|
|
19
25
|
autoBrightness;
|
|
20
26
|
movement;
|
|
27
|
+
presets = new Map();
|
|
21
28
|
lamp;
|
|
22
29
|
constructor(platform, accessory, config) {
|
|
23
30
|
this.platform = platform;
|
|
@@ -73,7 +80,7 @@ export class MorphAccessory {
|
|
|
73
80
|
this.daylight =
|
|
74
81
|
this.accessory.getServiceById(Service.Switch, 'daylight') ??
|
|
75
82
|
this.accessory.addService(Service.Switch, `${config.name} Daylight`, 'daylight');
|
|
76
|
-
this.
|
|
83
|
+
this.name(this.daylight, 'Daylight');
|
|
77
84
|
this.daylight
|
|
78
85
|
.getCharacteristic(Characteristic.On)
|
|
79
86
|
.onGet(() => this.live(() => this.lamp.getState().daylight))
|
|
@@ -86,34 +93,47 @@ export class MorphAccessory {
|
|
|
86
93
|
this.autoBrightness =
|
|
87
94
|
this.accessory.getServiceById(Service.Switch, 'auto') ??
|
|
88
95
|
this.accessory.addService(Service.Switch, `${config.name} Auto Brightness`, 'auto');
|
|
89
|
-
this.
|
|
96
|
+
this.name(this.autoBrightness, 'Auto Brightness');
|
|
90
97
|
this.autoBrightness
|
|
91
98
|
.getCharacteristic(Characteristic.On)
|
|
92
99
|
.onGet(() => this.live(() => this.lamp.getState().autoBrightness))
|
|
93
100
|
.onSet((value) => this.handleSet('auto brightness', () => this.lamp.setAutoBrightness(value)));
|
|
94
101
|
}
|
|
95
102
|
if (config.movementSwitch !== false) {
|
|
96
|
-
//
|
|
97
|
-
//
|
|
103
|
+
// Whether the lamp acts on what its sensor sees. Reading the sensor
|
|
104
|
+
// itself is not possible; see docs/PROTOCOL.md.
|
|
98
105
|
this.movement =
|
|
99
106
|
this.accessory.getServiceById(Service.Switch, 'movement') ??
|
|
100
107
|
this.accessory.addService(Service.Switch, `${config.name} Movement`, 'movement');
|
|
101
|
-
this.
|
|
108
|
+
this.name(this.movement, 'Movement');
|
|
102
109
|
this.movement
|
|
103
110
|
.getCharacteristic(Characteristic.On)
|
|
104
111
|
.onGet(() => this.live(() => this.lamp.getState().movement))
|
|
105
112
|
.onSet((value) => this.handleSet('movement mode', () => this.lamp.setMovement(value)));
|
|
106
113
|
}
|
|
107
|
-
if (config.
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
this.
|
|
116
|
-
|
|
114
|
+
if (config.presetSwitches !== false) {
|
|
115
|
+
// Momentary rather than stateful. The lamp does hold a preset and reports
|
|
116
|
+
// which, but what these are for is applying a set of values — so pressing
|
|
117
|
+
// one applies it and the switch springs back, the way a scene does.
|
|
118
|
+
for (const preset of Object.keys(PRESETS)) {
|
|
119
|
+
// "Preset" in the name, so a momentary one is not mistaken in a list
|
|
120
|
+
// for the mode switches above it, which do hold a state.
|
|
121
|
+
const label = `${preset[0].toUpperCase() + preset.slice(1)} Preset`;
|
|
122
|
+
const service = this.accessory.getServiceById(Service.Switch, preset) ??
|
|
123
|
+
this.accessory.addService(Service.Switch, `${config.name} ${label}`, preset);
|
|
124
|
+
this.name(service, label);
|
|
125
|
+
service
|
|
126
|
+
.getCharacteristic(Characteristic.On)
|
|
127
|
+
.onGet(() => false)
|
|
128
|
+
.onSet(async (value) => {
|
|
129
|
+
if (!value) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
await this.handleSet(`${label} preset`, () => this.lamp.setPreset(preset));
|
|
133
|
+
this.release(service);
|
|
134
|
+
});
|
|
135
|
+
this.presets.set(preset, service);
|
|
136
|
+
}
|
|
117
137
|
}
|
|
118
138
|
this.lamp.on('state', (state) => {
|
|
119
139
|
this.lightbulb.updateCharacteristic(Characteristic.On, state.on);
|
|
@@ -145,6 +165,31 @@ export class MorphAccessory {
|
|
|
145
165
|
* than saying nothing. Throwing a communication failure is how an accessory
|
|
146
166
|
* reports itself as unavailable, and the Home app shows "No Response".
|
|
147
167
|
*/
|
|
168
|
+
/**
|
|
169
|
+
* Let a momentary switch spring back.
|
|
170
|
+
*
|
|
171
|
+
* HomeKit has no stateless switch that appears as one, so this is the usual
|
|
172
|
+
* shape: report the press, then push the characteristic back to off shortly
|
|
173
|
+
* after so it does not sit there claiming to be a state.
|
|
174
|
+
*/
|
|
175
|
+
release(service) {
|
|
176
|
+
setTimeout(() => {
|
|
177
|
+
service.updateCharacteristic(this.platform.api.hap.Characteristic.On, false);
|
|
178
|
+
}, RELEASE_MS).unref();
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Name a service so the Home app shows it.
|
|
182
|
+
*
|
|
183
|
+
* `Name` alone is not enough: Home reads `ConfiguredName`, and without it
|
|
184
|
+
* every switch on an accessory shows under the accessory's own name — six
|
|
185
|
+
* controls all called "Desk Light".
|
|
186
|
+
*/
|
|
187
|
+
name(service, label) {
|
|
188
|
+
const { Characteristic } = this.platform.api.hap;
|
|
189
|
+
const full = `${this.config.name} ${label}`;
|
|
190
|
+
service.setCharacteristic(Characteristic.Name, full);
|
|
191
|
+
service.setCharacteristic(Characteristic.ConfiguredName, full);
|
|
192
|
+
}
|
|
148
193
|
live(read) {
|
|
149
194
|
if (!this.lamp.isConnected()) {
|
|
150
195
|
throw new this.platform.api.hap.HapStatusError(-70402 /* this.platform.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE */);
|
|
@@ -158,7 +203,6 @@ export class MorphAccessory {
|
|
|
158
203
|
for (const characteristic of [Characteristic.On, Characteristic.Brightness, Characteristic.ColorTemperature]) {
|
|
159
204
|
this.lightbulb.updateCharacteristic(characteristic, error);
|
|
160
205
|
}
|
|
161
|
-
this.motion?.updateCharacteristic(Characteristic.MotionDetected, error);
|
|
162
206
|
this.daylight?.updateCharacteristic(Characteristic.On, error);
|
|
163
207
|
this.autoBrightness?.updateCharacteristic(Characteristic.On, error);
|
|
164
208
|
this.movement?.updateCharacteristic(Characteristic.On, error);
|
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;
|
|
1
|
+
{"version":3,"file":"accessory.js","sourceRoot":"","sources":["../src/accessory.ts"],"names":[],"mappings":"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"}
|
package/dist/config.d.ts
CHANGED
|
@@ -15,8 +15,6 @@ export interface LightConfig {
|
|
|
15
15
|
ltk?: string;
|
|
16
16
|
/** Dyson account GUID the LTK was issued to. Stored alongside the key. */
|
|
17
17
|
accountId?: string;
|
|
18
|
-
/** Expose the built-in motion sensor as a separate HomeKit service. */
|
|
19
|
-
motionSensor?: boolean;
|
|
20
18
|
/**
|
|
21
19
|
* Expose daylight tracking as a switch. On by default: it is the lamp's
|
|
22
20
|
* defining feature and there is no other way to turn it back on from HomeKit
|
|
@@ -30,6 +28,10 @@ export interface LightConfig {
|
|
|
30
28
|
autoBrightnessSwitch?: boolean;
|
|
31
29
|
/** Expose movement-triggered lighting as a switch. On by default. */
|
|
32
30
|
movementSwitch?: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Expose the lamp's preset modes as switches. On by default.
|
|
33
|
+
*/
|
|
34
|
+
presetSwitches?: boolean;
|
|
33
35
|
}
|
|
34
36
|
/** A lamp whose credentials are known, from the config or the credential store. */
|
|
35
37
|
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":"AA8DA;;;;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"}
|
package/dist/dyson/lamp.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { EventEmitter } from 'node:events';
|
|
2
|
+
import type { Preset } from './protocol.js';
|
|
2
3
|
export interface LampState {
|
|
3
4
|
on: boolean;
|
|
4
5
|
/** HomeKit-style brightness, 0-100 %. */
|
|
@@ -22,6 +23,13 @@ export interface LampState {
|
|
|
22
23
|
autoBrightness: boolean;
|
|
23
24
|
/** Whether the lamp lights on movement and goes out when the room is still. */
|
|
24
25
|
movement: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Which preset the lamp is in, or `none`.
|
|
28
|
+
*
|
|
29
|
+
* The lamp keeps these mutually exclusive itself and reports both sides of a
|
|
30
|
+
* change, so this follows rather than decides.
|
|
31
|
+
*/
|
|
32
|
+
preset: Preset | 'none';
|
|
25
33
|
}
|
|
26
34
|
export interface LampOptions {
|
|
27
35
|
/** BLE MAC, e.g. `AA:BB:CC:DD:EE:FF`. */
|
|
@@ -44,7 +52,6 @@ export declare interface DysonMorphLamp {
|
|
|
44
52
|
on(event: 'connected', listener: () => void): this;
|
|
45
53
|
on(event: 'disconnected', listener: () => void): this;
|
|
46
54
|
on(event: 'state', listener: (state: LampState) => void): this;
|
|
47
|
-
on(event: 'motion', listener: (detected: boolean) => void): this;
|
|
48
55
|
}
|
|
49
56
|
export declare class DysonMorphLamp extends EventEmitter {
|
|
50
57
|
private readonly mac;
|
|
@@ -122,6 +129,13 @@ export declare class DysonMorphLamp extends EventEmitter {
|
|
|
122
129
|
* within a moment if the write did not land.
|
|
123
130
|
*/
|
|
124
131
|
private setFlag;
|
|
132
|
+
/**
|
|
133
|
+
* Put the lamp into a preset, or take it out of the one it is in.
|
|
134
|
+
*
|
|
135
|
+
* Only the chosen preset is written: the lamp turns the previous one off by
|
|
136
|
+
* itself and says so, and writing both would race that.
|
|
137
|
+
*/
|
|
138
|
+
setPreset(preset: Preset | 'none'): Promise<void>;
|
|
125
139
|
setDaylight(on: boolean): Promise<void>;
|
|
126
140
|
setColorTemperature(kelvin: number): Promise<void>;
|
|
127
141
|
private writeUint16;
|