matterbridge-dyson-robot 0.1.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.
Files changed (261) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/LICENSE +14 -0
  3. package/README.md +365 -0
  4. package/dist/async-eventemitter.d.ts +9 -0
  5. package/dist/async-eventemitter.d.ts.map +1 -0
  6. package/dist/async-eventemitter.js +35 -0
  7. package/dist/async-eventemitter.js.map +1 -0
  8. package/dist/check-configuration.d.ts +5 -0
  9. package/dist/check-configuration.d.ts.map +1 -0
  10. package/dist/check-configuration.js +35 -0
  11. package/dist/check-configuration.js.map +1 -0
  12. package/dist/check-versions.d.ts +3 -0
  13. package/dist/check-versions.d.ts.map +1 -0
  14. package/dist/check-versions.js +31 -0
  15. package/dist/check-versions.js.map +1 -0
  16. package/dist/config-types.d.ts +48 -0
  17. package/dist/config-types.d.ts.map +1 -0
  18. package/dist/config-types.js +4 -0
  19. package/dist/config-types.js.map +1 -0
  20. package/dist/decorator-changed.d.ts +16 -0
  21. package/dist/decorator-changed.d.ts.map +1 -0
  22. package/dist/decorator-changed.js +62 -0
  23. package/dist/decorator-changed.js.map +1 -0
  24. package/dist/dyson-360-msg-types.d.ts +113 -0
  25. package/dist/dyson-360-msg-types.d.ts.map +1 -0
  26. package/dist/dyson-360-msg-types.js +4 -0
  27. package/dist/dyson-360-msg-types.js.map +1 -0
  28. package/dist/dyson-360-types.d.ts +76 -0
  29. package/dist/dyson-360-types.d.ts.map +1 -0
  30. package/dist/dyson-360-types.js +72 -0
  31. package/dist/dyson-360-types.js.map +1 -0
  32. package/dist/dyson-air-msg-types.d.ts +99 -0
  33. package/dist/dyson-air-msg-types.d.ts.map +1 -0
  34. package/dist/dyson-air-msg-types.js +4 -0
  35. package/dist/dyson-air-msg-types.js.map +1 -0
  36. package/dist/dyson-air-sensor-types.d.ts +59 -0
  37. package/dist/dyson-air-sensor-types.d.ts.map +1 -0
  38. package/dist/dyson-air-sensor-types.js +4 -0
  39. package/dist/dyson-air-sensor-types.js.map +1 -0
  40. package/dist/dyson-air-state-types.d.ts +96 -0
  41. package/dist/dyson-air-state-types.d.ts.map +1 -0
  42. package/dist/dyson-air-state-types.js +4 -0
  43. package/dist/dyson-air-state-types.js.map +1 -0
  44. package/dist/dyson-air-types.d.ts +325 -0
  45. package/dist/dyson-air-types.d.ts.map +1 -0
  46. package/dist/dyson-air-types.js +382 -0
  47. package/dist/dyson-air-types.js.map +1 -0
  48. package/dist/dyson-device-360-base.d.ts +35 -0
  49. package/dist/dyson-device-360-base.d.ts.map +1 -0
  50. package/dist/dyson-device-360-base.js +233 -0
  51. package/dist/dyson-device-360-base.js.map +1 -0
  52. package/dist/dyson-device-360-commands.d.ts +7 -0
  53. package/dist/dyson-device-360-commands.d.ts.map +1 -0
  54. package/dist/dyson-device-360-commands.js +129 -0
  55. package/dist/dyson-device-360-commands.js.map +1 -0
  56. package/dist/dyson-device-360-faults-table.d.ts +20 -0
  57. package/dist/dyson-device-360-faults-table.d.ts.map +1 -0
  58. package/dist/dyson-device-360-faults-table.js +161 -0
  59. package/dist/dyson-device-360-faults-table.js.map +1 -0
  60. package/dist/dyson-device-360-faults.d.ts +10 -0
  61. package/dist/dyson-device-360-faults.d.ts.map +1 -0
  62. package/dist/dyson-device-360-faults.js +123 -0
  63. package/dist/dyson-device-360-faults.js.map +1 -0
  64. package/dist/dyson-device-360.d.ts +38 -0
  65. package/dist/dyson-device-360.d.ts.map +1 -0
  66. package/dist/dyson-device-360.js +45 -0
  67. package/dist/dyson-device-360.js.map +1 -0
  68. package/dist/dyson-device-air-base.d.ts +41 -0
  69. package/dist/dyson-device-air-base.d.ts.map +1 -0
  70. package/dist/dyson-device-air-base.js +446 -0
  71. package/dist/dyson-device-air-base.js.map +1 -0
  72. package/dist/dyson-device-air-heat.d.ts +52 -0
  73. package/dist/dyson-device-air-heat.d.ts.map +1 -0
  74. package/dist/dyson-device-air-heat.js +71 -0
  75. package/dist/dyson-device-air-heat.js.map +1 -0
  76. package/dist/dyson-device-air-quality.d.ts +7 -0
  77. package/dist/dyson-device-air-quality.d.ts.map +1 -0
  78. package/dist/dyson-device-air-quality.js +101 -0
  79. package/dist/dyson-device-air-quality.js.map +1 -0
  80. package/dist/dyson-device-air.d.ts +216 -0
  81. package/dist/dyson-device-air.d.ts.map +1 -0
  82. package/dist/dyson-device-air.js +80 -0
  83. package/dist/dyson-device-air.js.map +1 -0
  84. package/dist/dyson-device-base.d.ts +49 -0
  85. package/dist/dyson-device-base.d.ts.map +1 -0
  86. package/dist/dyson-device-base.js +46 -0
  87. package/dist/dyson-device-base.js.map +1 -0
  88. package/dist/dyson-device.d.ts +5 -0
  89. package/dist/dyson-device.d.ts.map +1 -0
  90. package/dist/dyson-device.js +43 -0
  91. package/dist/dyson-device.js.map +1 -0
  92. package/dist/dyson-mqtt-360.d.ts +16 -0
  93. package/dist/dyson-mqtt-360.d.ts.map +1 -0
  94. package/dist/dyson-mqtt-360.js +83 -0
  95. package/dist/dyson-mqtt-360.js.map +1 -0
  96. package/dist/dyson-mqtt-air.d.ts +48 -0
  97. package/dist/dyson-mqtt-air.d.ts.map +1 -0
  98. package/dist/dyson-mqtt-air.js +216 -0
  99. package/dist/dyson-mqtt-air.js.map +1 -0
  100. package/dist/dyson-mqtt-config.d.ts +6 -0
  101. package/dist/dyson-mqtt-config.d.ts.map +1 -0
  102. package/dist/dyson-mqtt-config.js +47 -0
  103. package/dist/dyson-mqtt-config.js.map +1 -0
  104. package/dist/dyson-mqtt-connect.d.ts +22 -0
  105. package/dist/dyson-mqtt-connect.d.ts.map +1 -0
  106. package/dist/dyson-mqtt-connect.js +145 -0
  107. package/dist/dyson-mqtt-connect.js.map +1 -0
  108. package/dist/dyson-mqtt-filter.d.ts +10 -0
  109. package/dist/dyson-mqtt-filter.d.ts.map +1 -0
  110. package/dist/dyson-mqtt-filter.js +25 -0
  111. package/dist/dyson-mqtt-filter.js.map +1 -0
  112. package/dist/dyson-mqtt-parse.d.ts +16 -0
  113. package/dist/dyson-mqtt-parse.d.ts.map +1 -0
  114. package/dist/dyson-mqtt-parse.js +100 -0
  115. package/dist/dyson-mqtt-parse.js.map +1 -0
  116. package/dist/dyson-mqtt-subscribe.d.ts +28 -0
  117. package/dist/dyson-mqtt-subscribe.d.ts.map +1 -0
  118. package/dist/dyson-mqtt-subscribe.js +85 -0
  119. package/dist/dyson-mqtt-subscribe.js.map +1 -0
  120. package/dist/dyson-mqtt.d.ts +51 -0
  121. package/dist/dyson-mqtt.d.ts.map +1 -0
  122. package/dist/dyson-mqtt.js +138 -0
  123. package/dist/dyson-mqtt.js.map +1 -0
  124. package/dist/dyson-types.d.ts +18 -0
  125. package/dist/dyson-types.d.ts.map +1 -0
  126. package/dist/dyson-types.js +20 -0
  127. package/dist/dyson-types.js.map +1 -0
  128. package/dist/endpoint-360-behavior.d.ts +55 -0
  129. package/dist/endpoint-360-behavior.d.ts.map +1 -0
  130. package/dist/endpoint-360-behavior.js +156 -0
  131. package/dist/endpoint-360-behavior.js.map +1 -0
  132. package/dist/endpoint-360-rvc.d.ts +14 -0
  133. package/dist/endpoint-360-rvc.d.ts.map +1 -0
  134. package/dist/endpoint-360-rvc.js +149 -0
  135. package/dist/endpoint-360-rvc.js.map +1 -0
  136. package/dist/endpoint-360.d.ts +35 -0
  137. package/dist/endpoint-360.d.ts.map +1 -0
  138. package/dist/endpoint-360.js +197 -0
  139. package/dist/endpoint-360.js.map +1 -0
  140. package/dist/endpoint-air-purifier.d.ts +24 -0
  141. package/dist/endpoint-air-purifier.d.ts.map +1 -0
  142. package/dist/endpoint-air-purifier.js +97 -0
  143. package/dist/endpoint-air-purifier.js.map +1 -0
  144. package/dist/endpoint-air-quality.d.ts +11 -0
  145. package/dist/endpoint-air-quality.d.ts.map +1 -0
  146. package/dist/endpoint-air-quality.js +104 -0
  147. package/dist/endpoint-air-quality.js.map +1 -0
  148. package/dist/endpoint-air-thermostat.d.ts +358 -0
  149. package/dist/endpoint-air-thermostat.d.ts.map +1 -0
  150. package/dist/endpoint-air-thermostat.js +67 -0
  151. package/dist/endpoint-air-thermostat.js.map +1 -0
  152. package/dist/endpoint-air.d.ts +125 -0
  153. package/dist/endpoint-air.d.ts.map +1 -0
  154. package/dist/endpoint-air.js +459 -0
  155. package/dist/endpoint-air.js.map +1 -0
  156. package/dist/endpoint-base.d.ts +36 -0
  157. package/dist/endpoint-base.d.ts.map +1 -0
  158. package/dist/endpoint-base.js +137 -0
  159. package/dist/endpoint-base.js.map +1 -0
  160. package/dist/error-360.d.ts +35 -0
  161. package/dist/error-360.d.ts.map +1 -0
  162. package/dist/error-360.js +105 -0
  163. package/dist/error-360.js.map +1 -0
  164. package/dist/index.d.ts +5 -0
  165. package/dist/index.d.ts.map +1 -0
  166. package/dist/index.js +8 -0
  167. package/dist/index.js.map +1 -0
  168. package/dist/logger-filter.d.ts +23 -0
  169. package/dist/logger-filter.d.ts.map +1 -0
  170. package/dist/logger-filter.js +104 -0
  171. package/dist/logger-filter.js.map +1 -0
  172. package/dist/logger-options.d.ts +18 -0
  173. package/dist/logger-options.d.ts.map +1 -0
  174. package/dist/logger-options.js +41 -0
  175. package/dist/logger-options.js.map +1 -0
  176. package/dist/logger-prefix.d.ts +12 -0
  177. package/dist/logger-prefix.d.ts.map +1 -0
  178. package/dist/logger-prefix.js +37 -0
  179. package/dist/logger-prefix.js.map +1 -0
  180. package/dist/periodic.d.ts +31 -0
  181. package/dist/periodic.d.ts.map +1 -0
  182. package/dist/periodic.js +102 -0
  183. package/dist/periodic.js.map +1 -0
  184. package/dist/platform.d.ts +18 -0
  185. package/dist/platform.d.ts.map +1 -0
  186. package/dist/platform.js +138 -0
  187. package/dist/platform.js.map +1 -0
  188. package/dist/settings.d.ts +9 -0
  189. package/dist/settings.d.ts.map +1 -0
  190. package/dist/settings.js +28 -0
  191. package/dist/settings.js.map +1 -0
  192. package/dist/ti/config-types-ti.d.ts +16 -0
  193. package/dist/ti/config-types-ti.d.ts.map +1 -0
  194. package/dist/ti/config-types-ti.js +65 -0
  195. package/dist/ti/config-types-ti.js.map +1 -0
  196. package/dist/ti/config-types.d.ts +37 -0
  197. package/dist/ti/config-types.d.ts.map +1 -0
  198. package/dist/ti/config-types.js +11 -0
  199. package/dist/ti/config-types.js.map +1 -0
  200. package/dist/ti/dyson-360-msg-types-ti.d.ts +22 -0
  201. package/dist/ti/dyson-360-msg-types-ti.d.ts.map +1 -0
  202. package/dist/ti/dyson-360-msg-types-ti.js +134 -0
  203. package/dist/ti/dyson-360-msg-types-ti.js.map +1 -0
  204. package/dist/ti/dyson-360-msg-types.d.ts +55 -0
  205. package/dist/ti/dyson-360-msg-types.d.ts.map +1 -0
  206. package/dist/ti/dyson-360-msg-types.js +13 -0
  207. package/dist/ti/dyson-360-msg-types.js.map +1 -0
  208. package/dist/ti/dyson-360-types-ti.d.ts +17 -0
  209. package/dist/ti/dyson-360-types-ti.d.ts.map +1 -0
  210. package/dist/ti/dyson-360-types-ti.js +94 -0
  211. package/dist/ti/dyson-360-types-ti.js.map +1 -0
  212. package/dist/ti/dyson-360-types.d.ts +40 -0
  213. package/dist/ti/dyson-360-types.d.ts.map +1 -0
  214. package/dist/ti/dyson-360-types.js +11 -0
  215. package/dist/ti/dyson-360-types.js.map +1 -0
  216. package/dist/ti/dyson-air-msg-types-ti.d.ts +19 -0
  217. package/dist/ti/dyson-air-msg-types-ti.d.ts.map +1 -0
  218. package/dist/ti/dyson-air-msg-types-ti.js +115 -0
  219. package/dist/ti/dyson-air-msg-types-ti.js.map +1 -0
  220. package/dist/ti/dyson-air-msg-types.d.ts +46 -0
  221. package/dist/ti/dyson-air-msg-types.d.ts.map +1 -0
  222. package/dist/ti/dyson-air-msg-types.js +15 -0
  223. package/dist/ti/dyson-air-msg-types.js.map +1 -0
  224. package/dist/ti/dyson-air-sensor-types-ti.d.ts +9 -0
  225. package/dist/ti/dyson-air-sensor-types-ti.d.ts.map +1 -0
  226. package/dist/ti/dyson-air-sensor-types-ti.js +68 -0
  227. package/dist/ti/dyson-air-sensor-types-ti.js.map +1 -0
  228. package/dist/ti/dyson-air-sensor-types.d.ts +16 -0
  229. package/dist/ti/dyson-air-sensor-types.d.ts.map +1 -0
  230. package/dist/ti/dyson-air-sensor-types.js +12 -0
  231. package/dist/ti/dyson-air-sensor-types.js.map +1 -0
  232. package/dist/ti/dyson-air-state-types-ti.d.ts +9 -0
  233. package/dist/ti/dyson-air-state-types-ti.d.ts.map +1 -0
  234. package/dist/ti/dyson-air-state-types-ti.js +105 -0
  235. package/dist/ti/dyson-air-state-types-ti.js.map +1 -0
  236. package/dist/ti/dyson-air-state-types.d.ts +16 -0
  237. package/dist/ti/dyson-air-state-types.d.ts.map +1 -0
  238. package/dist/ti/dyson-air-state-types.js +12 -0
  239. package/dist/ti/dyson-air-state-types.js.map +1 -0
  240. package/dist/ti/dyson-air-types-ti.d.ts +59 -0
  241. package/dist/ti/dyson-air-types-ti.d.ts.map +1 -0
  242. package/dist/ti/dyson-air-types-ti.js +385 -0
  243. package/dist/ti/dyson-air-types-ti.js.map +1 -0
  244. package/dist/ti/dyson-air-types.d.ts +166 -0
  245. package/dist/ti/dyson-air-types.d.ts.map +1 -0
  246. package/dist/ti/dyson-air-types.js +11 -0
  247. package/dist/ti/dyson-air-types.js.map +1 -0
  248. package/dist/ti/dyson-types-ti.d.ts +10 -0
  249. package/dist/ti/dyson-types-ti.d.ts.map +1 -0
  250. package/dist/ti/dyson-types-ti.js +29 -0
  251. package/dist/ti/dyson-types-ti.js.map +1 -0
  252. package/dist/ti/dyson-types.d.ts +19 -0
  253. package/dist/ti/dyson-types.d.ts.map +1 -0
  254. package/dist/ti/dyson-types.js +11 -0
  255. package/dist/ti/dyson-types.js.map +1 -0
  256. package/dist/utils.d.ts +24 -0
  257. package/dist/utils.d.ts.map +1 -0
  258. package/dist/utils.js +150 -0
  259. package/dist/utils.js.map +1 -0
  260. package/matterbridge-dyson-robot.schema.json +359 -0
  261. package/package.json +93 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,15 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ ## [Unreleased]
6
+
7
+ ## [v0.1.0] - 2025-05-16
8
+ * Initial version.
9
+
10
+ ---
11
+
12
+ Copyright © 2025 Alexander Thoukydides
13
+
14
+ [Unreleased]: https://github.com/thoukydides/matterbridge-dyson-robot/compare/v0.1.0...HEAD
15
+ [v0.1.0]: https://github.com/thoukydides/matterbridge-dyson-robot/releases/tag/v0.1.0
package/LICENSE ADDED
@@ -0,0 +1,14 @@
1
+ ISC License (ISC)
2
+ Copyright © 2025 Alexander Thoukydides
3
+
4
+ Permission to use, copy, modify, and/or distribute this software for any
5
+ purpose with or without fee is hereby granted, provided that the above
6
+ copyright notice and this permission notice appear in all copies.
7
+
8
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
9
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
10
+ FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
11
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
12
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
13
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
14
+ PERFORMANCE OF THIS SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,365 @@
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/wiki/thoukydides/matterbridge-dyson-robot/matterbridge-dyson-robot.png" height="200">
3
+ </p>
4
+ <div align=center>
5
+
6
+ # matterbridge-dyson-robot
7
+
8
+ [![npm](https://badgen.net/npm/v/matterbridge-dyson-robot)](https://www.npmjs.com/package/matterbridge-dyson-robot)
9
+ [![npm](https://badgen.net/npm/dt/matterbridge-dyson-robot)](https://www.npmjs.com/package/matterbridge-dyson-robot)
10
+ [![npm](https://badgen.net/npm/dw/matterbridge-dyson-robot)](https://www.npmjs.com/package/matterbridge-dyson-robot)
11
+ [![Build and Lint](https://github.com/thoukydides/matterbridge-dyson-robot/actions/workflows/build.yml/badge.svg)](https://github.com/thoukydides/matterbridge-dyson-robot/actions/workflows/build.yml)
12
+
13
+ A [Matterbridge](https://github.com/Luligu/matterbridge) plugin that connects [Dyson](https://www.dyson.co.uk/) robot vacuums and air treatment devices
14
+ to the [Matter](https://csa-iot.org/all-solutions/matter/) smart home ecosystem via their local MQTT APIs.
15
+
16
+ </div>
17
+
18
+ Dyson, Dyson Cool, Dyson Hot, Dyson Hot+Cool, Dyson Pure, Dyson Pure Cool, Dyson Pure Cool Link, Dyson Pure Hot+Cool Link, Dyson Pure Humidify+Cool, Dyson 360 Eye, Dyson 360 Heurist, and Dyson 360 Vis Nav, are trademarks of [Dyson Technology Limited](https://www.dyson.co.uk/).
19
+
20
+ ## Installation
21
+
22
+ ### Step 1 - Create Account and Connect Devices
23
+ 1. Use the MyDyson [iPhone](https://apps.apple.com/gb/app/mydyson/id993135524) or [Android](https://play.google.com/store/apps/details?id=com.dyson.mobile.android) app to create an account.
24
+ 1. Add the Dyson robot vacuum cleaner and/or air treatment devices to your Dyson account.
25
+
26
+ ### Step 2 - Obtain Device MQTT Credentials
27
+
28
+ #### Option 1: Using Wi-Fi Setup Label
29
+
30
+ 1. For each device find the label with the Wi-Fi setup credentials. This may be located:
31
+ 1. behind the clean bin of robot vacuums,
32
+ 1. underneath the base of air treatment devices, or
33
+ 1. attached to the operating manual.
34
+ 1. Note the **Product SSID** (`wifi_ssid`) and the **Product Wi-Fi Password** (`wifi_password`). These are case-sensitive and must be entered exactly as shown on the label.
35
+
36
+ #### Option 2: Using `opendyson` <!-- (enables either local or cloud connection) -->
37
+
38
+ 1. Install [opendyson](https://github.com/libdyson-wg/opendyson), e.g. if `Go` is installed and configured:
39
+ `go install github.com/libdyson-wg/opendyson`
40
+ 1. Login to your MyDyson account:
41
+ `opendyson login`
42
+ 1. Identify devices and retrieve their connection credentials:
43
+ `opendyson devices`
44
+ 1. For each device note the `mqtt` values: `username`, `password`, and `root_topic`
45
+
46
+ ### Step 3 - Matterbridge Plugin Installation
47
+
48
+ #### Recommended Approach using Matterbridge Frontend
49
+
50
+ 1. Open the Matterbridge web interface, e.g. at http://localhost:8283/.
51
+ 1. Under *Install plugins* type `matterbridge-dyson-robot` in the *Plugin name or plugin path* search box, then click *Install ⬇️*.
52
+ 1. Click *🔄 Restart Matterbridge* to apply the change.
53
+ 1. Open the **matterbridge-dyson-robot** *⚙️ Plugin config* and configure details of each robot vacuum or air treatment device:
54
+ 1. `name`: A friendly name (that will be used as the Matter **NodeLabel**) for the device.
55
+ 1. Select the type of MQTT credentials (from step 2) and enter the required details, either:
56
+ 1. **Wi-Fi Setup Configuration**
57
+ `host`: The hostname or IP address of the device on your local network.
58
+ `wifi_ssid` and `wifi_password`: The values from the product's Wi-Fi setup label.
59
+ 1. **Local MQTT Configuration**
60
+ `host`: The hostname or IP address of the device on your local network.
61
+ `username`, `password`, and `root_topic`: The values listed by `opendyson`.
62
+ 1. Use ➕ to add additional devices, and enter their details.
63
+ 1. Click *CONFIRM* to save the plugin configuration and restart Matterbridge again.
64
+
65
+ <details>
66
+ <summary>Alternative method using command line (and advanced configuration)</summary>
67
+
68
+ #### Installation using Command Line
69
+
70
+ 1. Stop Matterbridge:
71
+ `systemctl stop matterbridge`
72
+ 1. Install the plugin:
73
+ `npm install -g matterbridge-dyson-robot`
74
+ 1. Register it with Matterbridge:
75
+ `matterbridge -add matterbridge-dyson-robot`
76
+ 1. Restart Matterbridge:
77
+ `systemctl start matterbridge`
78
+
79
+ #### Example `matterbridge-dyson-robot.config.json`
80
+
81
+ Local network connection configured using credentials from Wi-Fi setup label:
82
+ ```JSON
83
+ {
84
+ "devices": [{
85
+ "name": "Katniss Everclean",
86
+ "host": "dyson-360eye.local",
87
+ "port": 1883,
88
+ "wifi_ssid": "360EYE-AA1-UK-BBB2222B",
89
+ "wifi_password": "abcdefgh"
90
+ }]
91
+ }
92
+ ```
93
+
94
+ Local network connection configured using MQTT credentials (obtained using `opendyson`):
95
+ ```JSON
96
+ {
97
+ "devices": [{
98
+ "name": "Katniss Everclean",
99
+ "host": "192.168.0.100",
100
+ "port": 1883,
101
+ "username": "AA1-UK-BBB2222B",
102
+ "password": "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/ABCDEFGHIJKLMNOPQRSTUV==",
103
+ "root_topic": "N223"
104
+ }]
105
+ }
106
+ ```
107
+
108
+ #### Advanced Configuration
109
+
110
+ You can include additional settings in `matterbridge-dyson-robot.config.json` to customise the behaviour or enable special debug features:
111
+ ```JSON
112
+ {
113
+ "name": "matterbridge-dyson-robot",
114
+ "type": "DynamicPlatform",
115
+ "version": "1.0.0",
116
+ "whiteList": [],
117
+ "blackList": ["360EYE-AA1-UK-BBB2222B"],
118
+ "entityWhiteList": [],
119
+ "entityBlackList": ["Composed Air Purifier", "Humidity Sensor", "Temperature Sensor"],
120
+ "deviceEntityBlackList": {
121
+ "CC3-UK-DDD4444D": ["Air Purifier"],
122
+ },
123
+ "devices": [{
124
+ "name": "Obi-Wan Cleanobi",
125
+ "host": "dyson-360eye.local",
126
+ "port": 1883,
127
+ "wifi_ssid": "360EYE-AA1-UK-BBB2222B",
128
+ "wifi_password": "abcdefgh"
129
+ }, {
130
+ "name": "Hoth Breeze",
131
+ "host": "192.168.0.100",
132
+ "port": 1883,
133
+ "username": "CC3-UK-DDD4444D",
134
+ "password": "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/ABCDEFGHIJKLMNOPQRSTUV==",
135
+ "root_topic": "475"
136
+ }],
137
+ "wildcardTopic": true,
138
+ "composedDevices": false,
139
+ "debug": false,
140
+ "debugFeatures": ["Log Endpoint Debug", "Log MQTT Client", "Log MQTT Payloads", "Log Serial Numbers", "Log Debug as Info"],
141
+ "unregisterOnShutdown": false
142
+ }
143
+ ```
144
+
145
+ | Key | Default | Description
146
+ | ----------------------- | ------- | ---
147
+ | `name`<br>`type`<br>`version` | n/a | These are managed by Matterbridge and do not need to be set manually.
148
+ | `devices[]` | `[]` | MQTT configuration for each Dyson device. See *Step 2 - Obtain Device MQTT Credentials* (above).
149
+ | `wildcardTopic` | `true` | When set to `true` the plugin subscribes to the wildcard (`#`) MQTT topic, receiving every message published or echoed by the robot vacuum and air treatment devices. This is useful for discovering new topics, seeing the commands issued by the MyDyson app to air treatment devices (but not to robot vacuums), and verifying correct `root_topic` and `username` settings.
150
+ | `blackList` | `[]` | If the list is not empty, then any robot vacuum and air treatment devices with matching serial numbers will not be exposed as Matter devices.
151
+ | `whiteList` | `[]` | If the list is not empty, then only robot vacuum and air treatment devices with matching serial numbers (and not on the `blacklist`) will be exposed as Matter devices.
152
+ | `entityBlackList` | `["Composed Air Purifier", "Humidity Sensor", "Temperature Sensor"]` | If the list is not empty, then any endpoint device types listed will be excluded. This applies to all air treatment devices. It does not affect robot vacuum devices.
153
+ | `entityWhiteList` | `[]` | If the list is not empty, then only endpoint device types on that list (and not on the `entityBlackList`) will be included. This applies to all air treatment devices. It does not affect robot vacuum devices.
154
+ | `deviceEntityBlackList` | `[]` | Per-device `entityBlackList`-style selection of endpoints. This only applies to air treatment devices. It is an object where the keys are device serial numbers, and the values are the list of endpoint device types that will be excluded for that device.
155
+ | `debug` | `false` | Sets the logger level for this plugin to *Debug*, overriding the global Matterbridge logger level setting.
156
+ | `debugFeatures` | `[]` | Miscellaneous options to control the information logged. None of these should be set unless you are investigating a compatibility issue, MQTT message error, or other problem.
157
+ | `unregisterOnShutdown` | `false` | Unregister all exposed devices on shutdown. This is used during development and testing; do not set it for normal use.
158
+
159
+ The various black/white lists control which robot vacuum and air treatment devices are exposed as Matter devices. Robot vacuums are always exposed as a simple Matter device on a single endpoint, but air treatment devices are implemented as multiple devices and endpoints that can be individually included or excluded. A device or endpoint must pass all the black/white list filters to be exposed (logical AND). This applies cumulatively across global and per-device filters. Devices are identified via their serial numbers (the same as their MQTT username) and endpoints are identified using their Matter device type: `Air Purifier`, `Air Quality Sensor`, `Composed Air Purifier` (a composed device consisting of an `Air Purifier` with all other relevant device types as children), `Humidity Sensor`, `Temperature Sensor`, or `Thermostat`.
160
+
161
+ The supported `debugFeatures` are:
162
+ - `Log Endpoint Debug`: Sets the `debug` flag to the Matterbridge/Matter.js endpoint implementation.
163
+ - `Log MQTT Client`: Enables (extremely) verbose debug logging from the low-level MQTT client. This is unlikely to be useful unless the plugin is unable to establish or maintain a connection to the Dyson device. (Requires *Debug* level logging.)
164
+ - `Log MQTT Payloads`: Enables logging of every MQTT payload that is sent or received. This is useful for diagnosing interoperability issues or identifying how to control new features. (Requires *Debug* level logging.)
165
+ - `Log Serial Numbers`: By default product serial numbers (a.k.a. MQTT usernames) and passwords are automatically redacted in the log. This setting logs serial numbers verbatim.
166
+ - `Log Debug as Info`: Redirect *Debug* level logging to *Info* level. This makes it visible in the Matterbridge frontend.
167
+
168
+ </details>
169
+
170
+ ## Functionality
171
+
172
+ The following sections describe the functionality exposed to Matter. Different ecosystems vary in their level of support; many Matter controllers will only provide access to a limited subset of this functionality.
173
+
174
+ <details>
175
+ <summary>Robot Vacuums</summary>
176
+
177
+ Each robot vacuum appears as a standalone Matter device with a single endpoint. This supports basic start/stop/pause/resume control and changing power mode. Detailed status information is provided for the robot vacuum's activity, battery, and any faults.
178
+
179
+ Zone cleaning and mapping control are not currently supported, as Dyson's MQTT API does not appear to expose these functions.
180
+
181
+ #### Robotic Vacuum Cleaner Device
182
+
183
+ - **RVC Run Mode** cluster:
184
+ - `Idle`: Abort cleaning and return to dock (same as `GoHome`).
185
+ - `Cleaning`: Start a full-clean.
186
+ - `Mapping`: Status only; no information is available about how to initiate mapping via MQTT.
187
+
188
+ - **RVC Clean Mode** cluster:
189
+ | Mode | Dyson 360 Eye | Dyson Heurist | Dyson Vis Nav |
190
+ | ---------- | :-----------: | :-----------: | :-----------: |
191
+ | `Quiet` | Quiet | Quiet | Quiet |
192
+ | `Quick` | | | Quick |
193
+ | `High` | | High | |
194
+ | `MaxBoost` | Max | Max | Boost |
195
+ | `Auto` | | | Auto |
196
+
197
+ **RVC Operational State** cluster:
198
+ - `Pause`: Pause cleaning or mapping activity.
199
+ - `Resume`: Resume from a paused state.
200
+ - `GoHome`: Abort cleaning and return to dock (same as `Idle`).
201
+ - *OperationalState* (`Stopped`, `Running`, `Paused`, `Error`, `SeekingCharger`, `Charging`, or `Docked`).
202
+ - Any active fault.
203
+
204
+ **Power Source** cluster:
205
+ - Battery charge level and charging status.
206
+ - Any active fault.
207
+
208
+ No **Service Area** cluster is implemented, due to absence of information about how to control zone cleaning via MQTT.
209
+
210
+ </details>
211
+
212
+ <details>
213
+ <summary>Air Treatment Devices</summary>
214
+
215
+ This plugin implements multiple Matter device types to support most of the functionality and sensors of air treatment devices:
216
+ - **Air Purifier**
217
+ - **Air Quality Sensor**
218
+ - **Humidity Sensor**
219
+ - **Temperature Sensor**
220
+ - **Thermostat** (*Heat+Cool* only)
221
+
222
+ You can expose each endpoint (sensor, thermostat, purifier) as a standalone Matter device, or group them into a single composed device with multiple endpoints. Some Matter controllers may display multiple instances of the same sensor due to overlap between standalone and composed devices. Use the black/white lists to control which devices are exposed.
223
+
224
+ Only one **Air Purifier** and one **Thermostat** can be exposed per physical device:
225
+ - If the standalone **Air Purifier** (`Air Purifier`) is enabled then the composed device (`Composed Air Purifier`) is disabled implicitly.
226
+ - If the standalone **Thermostat** (`Thermostat`) device is enabled, then heating controls are not included in any composed device.
227
+
228
+ Sensor devices can be duplicated, e.g. the measured temperature may be reported simultaneously in all of these:
229
+ - Standalone **Air Quality Sensor** device
230
+ - **Temperature Measurement** cluster > *MeasuredValue* attribute
231
+ - Standalone **Temperature Sensor** device
232
+ - **Temperature Measurement** cluster > *MeasuredValue* attribute
233
+ - Composed **Air Purifier** device
234
+ - Child **Air Quality Sensor** device
235
+ - **Temperature Measurement** cluster > *MeasuredValue* attribute
236
+ - Child **Temperature Sensor** device
237
+ - **Temperature Measurement** cluster > *MeasuredValue* attribute
238
+ - Child (or standalone) **Thermostat** device
239
+ - **Thermostat** cluster > *Local Temperature* attribute
240
+
241
+ #### Air Purifier Device
242
+
243
+ - **On/Off** cluster:
244
+ - Turn fan on/off (preserving speed setting)
245
+ - **Fan Control** cluster:
246
+ - Turn fan on/off (losing speed setting)
247
+ - Fan speed or auto
248
+ - Fan direction (not *(Hot+)Cool Link*)
249
+ - Night mode = `SleepWind`
250
+ - Side-to-side oscillation (not *Big+Quiet*) = `RockLeftRight`
251
+ - "Breeze" (*Humidify+Cool* only) = `NaturalWind`
252
+ - Tilt "breeze" oscillation (*Big+Quiet* only) = `RockUpDown`
253
+ - **HEPA Filter Monitoring** cluster:
254
+ - Remaining HEPA (or combined) filter life
255
+ - **Activated Carbon Filter Monitoring** cluster:
256
+ - Remaining activated carbon filter life (*Big+Quiet* only)
257
+
258
+ #### Air Quality Sensor Device
259
+
260
+ - **Air Quality** cluster:
261
+ - Synthesized qualitative air quality:
262
+ 1. Each available pollutant measurement (including the *Pure (Hot+)Cool Link* qualitative particulate measurement) is categorised as Good, Fair, Moderate, Poor, Very Poor, or Extremely Poor. This uses US EPA AQI breakpoints, WHO guidelines, other guidelines, and arbitrary mappings of qualitative measurements.
263
+ 1. The worst classification is used as the overall air quality.
264
+ - **Temperature Measurement** cluster:
265
+ - Measured temperature, if available
266
+ - **Relative Humidity Measurement** cluster:
267
+ - Measured relative humidity (%), if available
268
+ - **Total Volatile Organic Compounds Concentration Measurement** cluster:
269
+ - Measured VOC (qualitative), if available
270
+ - **Carbon Dioxide Concentration Measurement** cluster:
271
+ - Measured CO2 (ppm), if available
272
+ - **Nitrogen Dioxide Concentration Measurement** cluster:
273
+ - Measured NOx (qualitative), if available
274
+ - **Formaldehyde Concentration Measurement** cluster:
275
+ - Measured Formaldehyde level (µg/m³), if available
276
+ - **PM2.5 Concentration Measurement** cluster:
277
+ - Measured small particulates (µg/m³), if available
278
+ - **PM10 Concentration Measurement** cluster:
279
+ - Measured large particulates (µg/m³), if available
280
+
281
+ #### Humidity Sensor Device
282
+
283
+ - **Relative Humidity Measurement** cluster:
284
+ - Measured relative humidity, if available
285
+
286
+ #### Temperature Sensor Device
287
+
288
+ - **Temperature Measurement** cluster:
289
+ - Measured temperature, if available
290
+
291
+ #### Thermostat Device (*Heat+Cool* only)
292
+
293
+ - **Thermostat** cluster:
294
+ - Enable/disable heating
295
+ - Target temperature
296
+ - Measured temperature, if available
297
+
298
+ </details>
299
+
300
+ ## Compatibility
301
+
302
+ This plugin has only been tested with the following devices:
303
+ | Description | Model | MQTT Root Topic | Firmware |
304
+ | -------------------------- | :---: | :-------------: | :---------: |
305
+ | Dyson 360 Eye robot vacuum | RB01 | `N223` | `11.3.5.10` |
306
+ | Dyson Pure Cool Link | TP02 | `475` | `21.04.03` |
307
+ | Dyson Pure Hot+Cool Link | HP02 | `455` | `21.04.03` |
308
+
309
+ It may also work with other Dyson robot vacuums and air treatment devices, although some modifications may be required for full compatibility.
310
+
311
+ Dyson Vis Nav firmware update `RB03PR.01.08.006.5079` (released 11th April 2024) disabled local MQTT capability, so cannot be supported by this plugin. (That probably comes under "Enhanced Wi-Fi connectivity" in the firmware release note...)
312
+
313
+ Matter controllers vary in their support for different device types. This plugin is only tested with Apple HomeKit and the Apple Home app.
314
+
315
+ ### Matter Limitations
316
+
317
+ The Matter 1.4 specification does not define any device types or clusters for controlling humidification, so this plugin does not support that aspect of Pure Humidify+Cool devices.
318
+
319
+ ### Apple Home Limitations
320
+
321
+ The Apple Home app in iOS/iPadOS 18.4 has limited Matter support, with multiple idiosyncrasies.
322
+
323
+ #### Robot Vacuums
324
+
325
+ The Apple Home app expects each robot vacuum to be a standalone, individually-paired Matter node implementing a single endpoint. However, Matterbridge acts as a Matter bridge - either a single bridge node for all plugins (*bridge* mode), or a separate bridge node per plugin (*childbridge* mode) - with each plugin's device exposed as an additional child endpoint. This causes a few issues when using this plugin with the Home app:
326
+ * **One robot vacuum per Matterbridge instance:** A separate Matterbridge instance is required for each robot vacuum. Each must use unique port numbers (both `-port <port>` and `-frontend <port>`) and their own home directory (`-homedir <path>`). This plugin should be the only one enabled in each instance, and only a single robot vacuum device should be configured in each instance.
327
+ * **Device-specific information is ignored:** The Home app shows the bridge device information from Matterbridge’s own root **Device Basic Information** cluster, ignoring the plugin’s **Bridged Device Basic Information** cluster. As a result, the Home app displays the bridge’s name, manufacturer, model, serial number, and firmware version; *not* those of the robot vacuum.
328
+
329
+ Other quirks in the Home app:
330
+ * **Incorrect clean mode display:** The Home app displays ModeTag values (e.g. *Deep Clean*, *Low Noise*) rather than the advertised modes (*Quiet*, *Max*, etc) reported by the vacuum. It also only shows these when not cleaning, even though Dyson robot vacuums support changing the power mode during a clean.
331
+
332
+ #### Air Treatment Devices
333
+
334
+ The Apple Home app only supports simple Matter devices correctly. When multiple devices are composed into a single bridged device, or subset device types are included, the Home app exhibits multiple issues:
335
+ * The device icon can be for any of the composed or subset device types, instead of selecting the most relevant (the first recognised device type on the parent endpoint), e.g. an **Air Purifier** device may be randomly shown as a **Fan Device** or **Air Quality Sensor** instead.
336
+ * Controls may be duplicated in the user interface if they can correspond to multiple overlapping device types, e.g. two fan speed sliders are shown if a device describes itself as both an Air Purifier and a Fan Device.
337
+ * Functionality is often reduced, e.g. including an **Air Quality** device in an **Air Purifier** device hides *Auto* mode, fan oscillation controls, and all sensor measurements.
338
+
339
+ For these reasons, this plugin defaults to bridging each Matter device type separately. A composed device can be selected instead by setting `entityWhiteList` to `["Composed Air Purifier"]` and `entityBlackList` to `[]`.
340
+
341
+ Other quirks in the Home app:
342
+ * HEPA and carbon filter status is not shown (despite being part of the Matter Air Purifier device specification, and them being supported via the HomeKit Accessory Protocol).
343
+ * Other sensor measurements (CO2, Formaldehyde, NOx, PM2.5, PM10, and VOC) are not shown.
344
+
345
+ ## Changelog
346
+
347
+ All notable changes to this project are documented in the [CHANGELOG.md](CHANGELOG.md) file.
348
+
349
+ ## Reporting Issues
350
+
351
+ If you have discovered an issue or have an idea for how to improve this project, please [open a new issue](https://github.com/thoukydides/matterbridge-dyson-robot/issues/new/choose) using the appropriate issue template.
352
+
353
+ ### Pull Requests
354
+
355
+ This project does **NOT** accept pull requests. Any PRs submitted will be closed without discussion. For more details refer to the [`CONTRIBUTING.md`](https://github.com/thoukydides/.github/blob/master/CONTRIBUTING.md) file.
356
+
357
+ ## ISC License (ISC)
358
+
359
+ <details>
360
+ <summary>Copyright © 2025 Alexander Thoukydides</summary>
361
+
362
+ > Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
363
+ >
364
+ > THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
365
+ </details>
@@ -0,0 +1,9 @@
1
+ import { EventEmitter } from 'events';
2
+ type EventMap<T> = Record<keyof T, unknown[]>;
3
+ type Event<T extends EventMap<T>> = ReturnType<EventEmitter<T>['eventNames']>[number];
4
+ type EventArgs<T extends EventMap<T>, K> = K extends keyof T ? T[K] : never;
5
+ export declare class AsyncEventEmitter<T extends EventMap<T>> extends EventEmitter<T> {
6
+ onceAsync<K extends Event<T>>(eventName: K, signal?: AbortSignal): Promise<EventArgs<T, K>>;
7
+ }
8
+ export {};
9
+ //# sourceMappingURL=async-eventemitter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"async-eventemitter.d.ts","sourceRoot":"","sources":["../src/async-eventemitter.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAUtC,KAAK,QAAQ,CAAC,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;AAC9C,KAAK,KAAK,CAAC,CAAC,SAAS,QAAQ,CAAC,CAAC,CAAC,IAAI,UAAU,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;AACtF,KAAK,SAAS,CAAC,CAAC,SAAS,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,SAAS,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC;AAK5E,qBAAa,iBAAiB,CAAC,CAAC,SAAS,QAAQ,CAAC,CAAC,CAAC,CAAE,SAAQ,YAAY,CAAC,CAAC,CAAC;IAGnE,SAAS,CAAC,CAAC,SAAS,KAAK,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;CAqBpG"}
@@ -0,0 +1,35 @@
1
+ // Matterbridge plugin for Dyson robot vacuum and air treatment devices
2
+ // Copyright © 2025 Alexander Thoukydides
3
+ import { EventEmitter } from 'events';
4
+ // Node doesn't use DOMException for AbortError...
5
+ class AbortError extends Error {
6
+ constructor(cause) {
7
+ super('The operation was aborted', { cause });
8
+ this.name = 'AbortError';
9
+ }
10
+ }
11
+ // An EventEmitter with well-behaved Promisified listeners
12
+ export class AsyncEventEmitter extends EventEmitter {
13
+ // Typed event.once() that ignores unrelated errors
14
+ async onceAsync(eventName, signal) {
15
+ // Handle already-aborted signal immediately
16
+ if (signal?.aborted)
17
+ throw new AbortError(signal.reason);
18
+ // Otherwise return a promise that...
19
+ return new Promise((resolve, reject) => {
20
+ // ... resolves when the event occurs
21
+ const resolver = ((...args) => {
22
+ signal?.removeEventListener('abort', abortListener);
23
+ resolve(args);
24
+ });
25
+ this.once(eventName, resolver);
26
+ // ... or rejects if the signal is aborted
27
+ const abortListener = () => {
28
+ this.off(eventName, resolver);
29
+ reject(new AbortError(signal?.reason));
30
+ };
31
+ signal?.addEventListener('abort', abortListener, { once: true });
32
+ });
33
+ }
34
+ }
35
+ //# sourceMappingURL=async-eventemitter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"async-eventemitter.js","sourceRoot":"","sources":["../src/async-eventemitter.ts"],"names":[],"mappings":"AAAA,uEAAuE;AACvE,yCAAyC;AAEzC,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAEtC,kDAAkD;AAClD,MAAM,UAAW,SAAQ,KAAK;IAC1B,YAAY,KAAc;QACtB,KAAK,CAAC,2BAA2B,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;QAC9C,IAAI,CAAC,IAAI,GAAG,YAAY,CAAC;IAC7B,CAAC;CACJ;AAQD,0DAA0D;AAC1D,MAAM,OAAO,iBAAyC,SAAQ,YAAe;IAEzE,mDAAmD;IACnD,KAAK,CAAC,SAAS,CAAqB,SAAY,EAAE,MAAoB;QAClE,4CAA4C;QAC5C,IAAI,MAAM,EAAE,OAAO;YAAE,MAAM,IAAI,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAEzD,qCAAqC;QACrC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACnC,qCAAqC;YACrC,MAAM,QAAQ,GAAG,CAAC,CAAC,GAAG,IAAqB,EAAQ,EAAE;gBACjD,MAAM,EAAE,mBAAmB,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC;gBACpD,OAAO,CAAC,IAAI,CAAC,CAAC;YAClB,CAAC,CAAqB,CAAC;YACvB,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;YAE/B,0CAA0C;YAC1C,MAAM,aAAa,GAAG,GAAS,EAAE;gBAC7B,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;gBAC9B,MAAM,CAAC,IAAI,UAAU,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;YAC3C,CAAC,CAAC;YACF,MAAM,EAAE,gBAAgB,CAAC,OAAO,EAAE,aAAa,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QACrE,CAAC,CAAC,CAAC;IACP,CAAC;CACJ"}
@@ -0,0 +1,5 @@
1
+ import { PlatformConfig } from 'matterbridge';
2
+ import { AnsiLogger } from 'matterbridge/logger';
3
+ import { Config } from './config-types.js';
4
+ export declare function checkConfiguration(log: AnsiLogger, config: PlatformConfig): asserts config is Config & PlatformConfig;
5
+ //# sourceMappingURL=check-configuration.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check-configuration.d.ts","sourceRoot":"","sources":["../src/check-configuration.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAY,MAAM,qBAAqB,CAAC;AAK3D,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAK3C,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,UAAU,EAAE,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,IAAI,MAAM,GAAG,cAAc,CAmBrH"}
@@ -0,0 +1,35 @@
1
+ // Matterbridge plugin for Dyson robot vacuum and air treatment devices
2
+ // Copyright © 2025 Alexander Thoukydides
3
+ import { checkers } from './ti/config-types.js';
4
+ import { deepMerge, getValidationTree } from './utils.js';
5
+ import { DEFAULT_CONFIG, PLUGIN_NAME } from './settings.js';
6
+ import { inspect } from 'util';
7
+ import { INSPECT_VERBOSE } from './logger-options.js';
8
+ // Check that the configuration is valid
9
+ export function checkConfiguration(log, config) {
10
+ // Apply default values
11
+ Object.assign(config, deepMerge(DEFAULT_CONFIG, config));
12
+ // Ensure that all required fields are provided and are of suitable types
13
+ const checker = checkers.Config;
14
+ checker.setReportedPath('<PLATFORM_CONFIG>');
15
+ const strictValidation = checker.strictValidate(config);
16
+ if (!checker.test(config)) {
17
+ log.error('Plugin configuration errors:');
18
+ logCheckerValidation(log, config, "error" /* LogLevel.ERROR */, strictValidation);
19
+ throw new Error('Invalid plugin configuration');
20
+ }
21
+ // Warn of extraneous fields in the configuration
22
+ if (strictValidation) {
23
+ log.warn('Unsupported fields in plugin configuration will be ignored:');
24
+ logCheckerValidation(log, config, "warn" /* LogLevel.WARN */, strictValidation);
25
+ }
26
+ }
27
+ // Log configuration checker validation errors
28
+ function logCheckerValidation(log, config, level, errors) {
29
+ const errorLines = errors ? getValidationTree(errors) : [];
30
+ errorLines.forEach(line => { log.log(level, line); });
31
+ log.info(`${PLUGIN_NAME}.config.json:`);
32
+ const configLines = inspect(config, INSPECT_VERBOSE).split('\n');
33
+ configLines.forEach(line => { log.info(` ${line}`); });
34
+ }
35
+ //# sourceMappingURL=check-configuration.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check-configuration.js","sourceRoot":"","sources":["../src/check-configuration.ts"],"names":[],"mappings":"AAAA,uEAAuE;AACvE,yCAAyC;AAIzC,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAEhD,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE5D,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAC/B,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAEtD,wCAAwC;AACxC,MAAM,UAAU,kBAAkB,CAAC,GAAe,EAAE,MAAsB;IACtE,uBAAuB;IACvB,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC,CAAC;IAEzD,yEAAyE;IACzE,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC;IAChC,OAAO,CAAC,eAAe,CAAC,mBAAmB,CAAC,CAAC;IAC7C,MAAM,gBAAgB,GAAG,OAAO,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC;IACxD,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;QACxB,GAAG,CAAC,KAAK,CAAC,8BAA8B,CAAC,CAAC;QAC1C,oBAAoB,CAAC,GAAG,EAAE,MAAM,gCAAkB,gBAAgB,CAAC,CAAC;QACpE,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC;IACpD,CAAC;IAED,iDAAiD;IACjD,IAAI,gBAAgB,EAAE,CAAC;QACnB,GAAG,CAAC,IAAI,CAAC,6DAA6D,CAAC,CAAC;QACxE,oBAAoB,CAAC,GAAG,EAAE,MAAM,8BAAiB,gBAAgB,CAAC,CAAC;IACvE,CAAC;AACL,CAAC;AAED,8CAA8C;AAC9C,SAAS,oBAAoB,CAAC,GAAe,EAAE,MAAsB,EAAE,KAAe,EAAE,MAA6B;IACjH,MAAM,UAAU,GAAG,MAAM,CAAC,CAAC,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAC3D,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,GAAG,GAAG,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACtD,GAAG,CAAC,IAAI,CAAC,GAAG,WAAW,eAAe,CAAC,CAAC;IACxC,MAAM,WAAW,GAAG,OAAO,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACjE,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,GAAG,GAAG,CAAC,IAAI,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC9D,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { MatterbridgeDynamicPlatform } from 'matterbridge';
2
+ export declare function checkDependencyVersions(platform: MatterbridgeDynamicPlatform): void;
3
+ //# sourceMappingURL=check-versions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check-versions.d.ts","sourceRoot":"","sources":["../src/check-versions.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,2BAA2B,EAAE,MAAM,cAAc,CAAC;AAK3D,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,2BAA2B,GAAG,IAAI,CAsBnF"}
@@ -0,0 +1,31 @@
1
+ // Matterbridge plugin for Dyson robot vacuum and air treatment devices
2
+ // Copyright © 2025 Alexander Thoukydides
3
+ import { ENGINES, PLUGIN_NAME, PLUGIN_VERSION } from './settings.js';
4
+ import semver from 'semver';
5
+ // Log critical package and API versions
6
+ export function checkDependencyVersions(platform) {
7
+ const { log } = platform;
8
+ const versions = [
9
+ // Name Current version Required version
10
+ [PLUGIN_NAME, PLUGIN_VERSION, undefined],
11
+ ['Node.js', process.versions.node, ENGINES.node],
12
+ ['Matterbridge', platform.matterbridge.matterbridgeVersion, ENGINES.matterbridge]
13
+ ];
14
+ // Log/check each version against the requirements
15
+ versions.forEach(([name, current, required]) => {
16
+ const semverCurrent = semver.coerce(current);
17
+ if (!required) {
18
+ log.info(`${name} version ${current}`);
19
+ }
20
+ else if (semverCurrent === null) {
21
+ log.warn(`${name} version ${current} cannot be coerced to semver (require ${required})`);
22
+ }
23
+ else if (semver.satisfies(semverCurrent, required)) {
24
+ log.info(`${name} version ${current} (satisfies ${required})`);
25
+ }
26
+ else {
27
+ log.error(`${name} version ${current} is incompatible (require ${required})`);
28
+ }
29
+ });
30
+ }
31
+ //# sourceMappingURL=check-versions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check-versions.js","sourceRoot":"","sources":["../src/check-versions.ts"],"names":[],"mappings":"AAAA,uEAAuE;AACvE,yCAAyC;AAGzC,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACrE,OAAO,MAAM,MAAM,QAAQ,CAAC;AAE5B,wCAAwC;AACxC,MAAM,UAAU,uBAAuB,CAAC,QAAqC;IACzE,MAAM,EAAE,GAAG,EAAE,GAAG,QAAQ,CAAC;IACzB,MAAM,QAAQ,GAAoD;QAC9D,gFAAgF;QAChF,CAAC,WAAW,EAAQ,cAAc,EAA8B,SAAS,CAAe;QACxF,CAAC,SAAS,EAAU,OAAO,CAAC,QAAQ,CAAC,IAAI,EAAuB,OAAO,CAAC,IAAI,CAAY;QACxF,CAAC,cAAc,EAAK,QAAQ,CAAC,YAAY,CAAC,mBAAmB,EAAG,OAAO,CAAC,YAAY,CAAI;KAC3F,CAAC;IAEF,kDAAkD;IAClD,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,EAAE;QAC3C,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAC7C,IAAI,CAAC,QAAQ,EAAE,CAAC;YACZ,GAAG,CAAC,IAAI,CAAC,GAAG,IAAI,YAAY,OAAO,EAAE,CAAC,CAAC;QAC3C,CAAC;aAAM,IAAI,aAAa,KAAK,IAAI,EAAE,CAAC;YAChC,GAAG,CAAC,IAAI,CAAC,GAAG,IAAI,YAAY,OAAO,yCAAyC,QAAQ,GAAG,CAAC,CAAC;QAC7F,CAAC;aAAM,IAAI,MAAM,CAAC,SAAS,CAAC,aAAa,EAAE,QAAQ,CAAC,EAAE,CAAC;YACnD,GAAG,CAAC,IAAI,CAAC,GAAG,IAAI,YAAY,OAAO,eAAe,QAAQ,GAAG,CAAC,CAAC;QACnE,CAAC;aAAM,CAAC;YACJ,GAAG,CAAC,KAAK,CAAC,GAAG,IAAI,YAAY,OAAO,6BAA6B,QAAQ,GAAG,CAAC,CAAC;QAClF,CAAC;IACL,CAAC,CAAC,CAAC;AACP,CAAC"}
@@ -0,0 +1,48 @@
1
+ export interface DeviceConfigBase {
2
+ name: string;
3
+ }
4
+ export interface DeviceConfigWiFi extends DeviceConfigBase {
5
+ host: string;
6
+ port: number;
7
+ wifi_ssid: string;
8
+ wifi_password: string;
9
+ }
10
+ export interface DeviceConfigMqtt extends DeviceConfigBase {
11
+ host: string;
12
+ port: number;
13
+ username: string;
14
+ password: string;
15
+ root_topic: string;
16
+ }
17
+ export interface DeviceConfigIoT extends DeviceConfigBase {
18
+ endpoint: string;
19
+ client_id: string;
20
+ custom_authorizer_name: string;
21
+ token_key: string;
22
+ token_signature: string;
23
+ token_value: string;
24
+ username: string;
25
+ root_topic: string;
26
+ }
27
+ export type DeviceConfig = DeviceConfigMqtt | DeviceConfigIoT;
28
+ export type DeviceConfigAny = DeviceConfig | DeviceConfigWiFi;
29
+ export type EntityName = 'Air Purifier' | 'Air Quality Sensor' | 'Composed Air Purifier' | 'Humidity Sensor' | 'Temperature Sensor' | 'Thermostat';
30
+ export type DebugFeatures = 'Log Endpoint Debug' | 'Log MQTT Client' | 'Log MQTT Payloads' | 'Log Serial Numbers' | 'Log Debug as Info';
31
+ export interface Config {
32
+ name: string;
33
+ type: string;
34
+ version: string;
35
+ whiteList: string[];
36
+ blackList: string[];
37
+ entityWhiteList: EntityName[];
38
+ entityBlackList: EntityName[];
39
+ deviceEntityBlackList: {
40
+ [serialNumber: string]: EntityName[];
41
+ };
42
+ devices: DeviceConfigAny[];
43
+ wildcardTopic: boolean;
44
+ debug: boolean;
45
+ debugFeatures: DebugFeatures[];
46
+ unregisterOnShutdown: boolean;
47
+ }
48
+ //# sourceMappingURL=config-types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-types.d.ts","sourceRoot":"","sources":["../src/config-types.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,gBAAgB;IAC7B,IAAI,EAAoB,MAAM,CAAC;CAClC;AACD,MAAM,WAAW,gBAAiB,SAAQ,gBAAgB;IACtD,IAAI,EAAoB,MAAM,CAAC;IAC/B,IAAI,EAAoB,MAAM,CAAC;IAC/B,SAAS,EAAe,MAAM,CAAC;IAC/B,aAAa,EAAW,MAAM,CAAC;CAClC;AACD,MAAM,WAAW,gBAAiB,SAAQ,gBAAgB;IACtD,IAAI,EAAoB,MAAM,CAAC;IAC/B,IAAI,EAAoB,MAAM,CAAC;IAC/B,QAAQ,EAAgB,MAAM,CAAC;IAC/B,QAAQ,EAAgB,MAAM,CAAC;IAC/B,UAAU,EAAc,MAAM,CAAC;CAClC;AACD,MAAM,WAAW,eAAgB,SAAQ,gBAAgB;IACrD,QAAQ,EAAgB,MAAM,CAAC;IAC/B,SAAS,EAAe,MAAM,CAAC;IAC/B,sBAAsB,EAAE,MAAM,CAAC;IAC/B,SAAS,EAAe,MAAM,CAAC;IAC/B,eAAe,EAAS,MAAM,CAAC;IAC/B,WAAW,EAAa,MAAM,CAAC;IAC/B,QAAQ,EAAgB,MAAM,CAAC;IAC/B,UAAU,EAAc,MAAM,CAAC;CAClC;AACD,MAAM,MAAM,YAAY,GAAM,gBAAgB,GAAG,eAAe,CAAC;AACjE,MAAM,MAAM,eAAe,GAAG,YAAY,GAAG,gBAAgB,CAAC;AAG9D,MAAM,MAAM,UAAU,GAClB,cAAc,GACd,oBAAoB,GACpB,uBAAuB,GACvB,iBAAiB,GACjB,oBAAoB,GACpB,YAAY,CAAC;AAGjB,MAAM,MAAM,aAAa,GACrB,oBAAoB,GACpB,iBAAiB,GACjB,mBAAmB,GACnB,oBAAoB,GACpB,mBAAmB,CAAC;AAGxB,MAAM,WAAW,MAAM;IAEnB,IAAI,EAAoB,MAAM,CAAC;IAC/B,IAAI,EAAoB,MAAM,CAAC;IAC/B,OAAO,EAAiB,MAAM,CAAC;IAC/B,SAAS,EAAe,MAAM,EAAE,CAAC;IACjC,SAAS,EAAe,MAAM,EAAE,CAAC;IACjC,eAAe,EAAS,UAAU,EAAE,CAAC;IACrC,eAAe,EAAS,UAAU,EAAE,CAAC;IACrC,qBAAqB,EAAG;QAAE,CAAC,YAAY,EAAE,MAAM,GAAG,UAAU,EAAE,CAAA;KAAE,CAAC;IAEjE,OAAO,EAAiB,eAAe,EAAE,CAAC;IAC1C,aAAa,EAAW,OAAO,CAAC;IAChC,KAAK,EAAmB,OAAO,CAAC;IAChC,aAAa,EAAW,aAAa,EAAE,CAAC;IACxC,oBAAoB,EAAI,OAAO,CAAC;CACnC"}
@@ -0,0 +1,4 @@
1
+ // Matterbridge plugin for Dyson robot vacuum and air treatment devices
2
+ // Copyright © 2025 Alexander Thoukydides
3
+ export {};
4
+ //# sourceMappingURL=config-types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-types.js","sourceRoot":"","sources":["../src/config-types.ts"],"names":[],"mappings":"AAAA,uEAAuE;AACvE,yCAAyC"}
@@ -0,0 +1,16 @@
1
+ import { AnsiLogger } from 'matterbridge/logger';
2
+ import { MaybePromise } from 'matterbridge/matter';
3
+ type ChangedKey = string | symbol;
4
+ export declare class Changed {
5
+ readonly log: AnsiLogger;
6
+ readonly prevValues: Map<ChangedKey, unknown>;
7
+ constructor(log: AnsiLogger);
8
+ isChanged(key: ChangedKey, newValue: unknown): boolean;
9
+ setLast(key: ChangedKey, value: unknown): void;
10
+ flush(key?: ChangedKey): void;
11
+ }
12
+ export declare function ifValueChanged<T extends {
13
+ changed: Changed;
14
+ }, V, R extends MaybePromise>(originalMethod: (this: T, value: V) => R, context: ClassMethodDecoratorContext): (this: T, value: V) => R;
15
+ export {};
16
+ //# sourceMappingURL=decorator-changed.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decorator-changed.d.ts","sourceRoot":"","sources":["../src/decorator-changed.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAKnD,KAAK,UAAU,GAAG,MAAM,GAAG,MAAM,CAAC;AAClC,qBAAa,OAAO;IAMJ,QAAQ,CAAC,GAAG,EAAE,UAAU;IAHpC,QAAQ,CAAC,UAAU,2BAAkC;gBAGhC,GAAG,EAAE,UAAU;IAGpC,SAAS,CAAC,GAAG,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,GAAG,OAAO;IAWtD,OAAO,CAAC,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI;IAK9C,KAAK,CAAC,GAAG,CAAC,EAAE,UAAU,GAAG,IAAI;CAIhC;AAGD,wBAAgB,cAAc,CAAC,CAAC,SAAS;IAAE,OAAO,EAAE,OAAO,CAAA;CAAE,EAAE,CAAC,EAAE,CAAC,SAAS,YAAY,EACpF,cAAc,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,KAAK,CAAC,EACxC,OAAO,EAAS,2BAA2B,GAC5C,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,KAAK,CAAC,CAS1B"}