@smarthome.exchange/integrations 2.0.0 → 4.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 (251) hide show
  1. package/changelog.md +46 -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 +32 -4
  40. package/dist_ts/integrations/hue/hue.classes.client.js +331 -57
  41. package/dist_ts/integrations/hue/hue.classes.configflow.d.ts +6 -1
  42. package/dist_ts/integrations/hue/hue.classes.configflow.js +79 -27
  43. package/dist_ts/integrations/hue/hue.classes.integration.d.ts +3 -1
  44. package/dist_ts/integrations/hue/hue.classes.integration.js +83 -62
  45. package/dist_ts/integrations/hue/hue.mapper.d.ts +2 -0
  46. package/dist_ts/integrations/hue/hue.mapper.js +8 -6
  47. package/dist_ts/integrations/hue/hue.types.d.ts +32 -1
  48. package/dist_ts/integrations/hue/hue.validation.d.ts +9 -3
  49. package/dist_ts/integrations/hue/hue.validation.js +112 -11
  50. package/dist_ts/integrations/mqtt/mqtt.classes.client.d.ts +15 -2
  51. package/dist_ts/integrations/mqtt/mqtt.classes.client.js +173 -23
  52. package/dist_ts/integrations/mqtt/mqtt.classes.configflow.d.ts +5 -1
  53. package/dist_ts/integrations/mqtt/mqtt.classes.configflow.js +53 -33
  54. package/dist_ts/integrations/mqtt/mqtt.classes.integration.d.ts +4 -1
  55. package/dist_ts/integrations/mqtt/mqtt.classes.integration.js +127 -56
  56. package/dist_ts/integrations/mqtt/mqtt.classes.session.d.ts +13 -0
  57. package/dist_ts/integrations/mqtt/mqtt.classes.session.js +130 -0
  58. package/dist_ts/integrations/mqtt/mqtt.mapper.d.ts +3 -0
  59. package/dist_ts/integrations/mqtt/mqtt.mapper.js +44 -1
  60. package/dist_ts/integrations/mqtt/mqtt.types.d.ts +46 -0
  61. package/dist_ts/integrations/mqtt/mqtt.validation.d.ts +7 -0
  62. package/dist_ts/integrations/mqtt/mqtt.validation.js +180 -0
  63. package/dist_ts/integrations/samsungtv/samsungtv.classes.client.d.ts +4 -2
  64. package/dist_ts/integrations/samsungtv/samsungtv.classes.client.js +41 -16
  65. package/dist_ts/integrations/samsungtv/samsungtv.classes.configflow.d.ts +5 -0
  66. package/dist_ts/integrations/samsungtv/samsungtv.classes.configflow.js +20 -7
  67. package/dist_ts/integrations/samsungtv/samsungtv.classes.integration.d.ts +1 -0
  68. package/dist_ts/integrations/samsungtv/samsungtv.classes.integration.js +53 -50
  69. package/dist_ts/integrations/samsungtv/samsungtv.types.d.ts +4 -1
  70. package/dist_ts/integrations/shelly/shelly.classes.client.d.ts +26 -30
  71. package/dist_ts/integrations/shelly/shelly.classes.client.js +504 -276
  72. package/dist_ts/integrations/shelly/shelly.classes.configflow.d.ts +8 -3
  73. package/dist_ts/integrations/shelly/shelly.classes.configflow.js +43 -38
  74. package/dist_ts/integrations/shelly/shelly.classes.integration.d.ts +6 -6
  75. package/dist_ts/integrations/shelly/shelly.classes.integration.js +103 -101
  76. package/dist_ts/integrations/shelly/shelly.classes.subscription.d.ts +4 -0
  77. package/dist_ts/integrations/shelly/shelly.classes.subscription.js +24 -2
  78. package/dist_ts/integrations/shelly/shelly.mapper.d.ts +1 -2
  79. package/dist_ts/integrations/shelly/shelly.mapper.js +21 -20
  80. package/dist_ts/integrations/shelly/shelly.rpc.js +4 -1
  81. package/dist_ts/integrations/shelly/shelly.types.d.ts +42 -2
  82. package/dist_ts/integrations/shelly/shelly.types.js +11 -2
  83. package/dist_ts/integrations/shelly/shelly.validation.d.ts +7 -0
  84. package/dist_ts/integrations/shelly/shelly.validation.js +79 -0
  85. package/dist_ts/integrations/sonos/sonos.classes.client.d.ts +79 -22
  86. package/dist_ts/integrations/sonos/sonos.classes.client.js +427 -139
  87. package/dist_ts/integrations/sonos/sonos.classes.configflow.d.ts +9 -2
  88. package/dist_ts/integrations/sonos/sonos.classes.configflow.js +26 -28
  89. package/dist_ts/integrations/sonos/sonos.classes.integration.d.ts +7 -6
  90. package/dist_ts/integrations/sonos/sonos.classes.integration.js +289 -224
  91. package/dist_ts/integrations/sonos/sonos.config.d.ts +6 -1
  92. package/dist_ts/integrations/sonos/sonos.config.js +23 -2
  93. package/dist_ts/integrations/sonos/sonos.mapper.d.ts +1 -1
  94. package/dist_ts/integrations/sonos/sonos.mapper.js +14 -9
  95. package/dist_ts/integrations/sonos/sonos.matchers.js +3 -3
  96. package/dist_ts/integrations/sonos/sonos.types.d.ts +26 -1
  97. package/dist_ts/integrations/tasmota/tasmota.classes.integration.d.ts +5 -0
  98. package/dist_ts/integrations/tasmota/tasmota.classes.integration.js +23 -7
  99. package/dist_ts/integrations/tasmota/tasmota.classes.runtime.d.ts +19 -5
  100. package/dist_ts/integrations/tasmota/tasmota.classes.runtime.js +115 -41
  101. package/dist_ts/integrations/tasmota/tasmota.config.js +23 -1
  102. package/dist_ts/integrations/tasmota/tasmota.credentials.d.ts +11 -0
  103. package/dist_ts/integrations/tasmota/tasmota.credentials.js +97 -0
  104. package/dist_ts/integrations/tasmota/tasmota.discovery.d.ts +7 -1
  105. package/dist_ts/integrations/tasmota/tasmota.discovery.js +108 -30
  106. package/dist_ts/integrations/tasmota/tasmota.mapper.d.ts +2 -2
  107. package/dist_ts/integrations/tasmota/tasmota.mapper.js +7 -7
  108. package/dist_ts/integrations/tasmota/tasmota.types.d.ts +41 -2
  109. package/dist_ts/integrations/tuya/tuya.classes.integration.d.ts +4 -1
  110. package/dist_ts/integrations/tuya/tuya.classes.integration.js +79 -110
  111. package/dist_ts/integrations/tuya/tuya.classes.runtime.d.ts +5 -2
  112. package/dist_ts/integrations/tuya/tuya.classes.runtime.js +73 -26
  113. package/dist_ts/integrations/tuya/tuya.mapper.js +2 -2
  114. package/dist_ts/integrations/tuya/tuya.types.d.ts +3 -0
  115. package/dist_ts/integrations/tuya/tuya.validation.d.ts +2 -2
  116. package/dist_ts/integrations/tuya/tuya.validation.js +6 -11
  117. package/dist_ts/integrations/webostv/webostv.classes.client.d.ts +6 -5
  118. package/dist_ts/integrations/webostv/webostv.classes.client.js +147 -76
  119. package/dist_ts/integrations/webostv/webostv.classes.configflow.d.ts +7 -2
  120. package/dist_ts/integrations/webostv/webostv.classes.configflow.js +36 -20
  121. package/dist_ts/integrations/webostv/webostv.classes.integration.d.ts +4 -4
  122. package/dist_ts/integrations/webostv/webostv.classes.integration.js +46 -54
  123. package/dist_ts/integrations/webostv/webostv.mapper.js +5 -5
  124. package/dist_ts/integrations/webostv/webostv.types.d.ts +16 -3
  125. package/dist_ts/integrations/webostv/webostv.types.js +1 -1
  126. package/dist_ts/integrations/webostv/webostv.validation.d.ts +3 -1
  127. package/dist_ts/integrations/webostv/webostv.validation.js +23 -1
  128. package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.classes.configflow.d.ts +6 -1
  129. package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.classes.configflow.js +18 -8
  130. package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.classes.integration.d.ts +4 -1
  131. package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.classes.integration.js +54 -6
  132. package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.classes.runtime.d.ts +12 -6
  133. package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.classes.runtime.js +73 -59
  134. package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.mapper.d.ts +3 -2
  135. package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.mapper.js +17 -12
  136. package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.types.d.ts +31 -2
  137. package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.validation.d.ts +9 -1
  138. package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.validation.js +57 -7
  139. package/dist_ts/integrations/zwave_js/zwave_js.classes.client.d.ts +10 -4
  140. package/dist_ts/integrations/zwave_js/zwave_js.classes.client.js +167 -31
  141. package/dist_ts/integrations/zwave_js/zwave_js.classes.configflow.d.ts +6 -1
  142. package/dist_ts/integrations/zwave_js/zwave_js.classes.configflow.js +57 -35
  143. package/dist_ts/integrations/zwave_js/zwave_js.classes.integration.d.ts +3 -1
  144. package/dist_ts/integrations/zwave_js/zwave_js.classes.integration.js +59 -37
  145. package/dist_ts/integrations/zwave_js/zwave_js.discovery.js +3 -3
  146. package/dist_ts/integrations/zwave_js/zwave_js.mapper.js +41 -24
  147. package/dist_ts/integrations/zwave_js/zwave_js.types.d.ts +25 -3
  148. package/dist_ts/integrations/zwave_js/zwave_js.validation.d.ts +6 -2
  149. package/dist_ts/integrations/zwave_js/zwave_js.validation.js +52 -17
  150. package/dist_ts/plugins.d.ts +2 -1
  151. package/dist_ts/plugins.js +3 -2
  152. package/package.json +10 -10
  153. package/readme.md +25 -6
  154. package/ts/00_commitinfo_data.ts +1 -1
  155. package/ts/core/classes.nativeintegrationowner.ts +5 -0
  156. package/ts/core/classes.runtimemanager.ts +11 -2
  157. package/ts/core/types.ts +2 -0
  158. package/ts/integrations/deconz/deconz.classes.client.ts +348 -252
  159. package/ts/integrations/deconz/deconz.classes.configflow.ts +57 -29
  160. package/ts/integrations/deconz/deconz.classes.integration.ts +69 -37
  161. package/ts/integrations/deconz/deconz.mapper.ts +22 -5
  162. package/ts/integrations/deconz/deconz.types.ts +49 -6
  163. package/ts/integrations/deconz/deconz.validation.ts +177 -4
  164. package/ts/integrations/deconz/readme.md +15 -1
  165. package/ts/integrations/esphome/esphome.classes.client.ts +179 -94
  166. package/ts/integrations/esphome/esphome.classes.configflow.ts +40 -33
  167. package/ts/integrations/esphome/esphome.classes.integration.ts +63 -36
  168. package/ts/integrations/esphome/esphome.discovery.ts +3 -12
  169. package/ts/integrations/esphome/esphome.mapper.ts +9 -7
  170. package/ts/integrations/esphome/esphome.types.ts +35 -6
  171. package/ts/integrations/esphome/esphome.validation.ts +66 -0
  172. package/ts/integrations/esphome/readme.md +20 -6
  173. package/ts/integrations/homeassistant/homeassistant.classes.client.ts +353 -125
  174. package/ts/integrations/homeassistant/homeassistant.classes.integration.ts +35 -18
  175. package/ts/integrations/homeassistant/homeassistant.classes.runtime.ts +50 -63
  176. package/ts/integrations/homeassistant/homeassistant.types.ts +17 -1
  177. package/ts/integrations/homeassistant/readme.md +22 -6
  178. package/ts/integrations/hue/hue.classes.client.ts +288 -55
  179. package/ts/integrations/hue/hue.classes.configflow.ts +68 -25
  180. package/ts/integrations/hue/hue.classes.integration.ts +75 -54
  181. package/ts/integrations/hue/hue.mapper.ts +8 -5
  182. package/ts/integrations/hue/hue.types.ts +37 -1
  183. package/ts/integrations/hue/hue.validation.ts +102 -13
  184. package/ts/integrations/hue/readme.md +49 -12
  185. package/ts/integrations/mqtt/mqtt.classes.client.ts +136 -19
  186. package/ts/integrations/mqtt/mqtt.classes.configflow.ts +45 -32
  187. package/ts/integrations/mqtt/mqtt.classes.integration.ts +99 -38
  188. package/ts/integrations/mqtt/mqtt.classes.session.ts +118 -0
  189. package/ts/integrations/mqtt/mqtt.mapper.ts +43 -1
  190. package/ts/integrations/mqtt/mqtt.types.ts +52 -0
  191. package/ts/integrations/mqtt/mqtt.validation.ts +157 -0
  192. package/ts/integrations/mqtt/readme.md +12 -3
  193. package/ts/integrations/samsungtv/readme.md +11 -5
  194. package/ts/integrations/samsungtv/samsungtv.classes.client.ts +40 -15
  195. package/ts/integrations/samsungtv/samsungtv.classes.configflow.ts +21 -6
  196. package/ts/integrations/samsungtv/samsungtv.classes.integration.ts +53 -38
  197. package/ts/integrations/samsungtv/samsungtv.types.ts +4 -1
  198. package/ts/integrations/shelly/readme.md +22 -7
  199. package/ts/integrations/shelly/shelly.classes.client.ts +386 -269
  200. package/ts/integrations/shelly/shelly.classes.configflow.ts +43 -38
  201. package/ts/integrations/shelly/shelly.classes.integration.ts +92 -111
  202. package/ts/integrations/shelly/shelly.classes.subscription.ts +24 -1
  203. package/ts/integrations/shelly/shelly.mapper.ts +19 -20
  204. package/ts/integrations/shelly/shelly.rpc.ts +2 -0
  205. package/ts/integrations/shelly/shelly.types.ts +50 -2
  206. package/ts/integrations/shelly/shelly.validation.ts +76 -0
  207. package/ts/integrations/sonos/readme.md +117 -38
  208. package/ts/integrations/sonos/sonos.classes.client.ts +392 -123
  209. package/ts/integrations/sonos/sonos.classes.configflow.ts +25 -28
  210. package/ts/integrations/sonos/sonos.classes.integration.ts +211 -186
  211. package/ts/integrations/sonos/sonos.config.ts +24 -2
  212. package/ts/integrations/sonos/sonos.mapper.ts +13 -8
  213. package/ts/integrations/sonos/sonos.matchers.ts +2 -2
  214. package/ts/integrations/sonos/sonos.types.ts +30 -1
  215. package/ts/integrations/tasmota/readme.md +16 -5
  216. package/ts/integrations/tasmota/tasmota.classes.integration.ts +24 -6
  217. package/ts/integrations/tasmota/tasmota.classes.runtime.ts +112 -43
  218. package/ts/integrations/tasmota/tasmota.config.ts +17 -0
  219. package/ts/integrations/tasmota/tasmota.credentials.ts +92 -0
  220. package/ts/integrations/tasmota/tasmota.discovery.ts +87 -24
  221. package/ts/integrations/tasmota/tasmota.mapper.ts +6 -6
  222. package/ts/integrations/tasmota/tasmota.types.ts +43 -2
  223. package/ts/integrations/tuya/readme.md +52 -10
  224. package/ts/integrations/tuya/tuya.classes.integration.ts +69 -98
  225. package/ts/integrations/tuya/tuya.classes.runtime.ts +70 -24
  226. package/ts/integrations/tuya/tuya.mapper.ts +1 -1
  227. package/ts/integrations/tuya/tuya.types.ts +4 -0
  228. package/ts/integrations/tuya/tuya.validation.ts +5 -8
  229. package/ts/integrations/webostv/readme.md +115 -22
  230. package/ts/integrations/webostv/webostv.classes.client.ts +123 -50
  231. package/ts/integrations/webostv/webostv.classes.configflow.ts +35 -15
  232. package/ts/integrations/webostv/webostv.classes.integration.ts +44 -37
  233. package/ts/integrations/webostv/webostv.mapper.ts +4 -4
  234. package/ts/integrations/webostv/webostv.types.ts +18 -3
  235. package/ts/integrations/webostv/webostv.validation.ts +22 -1
  236. package/ts/integrations/zigbee2mqtt/readme.md +22 -27
  237. package/ts/integrations/zigbee2mqtt/zigbee2mqtt.classes.configflow.ts +16 -7
  238. package/ts/integrations/zigbee2mqtt/zigbee2mqtt.classes.integration.ts +53 -6
  239. package/ts/integrations/zigbee2mqtt/zigbee2mqtt.classes.runtime.ts +62 -49
  240. package/ts/integrations/zigbee2mqtt/zigbee2mqtt.mapper.ts +17 -11
  241. package/ts/integrations/zigbee2mqtt/zigbee2mqtt.types.ts +35 -2
  242. package/ts/integrations/zigbee2mqtt/zigbee2mqtt.validation.ts +53 -7
  243. package/ts/integrations/zwave_js/readme.md +42 -9
  244. package/ts/integrations/zwave_js/zwave_js.classes.client.ts +155 -31
  245. package/ts/integrations/zwave_js/zwave_js.classes.configflow.ts +55 -31
  246. package/ts/integrations/zwave_js/zwave_js.classes.integration.ts +57 -35
  247. package/ts/integrations/zwave_js/zwave_js.discovery.ts +2 -2
  248. package/ts/integrations/zwave_js/zwave_js.mapper.ts +37 -23
  249. package/ts/integrations/zwave_js/zwave_js.types.ts +26 -3
  250. package/ts/integrations/zwave_js/zwave_js.validation.ts +52 -18
  251. package/ts/plugins.ts +2 -1
@@ -1,48 +1,76 @@
1
+ import { NativeIntegrationOwner, NativeIntegrationScope } from '../../core/classes.nativeintegrationowner.js';
1
2
  import type { IConfigFlow, IConfigFlowContext, IConfigFlowStep, IDiscoveryCandidate } from '../../core/types.js';
2
- import { DeconzClient, DeconzRequestError } from './deconz.classes.client.js';
3
- import type { IDeconzConfig } from './deconz.types.js';
3
+ import { DeconzClient, DeconzRequestError, DeconzWriteError } from './deconz.classes.client.js';
4
+ import type { IDeconzConfig, IDeconzIntegrationOptions } from './deconz.types.js';
5
+ import { captureDeconzOptions, deconzBridgeId, deconzData, deconzText, normalizeDeconzConfig } from './deconz.validation.js';
4
6
 
5
7
  export class DeconzConfigFlow implements IConfigFlow<IDeconzConfig> {
8
+ readonly #options: Readonly<IDeconzIntegrationOptions>;
9
+ readonly #owner: NativeIntegrationOwner;
10
+ readonly #scope: NativeIntegrationScope;
11
+ readonly #ownsOwner: boolean;
12
+ #terminal = false;
13
+ #submitted = false;
14
+
15
+ constructor(optionsArg: IDeconzIntegrationOptions = {}, ownerArg?: NativeIntegrationOwner) {
16
+ this.#options = captureDeconzOptions(optionsArg);
17
+ this.#ownsOwner = !ownerArg;
18
+ this.#owner = ownerArg ?? new NativeIntegrationOwner('deconz', { homeId: this.#options.homeId, capacity: this.#options.capacity });
19
+ this.#scope = this.#owner.createFlowScope();
20
+ }
21
+
6
22
  public async start(candidateArg: IDiscoveryCandidate, contextArg: IConfigFlowContext): Promise<IConfigFlowStep<IDeconzConfig>> {
7
- void contextArg;
23
+ const context = this.#scope.admit(contextArg);
24
+ if (this.#terminal) throw new Error('deCONZ config flow is closed.');
25
+ const candidate = deconzData(candidateArg, ['id', 'source', 'host', 'port', 'name', 'integrationDomain', 'manufacturer', 'model', 'serialNumber', 'macAddress', 'metadata']);
26
+ // Discovery reports a selection hint; only current gateway reads can check that reported identity.
27
+ const bridgeId = deconzBridgeId(candidate.id);
28
+ const host = deconzText(candidate.host, 'discovered host');
29
+ const port = candidate.port;
30
+ if (port !== undefined && typeof port !== 'number') throw new TypeError('Invalid deCONZ discovered port.');
8
31
  const submit = async (valuesArg: Record<string, unknown>): Promise<IConfigFlowStep<IDeconzConfig>> => {
9
- let client: DeconzClient | undefined;
32
+ if (this.#terminal || this.#submitted) return { kind: 'error', title: 'deCONZ flow closed', error: 'deCONZ config flow is closed or already submitting.' };
33
+ this.#submitted = true;
34
+ let effectAccepted = false;
35
+ let capturedValues: Record<string, unknown> | undefined;
10
36
  try {
11
- const host = valuesArg.host ?? candidateArg.host;
12
- if (typeof host !== 'string' || !host) throw new Error('Host is required.');
13
- if (valuesArg.apiKey !== undefined && typeof valuesArg.apiKey !== 'string') throw new Error('API key must be a string.');
14
- if (valuesArg.port !== undefined && typeof valuesArg.port !== 'number') throw new Error('Port must be a number.');
15
- const protocol = valuesArg.protocol ?? 'http';
16
- if (protocol !== 'http' && protocol !== 'https') throw new Error('Protocol must be http or https.');
17
- const config: IDeconzConfig = { host, port: valuesArg.port as number | undefined ?? candidateArg.port, protocol, apiKey: typeof valuesArg.apiKey === 'string' && valuesArg.apiKey ? valuesArg.apiKey : undefined };
18
- client = new DeconzClient(config);
19
- if (!config.apiKey) {
20
- config.apiKey = await client.acquireApiKey();
21
- await client.destroy();
22
- client = new DeconzClient(config);
23
- }
24
- const snapshot = await client.getSnapshot();
25
- if (candidateArg.id && snapshot.config?.bridgeid && candidateArg.id.replace(/[^a-f0-9]/gi, '').toUpperCase() !== snapshot.config.bridgeid.toUpperCase()) {
26
- throw new Error('deCONZ gateway identity does not match discovery.');
27
- }
28
- config.bridgeId = snapshot.config?.bridgeid ?? snapshot.config?.uuid;
29
- return { kind: 'done', title: 'deCONZ connected', config };
37
+ const values = deconzData(valuesArg, ['host', 'port', 'protocol', 'credentialReference', 'apiKey']);
38
+ capturedValues = Object.freeze(values);
39
+ const config = normalizeDeconzConfig({ homeId: context.homeId, host: values.host ?? host, ...(values.port !== undefined || port !== undefined ? { port: values.port ?? port } : {}), protocol: values.protocol ?? 'http', bridgeId, credentialReference: deconzText(values.credentialReference, 'credential reference') });
40
+ const suppliedKey = values.apiKey === undefined || values.apiKey === '' ? undefined : deconzText(values.apiKey, 'pairing credential', 256);
41
+ const result = await this.#scope.run(context, context.homeId, async (lease) => {
42
+ const client = new DeconzClient(config, lease.context, this.#options, this.#owner);
43
+ let cleanupStarted = false;
44
+ lease.own(() => { if (cleanupStarted) return client.retryCleanup(); cleanupStarted = true; return client.destroy(); });
45
+ if (!client.canCreateCredential) throw new Error('deCONZ flow requires credential creation by the selected provider or encrypted store.');
46
+ const key = suppliedKey ?? await client.acquireApiKey(undefined, lease.signal);
47
+ effectAccepted = !suppliedKey;
48
+ await client.createCredential(key, lease.signal);
49
+ lease.assertOpen();
50
+ return { kind: 'done', title: 'deCONZ connected', config } as IConfigFlowStep<IDeconzConfig>;
51
+ });
52
+ this.#terminal = true;
53
+ await this.#scope.close();
54
+ return result;
30
55
  } catch (error) {
31
- if (error instanceof DeconzRequestError && error.apiErrorType === 101) {
32
- return { kind: 'wait', title: 'Unlock deCONZ', description: 'In Phoscon, open Gateway → Advanced → Authenticate app, then retry within 60 seconds.', submit: () => submit(valuesArg) };
56
+ if (error instanceof DeconzRequestError && error.apiErrorType === 101 && !effectAccepted) {
57
+ return { kind: 'wait', title: 'Unlock deCONZ', description: 'In Phoscon, open Gateway → Advanced → Authenticate app, then retry within 60 seconds.', submit: () => submit(capturedValues!) };
33
58
  }
34
- return { kind: 'error', title: 'deCONZ connection failed', error: error instanceof Error ? error.message : 'deCONZ setup failed.' };
35
- } finally { await client?.destroy(); }
59
+ if (effectAccepted || error instanceof DeconzWriteError) { this.#terminal = true; await this.#scope.close(); }
60
+ return { kind: 'error', title: 'deCONZ connection failed', error: effectAccepted || error instanceof DeconzWriteError ? 'deCONZ pairing was dispatched; do not replay pairing.' : 'deCONZ setup failed.' };
61
+ } finally { this.#submitted = false; }
36
62
  };
37
63
  return {
38
- kind: 'form', title: 'Connect deCONZ Gateway',
39
- description: 'Provide a gateway API key, or leave it empty to pair after unlocking Authenticate app in Phoscon.',
64
+ kind: 'form', title: 'Connect deCONZ Gateway', description: 'Select a credential reference. A new API key is validated and stored by the host credential provider or encrypted config store.',
40
65
  fields: [
41
66
  { name: 'host', label: 'Host', type: 'text', required: true },
42
67
  { name: 'port', label: 'Port', type: 'number' },
43
68
  { name: 'protocol', label: 'Protocol', type: 'select', options: [{ label: 'HTTP', value: 'http' }, { label: 'HTTPS', value: 'https' }] },
69
+ { name: 'credentialReference', label: 'Credential reference', type: 'text', required: true },
44
70
  { name: 'apiKey', label: 'API key', type: 'password' },
45
71
  ], submit,
46
72
  };
47
73
  }
74
+ public destroy(): Promise<void> { this.#terminal = true; return this.#ownsOwner ? this.#owner.close() : this.#scope.close(); }
75
+ public retryCleanup(): Promise<void> { return this.#ownsOwner ? this.#owner.retryCleanup() : this.#scope.retryCleanup(); }
48
76
  }
@@ -1,11 +1,13 @@
1
1
  import * as plugins from '../../plugins.js';
2
2
  import { BaseIntegration } from '../../core/classes.baseintegration.js';
3
3
  import type { IDeviceFeatureWriteRequest, IIntegrationEntity, IIntegrationRuntime, IIntegrationSetupContext, IServiceCallRequest, IServiceCallResult } from '../../core/types.js';
4
- import { DeconzClient } from './deconz.classes.client.js';
4
+ import { DeconzClient, DeconzWriteError, type IDeconzClientOperation } from './deconz.classes.client.js';
5
+ import { NativeIntegrationOwner, type INativeSetupLease } from '../../core/classes.nativeintegrationowner.js';
6
+ import { captureDeconzOptions, deconzData, normalizeDeconzConfig } from './deconz.validation.js';
5
7
  import { DeconzConfigFlow } from './deconz.classes.configflow.js';
6
8
  import { createDeconzDiscoveryDescriptor } from './deconz.discovery.js';
7
9
  import { DeconzMapper } from './deconz.mapper.js';
8
- import type { IDeconzConfig, IDeconzGroupActionPatch, IDeconzLightStatePatch, IDeconzSensorConfigPatch, IDeconzSensorStatePatch, IDeconzSnapshot } from './deconz.types.js';
10
+ import type { IDeconzConfig, IDeconzGroupActionPatch, IDeconzIntegrationOptions, IDeconzLightStatePatch, IDeconzSensorConfigPatch, IDeconzSensorStatePatch, IDeconzSnapshot } from './deconz.types.js';
9
11
 
10
12
  interface IDeconzServiceTarget {
11
13
  entity: IIntegrationEntity;
@@ -17,13 +19,13 @@ interface IDeconzServiceTarget {
17
19
  }
18
20
 
19
21
  export class DeconzIntegration extends BaseIntegration<IDeconzConfig> {
20
- private readonly clients = new Set<DeconzClient>();
21
- private destroyed = false;
22
+ readonly #owner: NativeIntegrationOwner;
23
+ readonly #options: Readonly<IDeconzIntegrationOptions>;
22
24
  public readonly domain = 'deconz';
23
25
  public readonly displayName = 'deCONZ';
24
26
  public readonly status = 'control-runtime' as const;
25
27
  public readonly discoveryDescriptor = createDeconzDiscoveryDescriptor();
26
- public readonly configFlow = new DeconzConfigFlow();
28
+ public readonly configFlow: DeconzConfigFlow;
27
29
  public readonly metadata = {
28
30
  source: 'home-assistant/core',
29
31
  upstreamPath: 'homeassistant/components/deconz',
@@ -36,46 +38,80 @@ export class DeconzIntegration extends BaseIntegration<IDeconzConfig> {
36
38
  restDocumentation: 'https://dresden-elektronik.github.io/deconz-rest-doc/',
37
39
  };
38
40
 
39
- public async setup(configArg: IDeconzConfig, contextArg: IIntegrationSetupContext): Promise<IIntegrationRuntime> {
40
- if (this.destroyed) throw new Error('deCONZ integration has been destroyed.');
41
- const client = new DeconzClient(configArg, contextArg.abortSignal);
42
- this.clients.add(client);
43
- try {
44
- await client.getSnapshot(contextArg.abortSignal);
45
- if (this.destroyed || !client.isHealthy) throw new Error('deCONZ setup cancelled.');
46
- const runtime = new DeconzRuntime(client, () => this.clients.delete(client));
47
- return runtime;
48
- } catch (error) { await client.destroy(); this.clients.delete(client); throw error; }
41
+ constructor(optionsArg: IDeconzIntegrationOptions = {}) {
42
+ super();
43
+ this.#options = captureDeconzOptions(optionsArg);
44
+ this.#owner = new NativeIntegrationOwner('deconz', { homeId: this.#options.homeId, capacity: this.#options.capacity });
45
+ this.configFlow = new DeconzConfigFlow(this.#options, this.#owner);
49
46
  }
50
47
 
51
- public async destroy(): Promise<void> {
52
- this.destroyed = true;
53
- const results = await Promise.allSettled([...this.clients].map(async (clientArg) => { await clientArg.destroy(); this.clients.delete(clientArg); }));
54
- const failures = results.filter((resultArg): resultArg is PromiseRejectedResult => resultArg.status === 'rejected');
55
- if (failures.length) throw new AggregateError(failures.map((failureArg) => failureArg.reason), 'deCONZ runtime cleanup failed.');
48
+ public async setup(configArg: IDeconzConfig, contextArg: IIntegrationSetupContext): Promise<IIntegrationRuntime> {
49
+ const config = normalizeDeconzConfig(configArg);
50
+ return this.#owner.runSetup(contextArg, config.homeId, async (lease) => {
51
+ const client = new DeconzClient(config, lease.context, this.#options, this.#owner);
52
+ let cleanupStarted = false;
53
+ lease.own(() => { if (cleanupStarted) return client.retryCleanup(); cleanupStarted = true; return client.destroy(); });
54
+ await client.getSnapshot(lease.signal);
55
+ lease.assertOpen();
56
+ if (!client.isHealthy) throw new Error('deCONZ setup cancelled.');
57
+ lease.promoteToRuntime();
58
+ return new DeconzRuntime(client, lease);
59
+ });
56
60
  }
61
+
62
+ public destroy(): Promise<void> { return this.#owner.close(); }
63
+ public retryCleanup(): Promise<void> { return this.#owner.retryCleanup(); }
64
+
57
65
  }
58
66
 
59
67
  export class HomeAssistantDeconzIntegration extends DeconzIntegration {}
60
68
 
61
69
  class DeconzRuntime implements IIntegrationRuntime {
62
- public domain = 'deconz';
63
-
64
- constructor(private readonly client: DeconzClient, private readonly onDestroyed: () => void) {}
65
-
70
+ public readonly domain = 'deconz';
71
+ constructor(private readonly client: DeconzClient, private readonly lease: INativeSetupLease) {}
66
72
  public isHealthy(): boolean { return this.client.isHealthy; }
67
-
68
- public async devices(abortSignalArg?: AbortSignal): Promise<plugins.shxInterfaces.data.IDeviceDefinition[]> {
69
- return DeconzMapper.toDevices(await this.client.getSnapshot(abortSignalArg));
73
+ public async devices(signalArg?: AbortSignal): Promise<plugins.shxInterfaces.data.IDeviceDefinition[]> { return DeconzMapper.toDevices(await this.client.getSnapshot(signalArg)); }
74
+ public async entities(): Promise<IIntegrationEntity[]> { return DeconzMapper.toEntities(await this.client.getSnapshot()); }
75
+ public async subscribe(handlerArg: Parameters<NonNullable<IIntegrationRuntime['subscribe']>>[0], signalArg?: AbortSignal): Promise<() => Promise<void>> {
76
+ return this.client.subscribeToEvents((eventArg) => handlerArg(DeconzMapper.toIntegrationEvent(eventArg)), signalArg, (errorArg) => handlerArg({ type: 'error', integrationDomain: 'deconz', data: { message: errorArg.message }, timestamp: Date.now() }));
70
77
  }
71
-
72
- public async entities(): Promise<IIntegrationEntity[]> {
73
- return DeconzMapper.toEntities(await this.client.getSnapshot());
78
+ public async writeFeature(requestArg: IDeviceFeatureWriteRequest, signalArg?: AbortSignal): Promise<IServiceCallResult> {
79
+ try {
80
+ const request = deconzData(requestArg, ['device', 'feature', 'value']);
81
+ const device = deconzData(request.device, ['id', 'integrationDomain', 'name', 'room', 'protocol', 'manufacturer', 'model', 'online', 'features', 'state', 'metadata']);
82
+ const feature = deconzData(request.feature, ['id', 'capability', 'name', 'readable', 'writable', 'unit']);
83
+ if (typeof device.id !== 'string' || typeof device.integrationDomain !== 'string' || typeof feature.id !== 'string' || typeof feature.capability !== 'string' || !['boolean', 'number', 'string'].includes(typeof request.value)) throw new Error('Invalid deCONZ feature request.');
84
+ const captured = { device: { id: device.id, integrationDomain: device.integrationDomain }, feature: { id: feature.id, capability: feature.capability }, value: request.value } as IDeviceFeatureWriteRequest;
85
+ const result = await this.client.execute((session) => new DeconzRuntimeCommands(session).writeFeature(captured, signalArg), signalArg);
86
+ return { ...result.result, ...(result.observation ? { data: result.observation } : {}) };
87
+ } catch (error) { return this.failure(error); }
74
88
  }
75
-
76
- public async subscribe(handlerArg: Parameters<NonNullable<IIntegrationRuntime['subscribe']>>[0], abortSignalArg?: AbortSignal): Promise<() => Promise<void>> {
77
- return this.client.subscribeToEvents((eventArg) => handlerArg(DeconzMapper.toIntegrationEvent(eventArg)), abortSignalArg, (errorArg) => handlerArg({ type: 'error', integrationDomain: 'deconz', data: { message: errorArg.message }, timestamp: Date.now() }));
89
+ public async callService(requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
90
+ try {
91
+ const request = deconzData(requestArg, ['domain', 'service', 'target', 'data']);
92
+ if (typeof request.domain !== 'string' || typeof request.service !== 'string') throw new Error('Invalid deCONZ service request.');
93
+ const target = deconzData(request.target, ['entityId', 'deviceId']);
94
+ if (Object.values(target).some((value) => typeof value !== 'string' || !value)) throw new Error('Invalid deCONZ service target.');
95
+ const fields = request.domain === 'light' ? request.service === 'turn_on' ? ['brightness', 'brightness_pct', 'percentage', 'transition', 'color_temp', 'color_temp_mired', 'xy_color'] : request.service === 'set_percentage' ? ['percentage'] : [] : request.domain === 'cover' ? ['position', 'percentage'] : ['sensor', 'binary_sensor', 'climate'].includes(request.domain) ? ['field', 'attribute', 'value', 'temperature', 'target_temperature'] : [];
96
+ const data = request.data === undefined ? undefined : deconzData(request.data, fields);
97
+ if (data) for (const [key, value] of Object.entries(data)) {
98
+ if (key === 'xy_color') {
99
+ if (!Array.isArray(value) || value.length !== 2 || Reflect.ownKeys(value).some((entry) => !['0', '1', 'length'].includes(String(entry)))) throw new Error('Invalid deCONZ color request.');
100
+ data[key] = ['0', '1'].map((entry) => { const descriptor = Object.getOwnPropertyDescriptor(value, entry); if (!descriptor || !('value' in descriptor) || typeof descriptor.value !== 'number') throw new Error('Invalid deCONZ color request.'); return descriptor.value as number; });
101
+ } else if (!['string', 'number', 'boolean'].includes(typeof value)) throw new Error('Invalid deCONZ service data.');
102
+ }
103
+ const captured = { domain: request.domain, service: request.service, target, data } as IServiceCallRequest;
104
+ const result = await this.client.execute((session) => new DeconzRuntimeCommands(session).callService(captured));
105
+ return { ...result.result, ...(result.observation ? { data: result.observation } : {}) };
106
+ } catch (error) { return this.failure(error); }
78
107
  }
108
+ public destroy(): Promise<void> { return this.lease.close(); }
109
+ private failure(errorArg: unknown): IServiceCallResult { return { success: false, error: 'deCONZ command failed.', data: { effectOutcome: errorArg instanceof DeconzWriteError ? 'indeterminate' : 'not-sent', causalityConfirmed: false, physicalCompletion: 'unconfirmed' } }; }
110
+ }
111
+
112
+ class DeconzRuntimeCommands {
113
+ public readonly domain = 'deconz';
114
+ constructor(private readonly client: IDeconzClientOperation) {}
79
115
 
80
116
  public async writeFeature(requestArg: IDeviceFeatureWriteRequest, abortSignalArg?: AbortSignal): Promise<IServiceCallResult> {
81
117
  if (!this.client.isHealthy) return { success: false, error: 'deCONZ runtime is not healthy.' };
@@ -140,10 +176,6 @@ class DeconzRuntime implements IIntegrationRuntime {
140
176
  return { success: false, error: `Unsupported deCONZ service domain: ${requestArg.domain}` };
141
177
  }
142
178
 
143
- public async destroy(): Promise<void> {
144
- await this.client.destroy();
145
- this.onDestroyed();
146
- }
147
179
 
148
180
  private async callLightService(targetArg: IDeconzServiceTarget, requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
149
181
  if (targetArg.resource !== 'lights' && targetArg.resource !== 'groups') {
@@ -99,6 +99,11 @@ export class DeconzMapper {
99
99
  devices.push(this.sensorToDevice(sensorId, sensor, updatedAt));
100
100
  }
101
101
 
102
+ if (snapshotArg.homeId) for (const device of devices) {
103
+ const resource = typeof device.metadata?.deconzResource === 'string' ? device.metadata.deconzResource : 'gateway';
104
+ const id = typeof device.metadata?.deconzId === 'string' ? device.metadata.deconzId : bridgeId;
105
+ device.id = this.canonicalDeviceId(snapshotArg, resource, id);
106
+ }
102
107
  if (snapshotArg.config?.rfconnected === false) {
103
108
  return devices.map((deviceArg) => ({ ...deviceArg, online: false, state: deviceArg.state.map((stateArg) => stateArg.featureId === 'connectivity' ? { ...stateArg, value: 'offline' } : stateArg) }));
104
109
  }
@@ -138,9 +143,21 @@ export class DeconzMapper {
138
143
  }
139
144
  }
140
145
 
146
+ if (snapshotArg.homeId) for (const entity of entities) {
147
+ const resource = String(entity.attributes?.deconzResource);
148
+ const id = String(entity.attributes?.deconzId);
149
+ entity.deviceId = this.canonicalDeviceId(snapshotArg, resource, id);
150
+ const field = entity.attributes?.deconzStateKey ?? entity.attributes?.deconzConfigKey ?? entity.platform;
151
+ entity.id = entity.uniqueId = `${entity.deviceId}:${entity.platform}:${encodeURIComponent(String(field))}`;
152
+ }
141
153
  return snapshotArg.config?.rfconnected === false ? entities.map((entityArg) => ({ ...entityArg, available: false })) : entities;
142
154
  }
143
155
 
156
+ private static canonicalDeviceId(snapshotArg: IDeconzSnapshot, resourceArg: string, idArg: string): string {
157
+ const item = resourceArg === 'lights' ? snapshotArg.lights[idArg] : resourceArg === 'sensors' ? snapshotArg.sensors[idArg] : undefined;
158
+ return ['deconz', snapshotArg.homeId!, this.bridgeId(snapshotArg).toLowerCase(), resourceArg, idArg, item?.uniqueid ?? ''].map((partArg) => encodeURIComponent(partArg)).join(':');
159
+ }
160
+
144
161
  public static toIntegrationEvent(eventArg: IDeconzWebsocketEvent): IIntegrationEvent {
145
162
  return {
146
163
  type: eventArg.e === 'added' ? 'device_added' : eventArg.e === 'deleted' ? 'device_removed' : 'state_changed',
@@ -185,13 +202,13 @@ export class DeconzMapper {
185
202
  ];
186
203
 
187
204
  if (this.isCoverLight(lightArg)) {
188
- features.push({ id: 'position', capability: 'cover', name: 'Position', readable: true, writable: true, unit: '%' });
205
+ features.push({ id: 'position', capability: 'cover', name: 'Position', readable: true, writable: typeof lightArg.state?.lift === 'number', unit: '%' });
189
206
  this.pushDeviceState(state, 'position', this.coverPosition(lightArg), updatedAtArg);
190
207
  } else if (this.isSwitchLight(lightArg)) {
191
- features.push({ id: 'on', capability: 'switch', name: 'Power', readable: true, writable: true });
208
+ features.push({ id: 'on', capability: 'switch', name: 'Power', readable: true, writable: typeof lightArg.state?.on === 'boolean' });
192
209
  this.pushDeviceState(state, 'on', lightArg.state?.on ?? false, updatedAtArg);
193
210
  } else {
194
- features.push({ id: 'on', capability: 'light', name: 'Power', readable: true, writable: true });
211
+ features.push({ id: 'on', capability: 'light', name: 'Power', readable: true, writable: typeof lightArg.state?.on === 'boolean' });
195
212
  this.pushDeviceState(state, 'on', lightArg.state?.on ?? false, updatedAtArg);
196
213
  if (typeof lightArg.state?.bri === 'number') {
197
214
  features.push({ id: 'brightness', capability: 'light', name: 'Brightness', readable: true, writable: true, unit: '%' });
@@ -238,8 +255,8 @@ export class DeconzMapper {
238
255
  online: true,
239
256
  features: [
240
257
  { id: 'connectivity', capability: 'sensor', name: 'Connectivity', readable: true, writable: false },
241
- { id: 'on', capability: 'light', name: 'Power', readable: true, writable: true },
242
- { id: 'brightness', capability: 'light', name: 'Brightness', readable: true, writable: true, unit: '%' },
258
+ { id: 'on', capability: 'light', name: 'Power', readable: true, writable: typeof groupArg.action?.on === 'boolean' },
259
+ ...(typeof groupArg.action?.bri === 'number' ? [{ id: 'brightness', capability: 'light' as const, name: 'Brightness', readable: true, writable: true, unit: '%' }] : []),
243
260
  ],
244
261
  state,
245
262
  metadata: {
@@ -1,3 +1,6 @@
1
+ import type * as plugins from '../../plugins.js';
2
+ import type { IConfigEncryptionKeyProvider } from '../../core/classes.configstore.js';
3
+
1
4
  export type TDeconzProtocol = 'http' | 'https';
2
5
 
3
6
  export type TDeconzResource = 'config' | 'groups' | 'lights' | 'scenes' | 'sensors';
@@ -9,15 +12,52 @@ export type TDeconzCommandMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
9
12
  export type TDeconzStateValue = string | number | boolean | null | undefined | number[] | string[] | Record<string, unknown>;
10
13
 
11
14
  export interface IDeconzConfig {
12
- host?: string;
15
+ homeId: string;
16
+ host: string;
13
17
  port?: number;
14
- apiKey?: string;
18
+ credentialReference: string;
15
19
  protocol?: TDeconzProtocol;
16
- bridgeId?: string;
17
- websocketPort?: number;
20
+ bridgeId: string;
18
21
  enableWebSocket?: boolean;
19
22
  requestTimeoutMs?: number;
20
- snapshot?: IDeconzSnapshot;
23
+ }
24
+
25
+ export interface IDeconzCredentialRequest {
26
+ readonly homeId: string;
27
+ readonly origin: string;
28
+ readonly bridgeId: string;
29
+ readonly credentialReference: string;
30
+ readonly signal: AbortSignal;
31
+ /** Monotonic performance.now() deadline, shared by credential resolution and HTTP dispatch. */
32
+ readonly deadline: number;
33
+ }
34
+
35
+ export interface IDeconzCredentialProvider {
36
+ resolve(requestArg: IDeconzCredentialRequest): Promise<string>;
37
+ /** Create a reference without replacing an existing credential; the host owns this persistence contract. */
38
+ create?(requestArg: IDeconzCredentialRequest, apiKeyArg: string): Promise<void>;
39
+ }
40
+
41
+ export interface IDeconzControlPermission {
42
+ readonly resource: 'config' | 'lights' | 'groups' | 'sensors' | 'scenes';
43
+ readonly resourceId: string;
44
+ readonly fields: readonly string[];
45
+ }
46
+
47
+ export interface IDeconzIntegrationOptions {
48
+ readonly homeId?: string;
49
+ readonly capacity?: number;
50
+ readonly credentialProvider?: IDeconzCredentialProvider;
51
+ readonly controlPermissions?: readonly IDeconzControlPermission[];
52
+ readonly db?: plugins.nosqldb.SmartdataDb;
53
+ readonly keyProvider?: IConfigEncryptionKeyProvider;
54
+ }
55
+
56
+ export interface IDeconzWriteResult {
57
+ readonly effectOutcome: 'accepted';
58
+ readonly observedState: IDeconzSnapshot;
59
+ readonly causalityConfirmed: false;
60
+ readonly physicalCompletion: 'unconfirmed';
21
61
  }
22
62
 
23
63
  export interface IDeconzGatewayConfig {
@@ -58,11 +98,13 @@ export interface IDeconzLightState {
58
98
 
59
99
  export interface IDeconzLight {
60
100
  id?: string;
61
- colorcapabilities?: number;
101
+ colorcapabilities?: number | string[];
102
+ modes?: string[];
62
103
  ctmax?: number;
63
104
  ctmin?: number;
64
105
  etag?: string;
65
106
  hascolor?: boolean;
107
+ effects?: string[];
66
108
  lastannounced?: string;
67
109
  lastseen?: string;
68
110
  manufacturername?: string;
@@ -192,6 +234,7 @@ export interface IDeconzScene {
192
234
  }
193
235
 
194
236
  export interface IDeconzSnapshot {
237
+ homeId?: string;
195
238
  config?: IDeconzGatewayConfig;
196
239
  groups: Record<string, IDeconzGroup>;
197
240
  lights: Record<string, IDeconzLight>;