@smarthome.exchange/integrations 2.0.0 → 3.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.
Files changed (245) hide show
  1. package/changelog.md +35 -0
  2. package/dist_ts/00_commitinfo_data.js +1 -1
  3. package/dist_ts/core/classes.nativeintegrationowner.d.ts +2 -0
  4. package/dist_ts/core/classes.nativeintegrationowner.js +5 -1
  5. package/dist_ts/core/classes.runtimemanager.d.ts +2 -0
  6. package/dist_ts/core/classes.runtimemanager.js +11 -3
  7. package/dist_ts/core/types.d.ts +2 -0
  8. package/dist_ts/integrations/deconz/deconz.classes.client.d.ts +37 -29
  9. package/dist_ts/integrations/deconz/deconz.classes.client.js +521 -294
  10. package/dist_ts/integrations/deconz/deconz.classes.configflow.d.ts +6 -1
  11. package/dist_ts/integrations/deconz/deconz.classes.configflow.js +63 -33
  12. package/dist_ts/integrations/deconz/deconz.classes.integration.d.ts +4 -3
  13. package/dist_ts/integrations/deconz/deconz.classes.integration.js +85 -40
  14. package/dist_ts/integrations/deconz/deconz.mapper.d.ts +1 -0
  15. package/dist_ts/integrations/deconz/deconz.mapper.js +24 -6
  16. package/dist_ts/integrations/deconz/deconz.types.d.ts +43 -6
  17. package/dist_ts/integrations/deconz/deconz.validation.d.ts +7 -1
  18. package/dist_ts/integrations/deconz/deconz.validation.js +215 -4
  19. package/dist_ts/integrations/esphome/esphome.classes.client.d.ts +19 -7
  20. package/dist_ts/integrations/esphome/esphome.classes.client.js +183 -89
  21. package/dist_ts/integrations/esphome/esphome.classes.configflow.d.ts +5 -2
  22. package/dist_ts/integrations/esphome/esphome.classes.configflow.js +43 -33
  23. package/dist_ts/integrations/esphome/esphome.classes.integration.d.ts +8 -6
  24. package/dist_ts/integrations/esphome/esphome.classes.integration.js +71 -43
  25. package/dist_ts/integrations/esphome/esphome.discovery.js +4 -12
  26. package/dist_ts/integrations/esphome/esphome.mapper.d.ts +2 -2
  27. package/dist_ts/integrations/esphome/esphome.mapper.js +6 -4
  28. package/dist_ts/integrations/esphome/esphome.types.d.ts +31 -6
  29. package/dist_ts/integrations/esphome/esphome.types.js +1 -1
  30. package/dist_ts/integrations/esphome/esphome.validation.d.ts +7 -0
  31. package/dist_ts/integrations/esphome/esphome.validation.js +66 -0
  32. package/dist_ts/integrations/homeassistant/homeassistant.classes.client.d.ts +32 -4
  33. package/dist_ts/integrations/homeassistant/homeassistant.classes.client.js +345 -116
  34. package/dist_ts/integrations/homeassistant/homeassistant.classes.integration.d.ts +8 -5
  35. package/dist_ts/integrations/homeassistant/homeassistant.classes.integration.js +34 -8
  36. package/dist_ts/integrations/homeassistant/homeassistant.classes.runtime.d.ts +7 -4
  37. package/dist_ts/integrations/homeassistant/homeassistant.classes.runtime.js +44 -56
  38. package/dist_ts/integrations/homeassistant/homeassistant.types.d.ts +12 -1
  39. package/dist_ts/integrations/hue/hue.classes.client.d.ts +6 -1
  40. package/dist_ts/integrations/hue/hue.classes.client.js +120 -45
  41. package/dist_ts/integrations/hue/hue.classes.configflow.js +16 -7
  42. package/dist_ts/integrations/hue/hue.classes.integration.d.ts +2 -0
  43. package/dist_ts/integrations/hue/hue.classes.integration.js +103 -30
  44. package/dist_ts/integrations/hue/hue.validation.d.ts +1 -0
  45. package/dist_ts/integrations/hue/hue.validation.js +14 -4
  46. package/dist_ts/integrations/mqtt/mqtt.classes.client.d.ts +15 -2
  47. package/dist_ts/integrations/mqtt/mqtt.classes.client.js +173 -23
  48. package/dist_ts/integrations/mqtt/mqtt.classes.configflow.d.ts +5 -1
  49. package/dist_ts/integrations/mqtt/mqtt.classes.configflow.js +53 -33
  50. package/dist_ts/integrations/mqtt/mqtt.classes.integration.d.ts +4 -1
  51. package/dist_ts/integrations/mqtt/mqtt.classes.integration.js +127 -56
  52. package/dist_ts/integrations/mqtt/mqtt.classes.session.d.ts +13 -0
  53. package/dist_ts/integrations/mqtt/mqtt.classes.session.js +130 -0
  54. package/dist_ts/integrations/mqtt/mqtt.mapper.d.ts +3 -0
  55. package/dist_ts/integrations/mqtt/mqtt.mapper.js +44 -1
  56. package/dist_ts/integrations/mqtt/mqtt.types.d.ts +46 -0
  57. package/dist_ts/integrations/mqtt/mqtt.validation.d.ts +7 -0
  58. package/dist_ts/integrations/mqtt/mqtt.validation.js +180 -0
  59. package/dist_ts/integrations/samsungtv/samsungtv.classes.client.d.ts +4 -2
  60. package/dist_ts/integrations/samsungtv/samsungtv.classes.client.js +41 -16
  61. package/dist_ts/integrations/samsungtv/samsungtv.classes.configflow.d.ts +5 -0
  62. package/dist_ts/integrations/samsungtv/samsungtv.classes.configflow.js +20 -7
  63. package/dist_ts/integrations/samsungtv/samsungtv.classes.integration.d.ts +1 -0
  64. package/dist_ts/integrations/samsungtv/samsungtv.classes.integration.js +53 -50
  65. package/dist_ts/integrations/samsungtv/samsungtv.types.d.ts +4 -1
  66. package/dist_ts/integrations/shelly/shelly.classes.client.d.ts +26 -30
  67. package/dist_ts/integrations/shelly/shelly.classes.client.js +504 -276
  68. package/dist_ts/integrations/shelly/shelly.classes.configflow.d.ts +8 -3
  69. package/dist_ts/integrations/shelly/shelly.classes.configflow.js +43 -38
  70. package/dist_ts/integrations/shelly/shelly.classes.integration.d.ts +6 -6
  71. package/dist_ts/integrations/shelly/shelly.classes.integration.js +103 -101
  72. package/dist_ts/integrations/shelly/shelly.classes.subscription.d.ts +4 -0
  73. package/dist_ts/integrations/shelly/shelly.classes.subscription.js +24 -2
  74. package/dist_ts/integrations/shelly/shelly.mapper.d.ts +1 -2
  75. package/dist_ts/integrations/shelly/shelly.mapper.js +21 -20
  76. package/dist_ts/integrations/shelly/shelly.rpc.js +4 -1
  77. package/dist_ts/integrations/shelly/shelly.types.d.ts +42 -2
  78. package/dist_ts/integrations/shelly/shelly.types.js +11 -2
  79. package/dist_ts/integrations/shelly/shelly.validation.d.ts +7 -0
  80. package/dist_ts/integrations/shelly/shelly.validation.js +79 -0
  81. package/dist_ts/integrations/sonos/sonos.classes.client.d.ts +79 -22
  82. package/dist_ts/integrations/sonos/sonos.classes.client.js +427 -139
  83. package/dist_ts/integrations/sonos/sonos.classes.configflow.d.ts +9 -2
  84. package/dist_ts/integrations/sonos/sonos.classes.configflow.js +26 -28
  85. package/dist_ts/integrations/sonos/sonos.classes.integration.d.ts +7 -6
  86. package/dist_ts/integrations/sonos/sonos.classes.integration.js +289 -224
  87. package/dist_ts/integrations/sonos/sonos.config.d.ts +6 -1
  88. package/dist_ts/integrations/sonos/sonos.config.js +23 -2
  89. package/dist_ts/integrations/sonos/sonos.mapper.d.ts +1 -1
  90. package/dist_ts/integrations/sonos/sonos.mapper.js +14 -9
  91. package/dist_ts/integrations/sonos/sonos.matchers.js +3 -3
  92. package/dist_ts/integrations/sonos/sonos.types.d.ts +26 -1
  93. package/dist_ts/integrations/tasmota/tasmota.classes.integration.d.ts +5 -0
  94. package/dist_ts/integrations/tasmota/tasmota.classes.integration.js +23 -7
  95. package/dist_ts/integrations/tasmota/tasmota.classes.runtime.d.ts +19 -5
  96. package/dist_ts/integrations/tasmota/tasmota.classes.runtime.js +115 -41
  97. package/dist_ts/integrations/tasmota/tasmota.config.js +23 -1
  98. package/dist_ts/integrations/tasmota/tasmota.credentials.d.ts +11 -0
  99. package/dist_ts/integrations/tasmota/tasmota.credentials.js +97 -0
  100. package/dist_ts/integrations/tasmota/tasmota.discovery.d.ts +7 -1
  101. package/dist_ts/integrations/tasmota/tasmota.discovery.js +108 -30
  102. package/dist_ts/integrations/tasmota/tasmota.mapper.d.ts +2 -2
  103. package/dist_ts/integrations/tasmota/tasmota.mapper.js +7 -7
  104. package/dist_ts/integrations/tasmota/tasmota.types.d.ts +41 -2
  105. package/dist_ts/integrations/tuya/tuya.classes.integration.d.ts +4 -1
  106. package/dist_ts/integrations/tuya/tuya.classes.integration.js +79 -110
  107. package/dist_ts/integrations/tuya/tuya.classes.runtime.d.ts +5 -2
  108. package/dist_ts/integrations/tuya/tuya.classes.runtime.js +73 -26
  109. package/dist_ts/integrations/tuya/tuya.mapper.js +2 -2
  110. package/dist_ts/integrations/tuya/tuya.types.d.ts +3 -0
  111. package/dist_ts/integrations/tuya/tuya.validation.d.ts +2 -2
  112. package/dist_ts/integrations/tuya/tuya.validation.js +6 -11
  113. package/dist_ts/integrations/webostv/webostv.classes.client.d.ts +6 -5
  114. package/dist_ts/integrations/webostv/webostv.classes.client.js +147 -76
  115. package/dist_ts/integrations/webostv/webostv.classes.configflow.d.ts +7 -2
  116. package/dist_ts/integrations/webostv/webostv.classes.configflow.js +36 -20
  117. package/dist_ts/integrations/webostv/webostv.classes.integration.d.ts +4 -4
  118. package/dist_ts/integrations/webostv/webostv.classes.integration.js +46 -54
  119. package/dist_ts/integrations/webostv/webostv.mapper.js +5 -5
  120. package/dist_ts/integrations/webostv/webostv.types.d.ts +16 -3
  121. package/dist_ts/integrations/webostv/webostv.types.js +1 -1
  122. package/dist_ts/integrations/webostv/webostv.validation.d.ts +3 -1
  123. package/dist_ts/integrations/webostv/webostv.validation.js +23 -1
  124. package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.classes.configflow.d.ts +6 -1
  125. package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.classes.configflow.js +18 -8
  126. package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.classes.integration.d.ts +4 -1
  127. package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.classes.integration.js +54 -6
  128. package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.classes.runtime.d.ts +12 -6
  129. package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.classes.runtime.js +73 -59
  130. package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.mapper.d.ts +3 -2
  131. package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.mapper.js +17 -12
  132. package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.types.d.ts +31 -2
  133. package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.validation.d.ts +9 -1
  134. package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.validation.js +57 -7
  135. package/dist_ts/integrations/zwave_js/zwave_js.classes.client.d.ts +10 -4
  136. package/dist_ts/integrations/zwave_js/zwave_js.classes.client.js +167 -31
  137. package/dist_ts/integrations/zwave_js/zwave_js.classes.configflow.d.ts +6 -1
  138. package/dist_ts/integrations/zwave_js/zwave_js.classes.configflow.js +57 -35
  139. package/dist_ts/integrations/zwave_js/zwave_js.classes.integration.d.ts +3 -1
  140. package/dist_ts/integrations/zwave_js/zwave_js.classes.integration.js +59 -37
  141. package/dist_ts/integrations/zwave_js/zwave_js.discovery.js +3 -3
  142. package/dist_ts/integrations/zwave_js/zwave_js.mapper.js +41 -24
  143. package/dist_ts/integrations/zwave_js/zwave_js.types.d.ts +25 -3
  144. package/dist_ts/integrations/zwave_js/zwave_js.validation.d.ts +6 -2
  145. package/dist_ts/integrations/zwave_js/zwave_js.validation.js +52 -17
  146. package/dist_ts/plugins.d.ts +2 -1
  147. package/dist_ts/plugins.js +3 -2
  148. package/package.json +8 -8
  149. package/readme.md +25 -6
  150. package/ts/00_commitinfo_data.ts +1 -1
  151. package/ts/core/classes.nativeintegrationowner.ts +5 -0
  152. package/ts/core/classes.runtimemanager.ts +11 -2
  153. package/ts/core/types.ts +2 -0
  154. package/ts/integrations/deconz/deconz.classes.client.ts +348 -252
  155. package/ts/integrations/deconz/deconz.classes.configflow.ts +57 -29
  156. package/ts/integrations/deconz/deconz.classes.integration.ts +69 -37
  157. package/ts/integrations/deconz/deconz.mapper.ts +22 -5
  158. package/ts/integrations/deconz/deconz.types.ts +49 -6
  159. package/ts/integrations/deconz/deconz.validation.ts +177 -4
  160. package/ts/integrations/deconz/readme.md +15 -1
  161. package/ts/integrations/esphome/esphome.classes.client.ts +179 -94
  162. package/ts/integrations/esphome/esphome.classes.configflow.ts +40 -33
  163. package/ts/integrations/esphome/esphome.classes.integration.ts +63 -36
  164. package/ts/integrations/esphome/esphome.discovery.ts +3 -12
  165. package/ts/integrations/esphome/esphome.mapper.ts +9 -7
  166. package/ts/integrations/esphome/esphome.types.ts +35 -6
  167. package/ts/integrations/esphome/esphome.validation.ts +66 -0
  168. package/ts/integrations/esphome/readme.md +20 -6
  169. package/ts/integrations/homeassistant/homeassistant.classes.client.ts +353 -125
  170. package/ts/integrations/homeassistant/homeassistant.classes.integration.ts +35 -18
  171. package/ts/integrations/homeassistant/homeassistant.classes.runtime.ts +50 -63
  172. package/ts/integrations/homeassistant/homeassistant.types.ts +17 -1
  173. package/ts/integrations/homeassistant/readme.md +22 -6
  174. package/ts/integrations/hue/hue.classes.client.ts +110 -41
  175. package/ts/integrations/hue/hue.classes.configflow.ts +12 -6
  176. package/ts/integrations/hue/hue.classes.integration.ts +92 -26
  177. package/ts/integrations/hue/hue.validation.ts +13 -3
  178. package/ts/integrations/hue/readme.md +3 -3
  179. package/ts/integrations/mqtt/mqtt.classes.client.ts +136 -19
  180. package/ts/integrations/mqtt/mqtt.classes.configflow.ts +45 -32
  181. package/ts/integrations/mqtt/mqtt.classes.integration.ts +99 -38
  182. package/ts/integrations/mqtt/mqtt.classes.session.ts +118 -0
  183. package/ts/integrations/mqtt/mqtt.mapper.ts +43 -1
  184. package/ts/integrations/mqtt/mqtt.types.ts +52 -0
  185. package/ts/integrations/mqtt/mqtt.validation.ts +157 -0
  186. package/ts/integrations/mqtt/readme.md +12 -3
  187. package/ts/integrations/samsungtv/readme.md +11 -5
  188. package/ts/integrations/samsungtv/samsungtv.classes.client.ts +40 -15
  189. package/ts/integrations/samsungtv/samsungtv.classes.configflow.ts +21 -6
  190. package/ts/integrations/samsungtv/samsungtv.classes.integration.ts +53 -38
  191. package/ts/integrations/samsungtv/samsungtv.types.ts +4 -1
  192. package/ts/integrations/shelly/readme.md +22 -7
  193. package/ts/integrations/shelly/shelly.classes.client.ts +386 -269
  194. package/ts/integrations/shelly/shelly.classes.configflow.ts +43 -38
  195. package/ts/integrations/shelly/shelly.classes.integration.ts +92 -111
  196. package/ts/integrations/shelly/shelly.classes.subscription.ts +24 -1
  197. package/ts/integrations/shelly/shelly.mapper.ts +19 -20
  198. package/ts/integrations/shelly/shelly.rpc.ts +2 -0
  199. package/ts/integrations/shelly/shelly.types.ts +50 -2
  200. package/ts/integrations/shelly/shelly.validation.ts +76 -0
  201. package/ts/integrations/sonos/readme.md +117 -38
  202. package/ts/integrations/sonos/sonos.classes.client.ts +392 -123
  203. package/ts/integrations/sonos/sonos.classes.configflow.ts +25 -28
  204. package/ts/integrations/sonos/sonos.classes.integration.ts +211 -186
  205. package/ts/integrations/sonos/sonos.config.ts +24 -2
  206. package/ts/integrations/sonos/sonos.mapper.ts +13 -8
  207. package/ts/integrations/sonos/sonos.matchers.ts +2 -2
  208. package/ts/integrations/sonos/sonos.types.ts +30 -1
  209. package/ts/integrations/tasmota/readme.md +16 -5
  210. package/ts/integrations/tasmota/tasmota.classes.integration.ts +24 -6
  211. package/ts/integrations/tasmota/tasmota.classes.runtime.ts +112 -43
  212. package/ts/integrations/tasmota/tasmota.config.ts +17 -0
  213. package/ts/integrations/tasmota/tasmota.credentials.ts +92 -0
  214. package/ts/integrations/tasmota/tasmota.discovery.ts +87 -24
  215. package/ts/integrations/tasmota/tasmota.mapper.ts +6 -6
  216. package/ts/integrations/tasmota/tasmota.types.ts +43 -2
  217. package/ts/integrations/tuya/readme.md +52 -10
  218. package/ts/integrations/tuya/tuya.classes.integration.ts +69 -98
  219. package/ts/integrations/tuya/tuya.classes.runtime.ts +70 -24
  220. package/ts/integrations/tuya/tuya.mapper.ts +1 -1
  221. package/ts/integrations/tuya/tuya.types.ts +4 -0
  222. package/ts/integrations/tuya/tuya.validation.ts +5 -8
  223. package/ts/integrations/webostv/readme.md +115 -22
  224. package/ts/integrations/webostv/webostv.classes.client.ts +123 -50
  225. package/ts/integrations/webostv/webostv.classes.configflow.ts +35 -15
  226. package/ts/integrations/webostv/webostv.classes.integration.ts +44 -37
  227. package/ts/integrations/webostv/webostv.mapper.ts +4 -4
  228. package/ts/integrations/webostv/webostv.types.ts +18 -3
  229. package/ts/integrations/webostv/webostv.validation.ts +22 -1
  230. package/ts/integrations/zigbee2mqtt/readme.md +22 -27
  231. package/ts/integrations/zigbee2mqtt/zigbee2mqtt.classes.configflow.ts +16 -7
  232. package/ts/integrations/zigbee2mqtt/zigbee2mqtt.classes.integration.ts +53 -6
  233. package/ts/integrations/zigbee2mqtt/zigbee2mqtt.classes.runtime.ts +62 -49
  234. package/ts/integrations/zigbee2mqtt/zigbee2mqtt.mapper.ts +17 -11
  235. package/ts/integrations/zigbee2mqtt/zigbee2mqtt.types.ts +35 -2
  236. package/ts/integrations/zigbee2mqtt/zigbee2mqtt.validation.ts +53 -7
  237. package/ts/integrations/zwave_js/readme.md +42 -9
  238. package/ts/integrations/zwave_js/zwave_js.classes.client.ts +155 -31
  239. package/ts/integrations/zwave_js/zwave_js.classes.configflow.ts +55 -31
  240. package/ts/integrations/zwave_js/zwave_js.classes.integration.ts +57 -35
  241. package/ts/integrations/zwave_js/zwave_js.discovery.ts +2 -2
  242. package/ts/integrations/zwave_js/zwave_js.mapper.ts +37 -23
  243. package/ts/integrations/zwave_js/zwave_js.types.ts +26 -3
  244. package/ts/integrations/zwave_js/zwave_js.validation.ts +52 -18
  245. package/ts/plugins.ts +2 -1
@@ -1,4 +1,144 @@
1
- import type { IDeconzConfig, IDeconzGatewayConfig, IDeconzGroupActionPatch, IDeconzLightingStatePatch, IDeconzLightStatePatch, IDeconzSensorConfigPatch, IDeconzSnapshot, IDeconzWebsocketEvent } from './deconz.types.js';
1
+ import type { IDeconzConfig, IDeconzGatewayConfig, IDeconzGroupActionPatch, IDeconzIntegrationOptions, IDeconzLightingStatePatch, IDeconzLightStatePatch, IDeconzSensorConfigPatch, IDeconzSnapshot, IDeconzWebsocketEvent } from './deconz.types.js';
2
+
3
+ export function deconzData(valueArg: unknown, allowedArg: readonly string[]): Record<string, unknown> {
4
+ if (!valueArg || typeof valueArg !== 'object' || ![Object.prototype, null].includes(Object.getPrototypeOf(valueArg) as object | null)) throw new TypeError('deCONZ requires a plain data object.');
5
+ const result: Record<string, unknown> = Object.create(null) as Record<string, unknown>;
6
+ for (const key of Reflect.ownKeys(valueArg)) {
7
+ const descriptor = Object.getOwnPropertyDescriptor(valueArg, key);
8
+ if (typeof key !== 'string' || !allowedArg.includes(key) || !descriptor?.enumerable || !('value' in descriptor)) throw new TypeError('Unsupported deCONZ field or accessor.');
9
+ result[key] = descriptor.value as unknown;
10
+ }
11
+ return result;
12
+ }
13
+
14
+ export function deconzText(valueArg: unknown, nameArg: string, maximumArg = 256): string {
15
+ if (typeof valueArg !== 'string' || !valueArg || valueArg.length > maximumArg || valueArg.trim() !== valueArg || /[\p{Cc}\p{Cs}]/u.test(valueArg)) throw new TypeError(`Invalid deCONZ ${nameArg}.`);
16
+ return valueArg;
17
+ }
18
+
19
+ export function deconzBridgeId(valueArg: unknown): string {
20
+ const text = deconzText(valueArg, 'selected bridge identity');
21
+ if (!/^[a-f0-9]{16}$/i.test(text) && !/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i.test(text)) throw new TypeError('Invalid deCONZ selected bridge identity.');
22
+ return text.toLowerCase();
23
+ }
24
+
25
+ export function normalizeDeconzConfig(valueArg: unknown): Readonly<IDeconzConfig> {
26
+ const value = deconzData(valueArg, ['homeId', 'host', 'port', 'protocol', 'bridgeId', 'credentialReference', 'enableWebSocket', 'requestTimeoutMs']);
27
+ const homeId = deconzText(value.homeId, 'configuration home');
28
+ const credentialReference = deconzText(value.credentialReference, 'credential reference');
29
+ if (encodeURIComponent(credentialReference).length > 900) throw new TypeError('deCONZ credential reference is too long.');
30
+ const config: IDeconzConfig = { homeId, host: deconzText(value.host, 'host'), credentialReference, bridgeId: deconzBridgeId(value.bridgeId) };
31
+ if (value.protocol !== undefined) {
32
+ if (value.protocol !== 'http' && value.protocol !== 'https') throw new TypeError('Invalid deCONZ protocol.');
33
+ config.protocol = value.protocol;
34
+ }
35
+ if (value.port !== undefined) config.port = deconzPort(value.port);
36
+ if (value.enableWebSocket !== undefined) {
37
+ if (typeof value.enableWebSocket !== 'boolean') throw new TypeError('Invalid deCONZ WebSocket setting.');
38
+ config.enableWebSocket = value.enableWebSocket;
39
+ }
40
+ const timeout = value.requestTimeoutMs ?? 15000;
41
+ if (typeof timeout !== 'number' || !Number.isInteger(timeout) || timeout < 1 || timeout > 120000) throw new TypeError('Invalid deCONZ request deadline.');
42
+ config.requestTimeoutMs = timeout;
43
+ deconzEndpoint(config);
44
+ return Object.freeze(config);
45
+ }
46
+
47
+ function method(valueArg: unknown, keyArg: string): ((...argsArg: never[]) => unknown) | undefined {
48
+ if (!valueArg || typeof valueArg !== 'object') throw new TypeError('Invalid deCONZ credential provider.');
49
+ let target: object | null = valueArg;
50
+ for (let depth = 0; target && depth < 16; depth++) {
51
+ const descriptor = Object.getOwnPropertyDescriptor(target, keyArg);
52
+ if (descriptor) {
53
+ if (!('value' in descriptor) || typeof descriptor.value !== 'function') throw new TypeError('deCONZ provider requires data methods.');
54
+ return Function.prototype.bind.call(descriptor.value, valueArg) as (...argsArg: never[]) => unknown;
55
+ }
56
+ target = Object.getPrototypeOf(target) as object | null;
57
+ }
58
+ return undefined;
59
+ }
60
+
61
+ export function captureDeconzOptions(valueArg: IDeconzIntegrationOptions = {}): Readonly<IDeconzIntegrationOptions> {
62
+ const value = deconzData(valueArg, ['homeId', 'capacity', 'credentialProvider', 'controlPermissions', 'db', 'keyProvider']);
63
+ const result: IDeconzIntegrationOptions = {};
64
+ if (value.homeId !== undefined) Object.assign(result, { homeId: deconzText(value.homeId, 'constructor home') });
65
+ if (value.capacity !== undefined) {
66
+ if (typeof value.capacity !== 'number' || !Number.isSafeInteger(value.capacity) || value.capacity < 1 || value.capacity > 16) throw new TypeError('deCONZ capacity must be from 1 through 16.');
67
+ Object.assign(result, { capacity: value.capacity });
68
+ }
69
+ if (value.credentialProvider !== undefined) {
70
+ const resolve = method(value.credentialProvider, 'resolve');
71
+ const create = method(value.credentialProvider, 'create');
72
+ if (!resolve) throw new TypeError('deCONZ credential provider must resolve references.');
73
+ Object.assign(result, { credentialProvider: Object.freeze({ resolve, ...(create ? { create } : {}) }) });
74
+ }
75
+ if (value.controlPermissions !== undefined) {
76
+ if (!Array.isArray(value.controlPermissions) || value.controlPermissions.length > 128) throw new TypeError('Invalid deCONZ control permissions.');
77
+ const permissions = dataArray(value.controlPermissions).map((permissionArg: unknown) => {
78
+ const permission = deconzData(permissionArg, ['resource', 'resourceId', 'fields']);
79
+ if (typeof permission.resource !== 'string' || !['config', 'lights', 'groups', 'sensors', 'scenes'].includes(permission.resource)) throw new TypeError('Invalid deCONZ permission resource.');
80
+ const resourceId = deconzText(permission.resourceId, 'permission resource', 128);
81
+ if (!Array.isArray(permission.fields) || !permission.fields.length || permission.fields.length > 32) throw new TypeError('Invalid deCONZ permission fields.');
82
+ const fields = dataArray(permission.fields).map((fieldArg: unknown) => deconzText(fieldArg, 'permission field', 64));
83
+ return Object.freeze({ resource: permission.resource, resourceId, fields: Object.freeze(fields) });
84
+ });
85
+ Object.assign(result, { controlPermissions: Object.freeze(permissions) });
86
+ }
87
+ if (value.db !== undefined) Object.assign(result, { db: value.db });
88
+ if (value.keyProvider !== undefined) {
89
+ const currentKey = method(value.keyProvider, 'currentKey');
90
+ const keyForId = method(value.keyProvider, 'keyForId');
91
+ if (!currentKey || !keyForId) throw new TypeError('deCONZ encrypted storage requires data-method key providers.');
92
+ Object.assign(result, { keyProvider: Object.freeze({ currentKey, keyForId }) });
93
+ }
94
+ if (Boolean(value.db) !== Boolean(value.keyProvider)) throw new TypeError('deCONZ owned encrypted storage requires both database and key provider.');
95
+ return Object.freeze(result);
96
+ }
97
+
98
+ function dataArray(valueArg: unknown[]): unknown[] {
99
+ if (Reflect.ownKeys(valueArg).some((key) => key !== 'length' && (typeof key !== 'string' || !/^(0|[1-9]\d*)$/.test(key) || Number(key) >= valueArg.length))) throw new TypeError('deCONZ arrays require dense data fields.');
100
+ return Array.from({ length: valueArg.length }, (_, index) => { const descriptor = Object.getOwnPropertyDescriptor(valueArg, String(index)); if (!descriptor?.enumerable || !('value' in descriptor)) throw new TypeError('deCONZ arrays require dense data fields.'); return descriptor.value as unknown; });
101
+ }
102
+
103
+ export function deconzCurrentPatch(snapshotArg: IDeconzSnapshot, resourceArg: 'lights' | 'groups' | 'sensors', idArg: string, sectionArg: 'state' | 'action' | 'config', patchArg: Record<string, unknown>): void {
104
+ if (snapshotArg.config?.rfconnected === false) throw new Error('deCONZ Zigbee network is unavailable.');
105
+ const item = snapshotArg[resourceArg][idArg];
106
+ if (!item) throw new Error('deCONZ control requires an exact current resource.');
107
+ const state = resourceArg === 'groups' ? snapshotArg.groups[idArg].action : resourceArg === 'sensors' ? sectionArg === 'config' ? snapshotArg.sensors[idArg].config : snapshotArg.sensors[idArg].state : snapshotArg.lights[idArg].state;
108
+ if (!state || state.reachable === false || resourceArg === 'sensors' && snapshotArg.sensors[idArg].config?.reachable === false) throw new Error('deCONZ resource is unavailable.');
109
+ if (resourceArg === 'groups' && !snapshotArg.groups[idArg].lights?.length) throw new Error('deCONZ group has no current members.');
110
+ for (const [key, value] of Object.entries(patchArg)) {
111
+ if (resourceArg === 'lights' && ['ct', 'hue', 'sat', 'xy'].includes(key)) {
112
+ const light = snapshotArg.lights[idArg];
113
+ const mode = key === 'hue' || key === 'sat' ? 'hs' : key;
114
+ const modes = light.modes ?? (Array.isArray(light.colorcapabilities) ? light.colorcapabilities : undefined);
115
+ const mask = mode === 'ct' ? 16 : mode === 'xy' ? 8 : 3;
116
+ if (modes && !modes.includes(mode) || typeof light.colorcapabilities === 'number' && !(light.colorcapabilities & mask)) throw new Error('deCONZ color control is not currently advertised.');
117
+ }
118
+ if (key === 'transitiontime') {
119
+ if (!Object.keys(patchArg).some((entryArg) => ['bri', 'ct', 'hue', 'sat', 'xy', 'on'].includes(entryArg))) throw new Error('deCONZ transition requires an actual lighting control.');
120
+ continue;
121
+ }
122
+ if (key === 'colorloopspeed') {
123
+ if (patchArg.effect !== 'colorloop') throw new Error('deCONZ colorloop speed requires a selected effect.');
124
+ continue;
125
+ }
126
+ if (key === 'toggle' && resourceArg === 'groups' && typeof state.on === 'boolean') continue;
127
+ if (key === 'stop' && resourceArg === 'lights' && typeof state.open === 'boolean') continue;
128
+ if (key === 'setvalve' && resourceArg === 'sensors' && typeof state.valve === 'number') continue;
129
+ if (key === 'effect' && resourceArg === 'lights') {
130
+ const effects = snapshotArg.lights[idArg].effects;
131
+ if (!Array.isArray(effects) || !effects.includes(String(value)) || typeof state.effect !== 'string') throw new Error('deCONZ effect is not currently advertised.');
132
+ continue;
133
+ }
134
+ if (state[key] === undefined || key !== 'lift' && typeof value !== typeof state[key]) throw new Error('deCONZ control field is not currently supported.');
135
+ if (key === 'ct' && resourceArg === 'lights') {
136
+ const light = snapshotArg.lights[idArg];
137
+ if (!Number.isInteger(light.ctmin) || !Number.isInteger(light.ctmax) || light.ctmin! < 1 || light.ctmax! < light.ctmin! || typeof value !== 'number' || value < light.ctmin! || value > light.ctmax!) throw new Error('deCONZ color temperature is outside current advertised bounds.');
138
+ }
139
+ if (key === 'ct' && resourceArg === 'groups' && (typeof value !== 'number' || value < 153 || value > 500)) throw new Error('deCONZ group color temperature is outside supported bounds.');
140
+ }
141
+ }
2
142
 
3
143
  type TDeconzPatchValue = string | number | boolean | number[];
4
144
  type TDeconzPatchValidator = (valueArg: unknown) => boolean;
@@ -46,6 +186,7 @@ const LIGHT_PATCH_FIELDS = {
46
186
 
47
187
  const GROUP_PATCH_FIELDS = {
48
188
  ...LIGHTING_PATCH_FIELDS,
189
+ ct: integerRange(153, 500),
49
190
  alert: stringValues(['none', 'select', 'lselect']),
50
191
  effect: stringValues(['none', 'colorloop']),
51
192
  toggle: booleanValue,
@@ -187,7 +328,7 @@ function state(valueArg: unknown): void {
187
328
  if (Object.values(valueArg).some((entryArg) => typeof entryArg === 'number' && !Number.isFinite(entryArg))) throw new Error('Invalid deCONZ numeric state.');
188
329
  fields(valueArg, ['alert', 'colormode', 'effect', 'airquality', 'lastupdated', 'mode', 'fanmode', 'preset'], 'string');
189
330
  fields(valueArg, ['on', 'open', 'reachable', 'all_on', 'any_on', 'alarm', 'carbonmonoxide', 'fire', 'flag', 'presence', 'vibration', 'water', 'tampered', 'lowbattery', 'dark', 'daylight', 'locked', 'stop'], 'boolean');
190
- fields(valueArg, ['bri', 'ct', 'hue', 'sat', 'tilt', 'speed', 'consumption', 'current', 'humidity', 'lux', 'lightlevel', 'power', 'pressure', 'status', 'temperature', 'voltage', 'heatsetpoint', 'coolsetpoint', 'offset', 'valve', 'airqualityppb'], 'number');
331
+ fields(valueArg, ['bri', 'ct', 'hue', 'sat', 'tilt', 'speed', 'consumption', 'current', 'humidity', 'lux', 'lightlevel', 'power', 'pressure', 'status', 'temperature', 'voltage', 'heatsetpoint', 'coolsetpoint', 'offset', 'valve', 'airqualityppb', 'gesture', 'duration'], 'number');
191
332
  for (const key of ['battery', 'buttonevent']) {
192
333
  if (valueArg[key] !== null) fields(valueArg, [key], 'number');
193
334
  }
@@ -204,6 +345,14 @@ function resource(valueArg: unknown, kindArg: 'lights' | 'groups' | 'sensors'):
204
345
  record(valueArg.state);
205
346
  if (kindArg === 'sensors') record(valueArg.config);
206
347
  for (const key of ['state', 'config', 'action']) if (valueArg[key] !== undefined) state(valueArg[key]);
348
+ if (kindArg === 'lights') {
349
+ fields(valueArg, ['ctmin', 'ctmax'], 'number');
350
+ for (const key of ['modes', 'colorcapabilities']) {
351
+ const value = valueArg[key];
352
+ if (value !== undefined && !(key === 'colorcapabilities' && typeof value === 'number' && Number.isInteger(value) && value >= 0 && value <= 65535) && (!Array.isArray(value) || value.some((itemArg) => typeof itemArg !== 'string'))) throw new Error('Invalid deCONZ advertised color capabilities.');
353
+ }
354
+ if (valueArg.effects !== undefined && (!Array.isArray(valueArg.effects) || valueArg.effects.some((itemArg) => typeof itemArg !== 'string' || !itemArg))) throw new Error('Invalid deCONZ advertised effects.');
355
+ }
207
356
  if (kindArg === 'groups') {
208
357
  for (const key of ['lights', 'devicemembership', 'lightsequence', 'multideviceids']) {
209
358
  const value = valueArg[key];
@@ -223,6 +372,20 @@ function resource(valueArg: unknown, kindArg: 'lights' | 'groups' | 'sensors'):
223
372
  }
224
373
  }
225
374
 
375
+ const stateKeys = ['alert', 'bri', 'colormode', 'ct', 'effect', 'hue', 'lift', 'on', 'open', 'reachable', 'sat', 'speed', 'stop', 'tilt', 'xy', 'all_on', 'any_on', 'airquality', 'airqualityppb', 'alarm', 'battery', 'buttonevent', 'carbonmonoxide', 'consumption', 'current', 'dark', 'daylight', 'fire', 'flag', 'gesture', 'humidity', 'lastupdated', 'lightlevel', 'lux', 'power', 'presence', 'pressure', 'status', 'temperature', 'vibration', 'voltage', 'water', 'coolsetpoint', 'fanmode', 'heatsetpoint', 'locked', 'lowbattery', 'mode', 'offset', 'preset', 'tampered', 'valve', 'duration'];
376
+ const resourceKeys = ['id', 'colorcapabilities', 'modes', 'ctmax', 'ctmin', 'effects', 'etag', 'hascolor', 'lastannounced', 'lastseen', 'manufacturername', 'modelid', 'name', 'swversion', 'type', 'uniqueid', 'devicemembership', 'hidden', 'lights', 'lightsequence', 'multideviceids', 'scenes', 'class', 'ep', 'mode'];
377
+ function cleanState(valueArg: Record<string, unknown>): Record<string, unknown> {
378
+ return Object.fromEntries(Object.entries(valueArg).filter(([key]) => stateKeys.includes(key)));
379
+ }
380
+ function cleanResource(valueArg: Record<string, unknown>): Record<string, unknown> {
381
+ const result: Record<string, unknown> = {};
382
+ for (const key of resourceKeys) if (valueArg[key] !== undefined) {
383
+ result[key] = key === 'scenes' ? Object.values(valueArg[key] as Record<string, Record<string, unknown>>).map((scene) => ({ id: scene.id, ...(scene.name !== undefined ? { name: scene.name } : {}) })) : structuredClone(valueArg[key]);
384
+ }
385
+ for (const section of ['state', 'config', 'action']) if (isDeconzRecord(valueArg[section])) result[section] = cleanState(valueArg[section]);
386
+ return result;
387
+ }
388
+
226
389
  export function deconzSnapshot(valueArg: unknown): IDeconzSnapshot {
227
390
  record(valueArg);
228
391
  const config = deconzGatewayConfig(valueArg.config);
@@ -240,7 +403,13 @@ export function deconzSnapshot(valueArg: unknown): IDeconzSnapshot {
240
403
  fields(item, ['id', 'groupId', 'name'], 'string');
241
404
  }
242
405
  }
243
- return { ...valueArg, config } as IDeconzSnapshot;
406
+ const result: IDeconzSnapshot = { config, lights: {}, groups: {}, sensors: {} };
407
+ for (const kind of ['lights', 'groups', 'sensors'] as const) {
408
+ for (const [id, itemArg] of Object.entries(valueArg[kind] as Record<string, Record<string, unknown>>)) {
409
+ result[kind][id] = cleanResource(itemArg);
410
+ }
411
+ }
412
+ return result;
244
413
  }
245
414
 
246
415
  export function deconzWebsocketEvent(valueArg: unknown): IDeconzWebsocketEvent {
@@ -257,5 +426,9 @@ export function deconzWebsocketEvent(valueArg: unknown): IDeconzWebsocketEvent {
257
426
  if (valueArg[key] !== undefined) resource(valueArg[key], kind);
258
427
  if (valueArg.e === 'added' && valueArg.r === kind && valueArg[key] === undefined) throw new Error('Invalid deCONZ added event.');
259
428
  }
260
- return valueArg as unknown as IDeconzWebsocketEvent;
429
+ const result: Record<string, unknown> = {};
430
+ for (const key of ['t', 'e', 'r', 'id', 'gid', 'scid', 'uniqueid', 'name']) if (valueArg[key] !== undefined) result[key] = valueArg[key];
431
+ for (const key of ['state', 'config']) if (isDeconzRecord(valueArg[key])) result[key] = cleanState(valueArg[key]);
432
+ for (const key of ['light', 'group', 'sensor', 'scene']) if (isDeconzRecord(valueArg[key])) result[key] = cleanResource(valueArg[key]);
433
+ return result as unknown as IDeconzWebsocketEvent;
261
434
  }
@@ -8,6 +8,20 @@ For reporting bugs, issues, or security vulnerabilities, please visit [community
8
8
 
9
9
  ## Direct write contracts
10
10
 
11
+ Setup and config-flow contexts require an independently selected `homeId`. The same home must appear in `IDeconzConfig`; an optional constructor `homeId` is an additional constraint. Integration, flow, runtime and public client operations share immutable home admission. Native device and entity identifiers include the owning home, selected bridge and exact resource identity.
12
+
13
+ Durable configuration contains `homeId`, `host`, optional `port`/`protocol`, the selected `bridgeId`, and a `credentialReference`. Inline API keys, fixture snapshots and caller-selected WebSocket ports are rejected. Supply an `IDeconzCredentialProvider` through constructor options, or an actual `NoSqlConfigStore` scoped to that home and `deconz`. The provider receives a frozen request containing the captured home, origin, bridge, reference, cancellation signal and monotonic deadline. It is the sole credential source when supplied; failed resolution never falls back to another source. A store envelope contains version 1 and the exact home, origin, bridge, reference and API key, encrypted by the store. Credential creation uses the store's insert-only `create` operation or the provider's `create` contract. Callers migrate existing keys into their credential provider or encrypted store before adopting reference configuration.
14
+
15
+ An integration may construct its own store from a caller-owned database and external encryption key provider. Only that store is closed; injected stores, databases and key providers remain caller-owned. Owned-store retry uses the public `NoSqlConfigStore.retryCleanup` contract.
16
+
17
+ Control permissions are supplied only through constructor `controlPermissions` and default to denied. Each permission names an exact resource kind, resource ID and permitted fields; configuration, discovery advertisements, forms, device features and stored credentials do not grant permission. Pairing requires a `config` permission for the selected bridge with both `pair` and `credentials:create`; accepting a supplied pairing key still requires `credentials:create`. Returned flow configuration contains the reference, never the key. An issued form becomes terminal when its owner closes or pairing/storage becomes indeterminate.
18
+
19
+ Before credentials are dispatched, the client checks the currently reported selected bridge through the public configuration endpoint. Commands then read current resources and check exact targets, supported fields, types, advertised color modes/effects and temperature bounds. This comparison checks a reported identifier; it does not cryptographically authenticate a LAN peer. Light, switch, covering, group, thermostat and writable CLIP controls remain available when their current capabilities and owner permissions allow them.
20
+
21
+ Each accepted command sends one PUT and performs an independent supported REST snapshot read. Its result contains `effectOutcome: 'accepted'`, `observedState`, `causalityConfirmed: false` and `physicalCompletion: 'unconfirmed'`. An acknowledgement does not prove a physical device changed, and matching readback does not prove the command caused it. Failure after dispatch produces an indeterminate result; shutdown and explicit cleanup retry never replay a command. Generic `post` creation has no supported control contract and is denied.
22
+
23
+ The shared capacity defaults to 16 and includes live runtimes, subscriptions, pending operations and retained cleanup. A runtime command uses one operation slot through validation and readback. Asynchronous event/error callbacks retain their actual promises. Deadlines abort transport and prevent late credential dispatch; noncooperative callbacks remain owned until they settle. `destroy()` is terminal and memoized, joins sibling cleanup despite failures, and retains failed handles. Only `retryCleanup()` retries cleanup or acknowledges settled callback failures. Borrowed-store internals remain under the injected store owner's lifecycle.
24
+
11
25
  `DeconzClient.setLightState`, `setGroupState` and `setSensorConfig` accept explicit patch interfaces. Patches must be non-empty plain objects containing enumerable data properties. Unknown fields, nested objects, accessors, serialization hooks and values outside the protocol types or ranges are rejected before HTTP I/O. Read response fields such as light `reachable` and `colormode`, group `all_on`, and sensor `temperature`, `lowbattery`, `tampered` and `valve` are not write fields.
12
26
 
13
27
  | Method | Accepted fields |
@@ -16,7 +30,7 @@ For reporting bugs, issues, or security vulnerabilities, please visit [community
16
30
  | `setGroupState` | `alert`, `bri`, `colorloopspeed`, `ct`, `effect`, `hue`, `on`, `sat`, `transitiontime`, `xy`, `toggle` |
17
31
  | `setSensorConfig` | `battery`, `coolsetpoint`, `fanmode`, `heatsetpoint`, `locked`, `mode`, `offset`, `on`, `preset`, `reachable`, `setvalve` |
18
32
 
19
- Brightness and saturation use integers from 0 to 255; hue uses 0 to 65535; color coordinates are two finite numbers from 0 to 1. Color temperature is an integer from 1 to 65535 mireds; each device can impose narrower limits. Transition time uses integer tenths of a second from 0 to 65535. `colorloopspeed` uses 1 to 255 and requires `effect: 'colorloop'`. Light effect names remain device dependent and must be non-empty strings; group effects are `'none'` or `'colorloop'`. Light alert names include the gateway's documented identify and trigger effects; group alerts are `'none'`, `'select'` or `'lselect'`.
33
+ Brightness and saturation use integers from 0 to 255; hue uses 0 to 65535; color coordinates are two finite numbers from 0 to 1. Color temperature is an integer from 1 to 65535 mireds for lights, bounded by their current advertised `ctmin`/`ctmax`; group controls use 153 to 500. Transition time uses integer tenths of a second from 0 to 65535. `colorloopspeed` uses 1 to 255 and requires `effect: 'colorloop'`. Light effect names remain device dependent and must be non-empty strings; group effects are `'none'` or `'colorloop'`. Light alert names include the gateway's documented identify and trigger effects; group alerts are `'none'`, `'select'` or `'lselect'`.
20
34
 
21
35
  Window covering `lift` is an integer percentage closed from 0 to 100 or `'stop'`; `tilt` uses 0 to 100 and fan `speed` uses 0 to 6. Sensor setpoints use hundredths of a degree: `heatsetpoint` is 500 to 3200 and `coolsetpoint` is 700 to 3500. `offset` uses a signed 16-bit integer. Sensor mode, fan mode and preset strings are restricted to the documented choices in `IDeconzSensorConfigPatch`. Availability of each write field still depends on the gateway resource; a protocol-valid patch does not grant a device capability.
22
36