homebridge-deconz 0.0.6 → 0.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -0
- package/cli/deconz.js +980 -0
- package/config.schema.json +1 -1
- package/lib/Client/ApiError.js +42 -0
- package/lib/{DeconzClient.js → Deconz/ApiClient.js} +82 -158
- package/lib/Deconz/ApiError.js +42 -0
- package/lib/Deconz/ApiResponse.js +54 -0
- package/lib/Deconz/Device.js +100 -0
- package/lib/{DeconzDiscovery.js → Deconz/Discovery.js} +4 -3
- package/lib/Deconz/Resource.js +1206 -0
- package/lib/{DeconzWsClient.js → Deconz/WsClient.js} +59 -44
- package/lib/Deconz/index.js +21 -0
- package/lib/DeconzAccessory/Contact.js +54 -0
- package/lib/DeconzAccessory/Gateway.js +316 -374
- package/lib/DeconzAccessory/Light.js +72 -0
- package/lib/DeconzAccessory/Motion.js +51 -0
- package/lib/DeconzAccessory/Sensor.js +35 -0
- package/lib/DeconzAccessory/Temperature.js +63 -0
- package/lib/DeconzAccessory/Thermostat.js +50 -0
- package/lib/DeconzAccessory/WarningDevice.js +56 -0
- package/lib/DeconzAccessory/WindowCovering.js +47 -0
- package/lib/DeconzAccessory/index.js +216 -0
- package/lib/DeconzPlatform.js +8 -3
- package/lib/DeconzService/AirPressure.js +43 -0
- package/lib/DeconzService/AirQuality.js +20 -10
- package/lib/DeconzService/Alarm.js +16 -9
- package/lib/DeconzService/Battery.js +43 -0
- package/lib/DeconzService/Button.js +12 -2
- package/lib/DeconzService/CarbonMonoxide.js +38 -0
- package/lib/DeconzService/Consumption.js +65 -0
- package/lib/DeconzService/Contact.js +60 -0
- package/lib/DeconzService/Daylight.js +132 -0
- package/lib/DeconzService/DeviceSettings.js +13 -5
- package/lib/DeconzService/Flag.js +52 -0
- package/lib/DeconzService/GatewaySettings.js +8 -58
- package/lib/DeconzService/Humidity.js +37 -0
- package/lib/DeconzService/Leak.js +38 -0
- package/lib/DeconzService/Light.js +376 -0
- package/lib/DeconzService/LightLevel.js +54 -0
- package/lib/DeconzService/LightsResource.js +112 -0
- package/lib/DeconzService/Motion.js +101 -0
- package/lib/DeconzService/Outlet.js +76 -0
- package/lib/DeconzService/Power.js +83 -0
- package/lib/DeconzService/SensorsResource.js +96 -0
- package/lib/DeconzService/Smoke.js +38 -0
- package/lib/DeconzService/Status.js +53 -0
- package/lib/DeconzService/Switch.js +93 -0
- package/lib/DeconzService/Temperature.js +63 -0
- package/lib/DeconzService/Thermostat.js +175 -0
- package/lib/DeconzService/WarningDevice.js +68 -0
- package/lib/DeconzService/WindowCovering.js +139 -0
- package/lib/DeconzService/index.js +94 -0
- package/package.json +7 -4
- package/lib/DeconzAccessory/Device.js +0 -91
- package/lib/DeconzAccessory.js +0 -16
- package/lib/DeconzDevice.js +0 -245
- package/lib/DeconzService/Sensor.js +0 -58
- package/lib/DeconzService.js +0 -43
package/config.schema.json
CHANGED
@@ -69,7 +69,7 @@
|
|
69
69
|
"maximum": 2000
|
70
70
|
},
|
71
71
|
"waitTimeUpdate": {
|
72
|
-
"description": "The time, in milliseconds, to wait for a change from HomeKit to another characteristic for the same light or group, before updating the deCONZ gateway. Default:
|
72
|
+
"description": "The time, in milliseconds, to wait for a change from HomeKit to another characteristic for the same light or group, before updating the deCONZ gateway. Default: 100.",
|
73
73
|
"type": "integer",
|
74
74
|
"minimum": 0,
|
75
75
|
"maximum": 500
|
@@ -0,0 +1,42 @@
|
|
1
|
+
// homebridge-deconz/lib/Deconz/ApiError.js
|
2
|
+
//
|
3
|
+
// Homebridge plug-in for deCONZ.
|
4
|
+
// Copyright © 2018-2022 Erik Baauw. All rights reserved.
|
5
|
+
|
6
|
+
'use strict'
|
7
|
+
|
8
|
+
const homebridgeLib = require('homebridge-lib')
|
9
|
+
|
10
|
+
// API errors that could still cause (part of) the PUT command to be executed.
|
11
|
+
const nonCriticalApiErrorTypes = [
|
12
|
+
6, // parameter not available
|
13
|
+
7, // invalid value for parameter
|
14
|
+
8, // paramater not modifiable
|
15
|
+
201 // paramater not modifiable, device is set to off
|
16
|
+
]
|
17
|
+
|
18
|
+
/** Deconz API error.
|
19
|
+
* @hideconstructor
|
20
|
+
* @extends HttpClient.HttpError
|
21
|
+
* @memberof Deconz
|
22
|
+
*/
|
23
|
+
class ApiError extends homebridgeLib.HttpClient.HttpError {
|
24
|
+
constructor (message, response, type, description) {
|
25
|
+
super(message, response.request, response.statusCode, response.statusMessage)
|
26
|
+
|
27
|
+
/** @member {integer} - The API error type.
|
28
|
+
*/
|
29
|
+
this.type = type
|
30
|
+
|
31
|
+
/** @member {string} - The API error description.
|
32
|
+
*/
|
33
|
+
this.description = description
|
34
|
+
|
35
|
+
/** @member {boolean} - Indication that the request might still succeed
|
36
|
+
* for other attributes.
|
37
|
+
*/
|
38
|
+
this.nonCritical = nonCriticalApiErrorTypes.includes(type)
|
39
|
+
}
|
40
|
+
}
|
41
|
+
|
42
|
+
module.exports = ApiError
|
@@ -1,4 +1,4 @@
|
|
1
|
-
// homebridge-deconz/lib/
|
1
|
+
// homebridge-deconz/lib/Deconz/ApiClient.js
|
2
2
|
//
|
3
3
|
// Homebridge plug-in for deCONZ.
|
4
4
|
// Copyright © 2018-2022 Erik Baauw. All rights reserved.
|
@@ -9,15 +9,9 @@ const events = require('events')
|
|
9
9
|
const homebridgeLib = require('homebridge-lib')
|
10
10
|
const os = require('os')
|
11
11
|
|
12
|
-
|
13
|
-
const nonCriticalApiErrorTypes = [
|
14
|
-
6, // parameter not available
|
15
|
-
7, // invalid value for parameter
|
16
|
-
8, // paramater not modifiable
|
17
|
-
201 // paramater not modifiable, device is set to off
|
18
|
-
]
|
12
|
+
const Deconz = require('../Deconz')
|
19
13
|
|
20
|
-
// Estmate the number of Zigbee messages resulting from
|
14
|
+
// Estmate the number of Zigbee messages resulting from PUT body.
|
21
15
|
function numberOfZigbeeMessages (body = {}) {
|
22
16
|
let n = 0
|
23
17
|
if (Object.keys(body).includes('on')) {
|
@@ -41,82 +35,37 @@ function numberOfZigbeeMessages (body = {}) {
|
|
41
35
|
return n === 0 ? 1 : n
|
42
36
|
}
|
43
37
|
|
44
|
-
/**
|
45
|
-
* @hideconstructor
|
46
|
-
* @extends HttpClient.HttpError
|
47
|
-
* @memberof DeconzClient
|
48
|
-
*/
|
49
|
-
class DeconzError extends homebridgeLib.HttpClient.HttpError {
|
50
|
-
constructor (message, response, type, description) {
|
51
|
-
super(message, response.request, response.statusCode, response.statusMessage)
|
52
|
-
|
53
|
-
/** @member {integer} - The API error type.
|
54
|
-
*/
|
55
|
-
this.type = type
|
56
|
-
|
57
|
-
/** @member {string} - The API error description.
|
58
|
-
*/
|
59
|
-
this.description = description
|
60
|
-
|
61
|
-
/** @member {boolean} - Indication that the request might still succeed
|
62
|
-
* for other attributes.
|
63
|
-
*/
|
64
|
-
this.nonCritical = nonCriticalApiErrorTypes.includes(type)
|
65
|
-
}
|
66
|
-
}
|
67
|
-
|
68
|
-
/** Deconz API response.
|
69
|
-
* @hideconstructor
|
70
|
-
* @extends HttpClient.HttpResponse
|
71
|
-
* @memberof DeconzClient
|
72
|
-
*/
|
73
|
-
class DeconzResponse extends homebridgeLib.HttpClient.HttpResponse {
|
74
|
-
constructor (response) {
|
75
|
-
super(
|
76
|
-
response.request, response.statusCode, response.statusMessage,
|
77
|
-
response.headers, response.body, response.parsedBody
|
78
|
-
)
|
79
|
-
|
80
|
-
/** @member {object} - An object with the `"success"` API responses.
|
81
|
-
*/
|
82
|
-
this.success = {}
|
83
|
-
|
84
|
-
/** @member {DeconzClient.DeconzError[]} - A list of `"error"` API responses.
|
85
|
-
*/
|
86
|
-
this.errors = []
|
87
|
-
|
88
|
-
if (Array.isArray(response.body)) {
|
89
|
-
for (const id in response.body) {
|
90
|
-
const e = response.body[id].error
|
91
|
-
if (e != null && typeof e === 'object') {
|
92
|
-
this.errors.push(new DeconzError(
|
93
|
-
`api error ${e.type}: ${e.description}`,
|
94
|
-
response, e.type, e.description
|
95
|
-
))
|
96
|
-
}
|
97
|
-
const s = response.body[id].success
|
98
|
-
if (s != null && typeof s === 'object') {
|
99
|
-
for (const path of Object.keys(s)) {
|
100
|
-
const a = path.split('/')
|
101
|
-
const key = a[a.length - 1]
|
102
|
-
this.success[key] = s[path]
|
103
|
-
}
|
104
|
-
}
|
105
|
-
}
|
106
|
-
}
|
107
|
-
}
|
108
|
-
}
|
109
|
-
|
110
|
-
/** REST API client for deCONZ gateway.
|
38
|
+
/** REST API client for a deCONZ gateway.
|
111
39
|
*
|
112
40
|
* See the [deCONZ API](https://dresden-elektronik.github.io/deconz-rest-doc/)
|
113
41
|
* documentation for a better understanding of the API.
|
114
42
|
* @extends HttpClient
|
43
|
+
* @memberof Deconz
|
115
44
|
*/
|
116
|
-
class
|
117
|
-
|
118
|
-
|
45
|
+
class ApiClient extends homebridgeLib.HttpClient {
|
46
|
+
/** Events reported through `buttonevent`.
|
47
|
+
* @type {Object<string, integer>}
|
48
|
+
*/
|
49
|
+
static get buttonEvent () {
|
50
|
+
return {
|
51
|
+
PRESS: 0,
|
52
|
+
HOLD: 1,
|
53
|
+
SHORT_RELEASE: 2,
|
54
|
+
LONG_RELEASE: 3,
|
55
|
+
DOUBLE_PRESS: 4,
|
56
|
+
TRIPLE_PRESS: 5,
|
57
|
+
QUADRUPLE_PRESS: 6,
|
58
|
+
SHAKE: 7,
|
59
|
+
DROP: 8,
|
60
|
+
TILT: 9
|
61
|
+
}
|
62
|
+
}
|
119
63
|
|
64
|
+
/** Convert date as reported by deCONZ to human readable string.
|
65
|
+
* @param {string} date - The ISO-8601 date string.
|
66
|
+
* @param {boolean} [utc=true] - Treat date as UTC, even with missing `Z`.
|
67
|
+
* @param {string} date - The human readable date string.
|
68
|
+
*/
|
120
69
|
static dateToString (date, utc = true) {
|
121
70
|
if (date == null || date === 'none') {
|
122
71
|
return 'n/a'
|
@@ -127,33 +76,30 @@ class DeconzClient extends homebridgeLib.HttpClient {
|
|
127
76
|
return String(new Date(date)).slice(0, 24)
|
128
77
|
}
|
129
78
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
o.cluster = a[2]
|
139
|
-
}
|
140
|
-
return o
|
79
|
+
/** Convert `lightlevel` to lux.
|
80
|
+
* @param {integer} lightLevel - The `lightlevel` as reported by deCONZ.
|
81
|
+
* @return {integer} lux - The value in lux.
|
82
|
+
*/
|
83
|
+
static lightLevelToLux (v) {
|
84
|
+
let lux = v ? Math.pow(10, (v - 1) / 10000) : 0.0001
|
85
|
+
lux = Math.round(lux * 10000) / 10000
|
86
|
+
return Math.max(0.0001, Math.min(lux, 100000))
|
141
87
|
}
|
142
88
|
|
143
|
-
/** Create a new instance of a
|
89
|
+
/** Create a new instance of a Deconz.Client.
|
144
90
|
*
|
145
91
|
* The caller is expected to verify that the given host is a reachable
|
146
92
|
* deCONZ gateway, by calling
|
147
93
|
* {@link DeconzDiscovery#config DeconzDiscovery#config()} and passing the
|
148
94
|
* response as `params.config`.<br>
|
149
|
-
* The caller is expected to persist the
|
150
|
-
*
|
151
|
-
* If no API key is known {@link
|
95
|
+
* The caller is expected to persist the API key, passing it as
|
96
|
+
* `params.apiKey`.
|
97
|
+
* If no API key is known {@link Deconz.ApiClient#getApiKey getApiKey()} can
|
152
98
|
* be called to create one.<br>
|
153
99
|
*
|
154
100
|
* @param {object} params - Parameters.
|
155
101
|
* @param {?string} params.config - The bridge/gateway public configuration,
|
156
|
-
* i.e. the response of {@link
|
102
|
+
* i.e. the response of {@link Deconz.Discovery#config config()}.
|
157
103
|
* @param {!string} params.host - Hostname/IP address and port of the
|
158
104
|
* deCONZ gateway.
|
159
105
|
* @param {boolean} [params.keepAlive=false] - Keep server connection(s)
|
@@ -163,7 +109,7 @@ class DeconzClient extends homebridgeLib.HttpClient {
|
|
163
109
|
* @param {boolean} [params.phoscon=false] - Mimic Phoscon web app to use
|
164
110
|
* deCONZ gateway API extensions.
|
165
111
|
* @param {integer} [params.timeout=5] - Request timeout (in seconds).
|
166
|
-
* @param {?string} params.
|
112
|
+
* @param {?string} params.apiKey - The API key of the deCONZ gateway.
|
167
113
|
* @param {integer} [params.waitTimePut=50] - The time (in milliseconds),
|
168
114
|
* after sending a PUT request, to wait before sending another PUT request.
|
169
115
|
* @param {integer} [params.waitTimePutGroup=1000] - The time (in
|
@@ -190,7 +136,7 @@ class DeconzClient extends homebridgeLib.HttpClient {
|
|
190
136
|
.intKey('maxSockets', 1, 20)
|
191
137
|
.boolKey('phoscon')
|
192
138
|
.intKey('timeout', 1, 60)
|
193
|
-
.stringKey('
|
139
|
+
.stringKey('apiKey')
|
194
140
|
.intKey('waitTimePut', 0, 50)
|
195
141
|
.intKey('waitTimePutGroup', 0, 1000)
|
196
142
|
.intKey('waitTimeResend', 0, 1000)
|
@@ -209,8 +155,8 @@ class DeconzClient extends homebridgeLib.HttpClient {
|
|
209
155
|
// options.headers = { Accept: 'application/vnd.ddel.v1' }
|
210
156
|
options.headers = { Accept: 'application/vnd.ddel.v1.1,vnd.ddel.v1.1' }
|
211
157
|
}
|
212
|
-
if (_options.
|
213
|
-
options.path += '/' + _options.
|
158
|
+
if (_options.apiKey) {
|
159
|
+
options.path += '/' + _options.apiKey
|
214
160
|
}
|
215
161
|
super(options)
|
216
162
|
this._options = _options
|
@@ -224,7 +170,7 @@ class DeconzClient extends homebridgeLib.HttpClient {
|
|
224
170
|
*/
|
225
171
|
get bridgeid () { return this._options.config.bridgeid }
|
226
172
|
|
227
|
-
/** Server (base) path, `/api/`
|
173
|
+
/** Server (base) path, `/api/`_API_key_.
|
228
174
|
* @type {string}
|
229
175
|
* @readonly
|
230
176
|
*/
|
@@ -233,9 +179,9 @@ class DeconzClient extends homebridgeLib.HttpClient {
|
|
233
179
|
/** The API key.
|
234
180
|
* @type {string}
|
235
181
|
*/
|
236
|
-
get
|
237
|
-
set
|
238
|
-
this._options.
|
182
|
+
get apiKey () { return this._options.apiKey }
|
183
|
+
set apiKey (value) {
|
184
|
+
this._options.apiKey = value
|
239
185
|
let path = '/api'
|
240
186
|
if (value != null) {
|
241
187
|
path += '/' + value
|
@@ -245,7 +191,7 @@ class DeconzClient extends homebridgeLib.HttpClient {
|
|
245
191
|
|
246
192
|
// ===========================================================================
|
247
193
|
|
248
|
-
/** Issue a GET request of `/api/`
|
194
|
+
/** Issue a GET request of `/api/`_API_key_`/`_resource_.
|
249
195
|
*
|
250
196
|
* @param {string} resource - The resource.<br>
|
251
197
|
* This might be a resource as exposed by the API, e.g. `/lights/1/state`,
|
@@ -309,9 +255,9 @@ class DeconzClient extends homebridgeLib.HttpClient {
|
|
309
255
|
return body
|
310
256
|
}
|
311
257
|
|
312
|
-
/** Issue a PUT request to `/api/`
|
258
|
+
/** Issue a PUT request to `/api/`_API_key_`/`_resource_.
|
313
259
|
*
|
314
|
-
*
|
260
|
+
* ApiClient throttles the number of PUT requests to limit the Zigbee traffic
|
315
261
|
* to 20 unicast messsages per seconds, or 1 broadcast message per second,
|
316
262
|
* delaying the request when needed.
|
317
263
|
* @param {string} resource - The resource.
|
@@ -342,7 +288,7 @@ class DeconzClient extends homebridgeLib.HttpClient {
|
|
342
288
|
return this.request('PUT', resource, body)
|
343
289
|
}
|
344
290
|
|
345
|
-
/** Issue a POST request to `/api/`
|
291
|
+
/** Issue a POST request to `/api/`_API_key_`/`_resource_.
|
346
292
|
*
|
347
293
|
* @param {string} resource - The resource.
|
348
294
|
* @param {*} body - The body, which will be converted to JSON.
|
@@ -353,7 +299,7 @@ class DeconzClient extends homebridgeLib.HttpClient {
|
|
353
299
|
return this.request('POST', resource, body)
|
354
300
|
}
|
355
301
|
|
356
|
-
/** Issue a DELETE request of `/api/`
|
302
|
+
/** Issue a DELETE request of `/api/`_API_key_`/`_resource_.
|
357
303
|
* @param {string} resource - The resource.
|
358
304
|
* @param {*} body - The body, which will be converted to JSON.
|
359
305
|
* @return {DeconzResponse} response - The response.
|
@@ -365,48 +311,48 @@ class DeconzClient extends homebridgeLib.HttpClient {
|
|
365
311
|
|
366
312
|
// ===========================================================================
|
367
313
|
|
368
|
-
/**
|
314
|
+
/** Obtain an API key and set {@link Deconz.ApiClient#apiKey apiKey}.
|
369
315
|
*
|
370
|
-
* Calls {@link
|
316
|
+
* Calls {@link Deconz.ApiClient#post post()} to issue a POST request to `/api`.
|
371
317
|
*
|
372
|
-
* Before calling `
|
318
|
+
* Before calling `getApiKey`, the deCONZ gateway must be unlocked, unless
|
373
319
|
* the client is running on the same host as the gateway.
|
374
|
-
* @return {string}
|
320
|
+
* @return {string} apiKey - The newly created API key.
|
375
321
|
* @throws {HttpError} In case of HTTP error.
|
376
322
|
* @throws {DeconzError} In case of API error.
|
377
323
|
*/
|
378
|
-
async
|
324
|
+
async getApiKey (application) {
|
379
325
|
if (typeof application !== 'string' || application === '') {
|
380
326
|
throw new TypeError(`${application}: invalid application name`)
|
381
327
|
}
|
382
|
-
const
|
328
|
+
const apiKey = this._options.apiKey
|
383
329
|
const body = { devicetype: `${application}#${os.hostname().split('.')[0]}` }
|
384
|
-
this.
|
330
|
+
this.apiKey = null
|
385
331
|
try {
|
386
332
|
const response = await this.post('/', body)
|
387
|
-
this.
|
388
|
-
return this.
|
333
|
+
this.apiKey = response.success.username
|
334
|
+
return this.apiKey
|
389
335
|
} catch (error) {
|
390
|
-
this.
|
336
|
+
this.apiKey = apiKey
|
391
337
|
throw (error)
|
392
338
|
}
|
393
339
|
}
|
394
340
|
|
395
|
-
/** Delete the API key and clear {@link
|
341
|
+
/** Delete the API key and clear {@link Deconz.ApiClient#apiKey apiKey}.
|
396
342
|
* @throws {HttpError} In case of HTTP error.
|
397
343
|
* @throws {DeconzError} In case of API error.
|
398
344
|
*/
|
399
|
-
async
|
345
|
+
async deleteApiKey () {
|
400
346
|
try {
|
401
|
-
await this.delete('/config/whitelist/' + this.
|
347
|
+
await this.delete('/config/whitelist/' + this.apiKey)
|
402
348
|
} catch (error) {}
|
403
|
-
this.
|
349
|
+
this.apiKey = null
|
404
350
|
}
|
405
351
|
|
406
352
|
/** Unlock the gateway to allow creating a new API key.
|
407
353
|
*
|
408
|
-
* Calls {@link
|
409
|
-
* `/api/`
|
354
|
+
* Calls {@link Deconz.ApiClient#put put()} to issue a PUT request to
|
355
|
+
* `/api/`_API_key_`/config`.
|
410
356
|
*
|
411
357
|
* @return {DeconzResponse} response - The response.
|
412
358
|
* @throws {HttpError} In case of HTTP error.
|
@@ -416,35 +362,13 @@ class DeconzClient extends homebridgeLib.HttpClient {
|
|
416
362
|
return this.put('/config', { unlock: 60 })
|
417
363
|
}
|
418
364
|
|
419
|
-
/** Initiate a touchlink scan.
|
420
|
-
*
|
421
|
-
* Calls {@link DeconzClient#post post()} to issue a POST request to
|
422
|
-
* `/api/`_username_`/touchlink/scan`, to initiate a touchlink scan.
|
423
|
-
* As the ConBee II and RaspBee II firmware lack support for touchlink,
|
424
|
-
* this will only work for the original ConBee and RaspBee.
|
425
|
-
* To see the results of the scan, issue a GET request of
|
426
|
-
* `/api/`_username_`/touchlink/scan`.
|
427
|
-
* The ID returned in the scan results is needed to touchlink identify or
|
428
|
-
* reset the device.
|
429
|
-
* To issue a touchlink identify, issue a POST request of
|
430
|
-
* `/api/`_username_`/touchlink/`_ID_`/identify`.
|
431
|
-
* To issue a touchlink reset, issue a POST request to
|
432
|
-
* `/api/`_username_`/touchlink/`_ID_`/reset`.
|
433
|
-
* @return {DeconzResponse} response - The response.
|
434
|
-
* @throws {HttpError} In case of HTTP error.
|
435
|
-
* @throws {DeconzError} In case of API error.
|
436
|
-
*/
|
437
|
-
async touchlink () {
|
438
|
-
return this.post('/touchlink/scan')
|
439
|
-
}
|
440
|
-
|
441
365
|
/** Search for new devices.
|
442
366
|
*
|
443
|
-
* Calls {@link
|
444
|
-
* `/api/`
|
367
|
+
* Calls {@link Deconz.ApiClient#put put()} to issue a PUT request to
|
368
|
+
* `/api/`_API_key_`/config`, to enable pairing of new Zigbee devices.
|
445
369
|
*
|
446
370
|
* To see the newly paired devices, issue a GET request of
|
447
|
-
* `/api/`
|
371
|
+
* `/api/`_API_key_`/lights/new` and/or `/api/`_API_key_`/sensor/new`
|
448
372
|
* @return {DeconzResponse} response - The response.
|
449
373
|
* @throws {HttpError} In case of HTTP error.
|
450
374
|
* @throws {DeconzError} In case of API error.
|
@@ -455,8 +379,8 @@ class DeconzClient extends homebridgeLib.HttpClient {
|
|
455
379
|
|
456
380
|
/** Restart the gateway.
|
457
381
|
*
|
458
|
-
* Calls {@link
|
459
|
-
* `/api/`
|
382
|
+
* Calls {@link Deconz.ApiClient#post post()} to issue a POST request to
|
383
|
+
* `/api/`_API_key_`/config/restartapp`, to restart the deCONZ gateway.
|
460
384
|
*
|
461
385
|
* @return {DeconzResponse} response - The response.
|
462
386
|
* @throws {HttpError} In case of HTTP error.
|
@@ -470,9 +394,9 @@ class DeconzClient extends homebridgeLib.HttpClient {
|
|
470
394
|
|
471
395
|
/** Issue an HTTP request to the deCONZ gateway.
|
472
396
|
*
|
473
|
-
* This method does the heavy lifting for {@link
|
474
|
-
* {@link
|
475
|
-
* {@link
|
397
|
+
* This method does the heavy lifting for {@link Deconz.AplClient#get get()},
|
398
|
+
* {@link Deconz.ApiClient#put put()}, {@link Deconz.ApiClient#post post()},
|
399
|
+
* and {@link Deconz.ApiClient#delete delete()}.
|
476
400
|
* It shouldn't be called directly.
|
477
401
|
*
|
478
402
|
* @param {string} method - The method for the request.
|
@@ -485,12 +409,12 @@ class DeconzClient extends homebridgeLib.HttpClient {
|
|
485
409
|
async request (method, resource, body = null, retry = 0) {
|
486
410
|
try {
|
487
411
|
const httpResponse = await super.request(method, resource, body)
|
488
|
-
const response = new
|
412
|
+
const response = new Deconz.ApiResponse(httpResponse)
|
489
413
|
for (const error of response.errors) {
|
490
414
|
/** Emitted for each API error returned by the or deCONZ gateway.
|
491
415
|
*
|
492
|
-
* @event
|
493
|
-
* @param {HttpClient.HttpError|
|
416
|
+
* @event Deconz.ApiClient#error
|
417
|
+
* @param {HttpClient.HttpError|Deconz.ApiError} error - The error.
|
494
418
|
*/
|
495
419
|
this.emit('error', error)
|
496
420
|
if (!error.nonCritical) {
|
@@ -516,4 +440,4 @@ class DeconzClient extends homebridgeLib.HttpClient {
|
|
516
440
|
}
|
517
441
|
}
|
518
442
|
|
519
|
-
module.exports =
|
443
|
+
module.exports = ApiClient
|
@@ -0,0 +1,42 @@
|
|
1
|
+
// homebridge-deconz/lib/Deconz/ApiError.js
|
2
|
+
//
|
3
|
+
// Homebridge plug-in for deCONZ.
|
4
|
+
// Copyright © 2018-2022 Erik Baauw. All rights reserved.
|
5
|
+
|
6
|
+
'use strict'
|
7
|
+
|
8
|
+
const homebridgeLib = require('homebridge-lib')
|
9
|
+
|
10
|
+
// API errors that could still cause (part of) the PUT command to be executed.
|
11
|
+
const nonCriticalApiErrorTypes = [
|
12
|
+
6, // parameter not available
|
13
|
+
7, // invalid value for parameter
|
14
|
+
8, // paramater not modifiable
|
15
|
+
201 // paramater not modifiable, device is set to off
|
16
|
+
]
|
17
|
+
|
18
|
+
/** Deconz API error.
|
19
|
+
* @hideconstructor
|
20
|
+
* @extends HttpClient.HttpError
|
21
|
+
* @memberof Deconz
|
22
|
+
*/
|
23
|
+
class ApiError extends homebridgeLib.HttpClient.HttpError {
|
24
|
+
constructor (message, response, type, description) {
|
25
|
+
super(message, response.request, response.statusCode, response.statusMessage)
|
26
|
+
|
27
|
+
/** @member {integer} - The API error type.
|
28
|
+
*/
|
29
|
+
this.type = type
|
30
|
+
|
31
|
+
/** @member {string} - The API error description.
|
32
|
+
*/
|
33
|
+
this.description = description
|
34
|
+
|
35
|
+
/** @member {boolean} - Indication that the request might still succeed
|
36
|
+
* for other attributes.
|
37
|
+
*/
|
38
|
+
this.nonCritical = nonCriticalApiErrorTypes.includes(type)
|
39
|
+
}
|
40
|
+
}
|
41
|
+
|
42
|
+
module.exports = ApiError
|
@@ -0,0 +1,54 @@
|
|
1
|
+
// homebridge-deconz/lib/Deconz/ApiResponse.js
|
2
|
+
//
|
3
|
+
// Homebridge plug-in for deCONZ.
|
4
|
+
// Copyright © 2018-2022 Erik Baauw. All rights reserved.
|
5
|
+
|
6
|
+
'use strict'
|
7
|
+
|
8
|
+
const homebridgeLib = require('homebridge-lib')
|
9
|
+
|
10
|
+
const Deconz = require('../Deconz')
|
11
|
+
|
12
|
+
/** Deconz API response.
|
13
|
+
* @hideconstructor
|
14
|
+
* @extends HttpClient.HttpResponse
|
15
|
+
* @memberof Deconz
|
16
|
+
*/
|
17
|
+
class ApiResponse extends homebridgeLib.HttpClient.HttpResponse {
|
18
|
+
constructor (response) {
|
19
|
+
super(
|
20
|
+
response.request, response.statusCode, response.statusMessage,
|
21
|
+
response.headers, response.body, response.parsedBody
|
22
|
+
)
|
23
|
+
|
24
|
+
/** @member {object} - An object with the `"success"` API responses.
|
25
|
+
*/
|
26
|
+
this.success = {}
|
27
|
+
|
28
|
+
/** @member {Deconz.ApiError[]} - A list of `"error"` API responses.
|
29
|
+
*/
|
30
|
+
this.errors = []
|
31
|
+
|
32
|
+
if (Array.isArray(response.body)) {
|
33
|
+
for (const id in response.body) {
|
34
|
+
const e = response.body[id].error
|
35
|
+
if (e != null && typeof e === 'object') {
|
36
|
+
this.errors.push(new Deconz.ApiError(
|
37
|
+
`api error ${e.type}: ${e.description}`,
|
38
|
+
response, e.type, e.description
|
39
|
+
))
|
40
|
+
}
|
41
|
+
const s = response.body[id].success
|
42
|
+
if (s != null && typeof s === 'object') {
|
43
|
+
for (const path of Object.keys(s)) {
|
44
|
+
const a = path.split('/')
|
45
|
+
const key = a[a.length - 1]
|
46
|
+
this.success[key] = s[path]
|
47
|
+
}
|
48
|
+
}
|
49
|
+
}
|
50
|
+
}
|
51
|
+
}
|
52
|
+
}
|
53
|
+
|
54
|
+
module.exports = ApiResponse
|
@@ -0,0 +1,100 @@
|
|
1
|
+
// homebridge-deconz/lib/Deconz/Device.js
|
2
|
+
// Copyright © 2022 Erik Baauw. All rights reserved.
|
3
|
+
//
|
4
|
+
// Homebridge plugin for deCONZ.
|
5
|
+
|
6
|
+
'use strict'
|
7
|
+
|
8
|
+
/** Delegate class for a Zigbee or virtual device on a deCONZ gateway.
|
9
|
+
*
|
10
|
+
* The deCONZ REST API exposes a Zigbee device using one or more resources.
|
11
|
+
* These resources are linked to the device through the `uniqueid` in the
|
12
|
+
* resource body.
|
13
|
+
* Each supported device corresponds to a HomeKit accessory.
|
14
|
+
* Each supported resource corresponds to a HomeKit service.
|
15
|
+
* @memberof Deconz
|
16
|
+
*/
|
17
|
+
class Device {
|
18
|
+
/** Create a new instance of a delegate of a device, from a resource.
|
19
|
+
*
|
20
|
+
* @param {Deconz.Resource} resource - The resource.
|
21
|
+
*/
|
22
|
+
constructor (resource) {
|
23
|
+
/** The device ID.
|
24
|
+
*
|
25
|
+
* This is the {@link Deconz.Resource#id id} of the delegates
|
26
|
+
* of all resources for the device.
|
27
|
+
* @type {string}
|
28
|
+
*/
|
29
|
+
this.id = resource.id
|
30
|
+
|
31
|
+
/** Zigbee device vs virtual device.
|
32
|
+
*
|
33
|
+
* This is the {@link Deconz.Resource#zigbee zigbee} of the
|
34
|
+
* delegates of all resources for the device.
|
35
|
+
* @type {boolean}
|
36
|
+
*/
|
37
|
+
this.zigbee = resource.zigbee
|
38
|
+
|
39
|
+
/** The delegates of the resources for the device, by subtype of the
|
40
|
+
* corresponding HomeKit service.
|
41
|
+
* @type {Object.<string, Deconz.Resource>}
|
42
|
+
*/
|
43
|
+
this.resourceBySubtype = {}
|
44
|
+
this.resourceBySubtype[resource.subtype] = resource
|
45
|
+
|
46
|
+
/** The key of the delegate for the primary resource for the device in
|
47
|
+
* {@link DeconzDevice#resourceBySubtype resourceBySubtype}
|
48
|
+
*
|
49
|
+
* This is the {@link DeconzDevice.Resource#subtype subtype} of the
|
50
|
+
* HomeKit service corresponding to the primary resource.
|
51
|
+
* @type {string}
|
52
|
+
*/
|
53
|
+
this.primary = resource.subtype
|
54
|
+
}
|
55
|
+
|
56
|
+
/** The delegate of the primary resource of the device.
|
57
|
+
* @type {Deconz.Resource}
|
58
|
+
*/
|
59
|
+
get resource () { return this.resourceBySubtype[this.primary] }
|
60
|
+
|
61
|
+
/** List of resource paths of the resources for the device.
|
62
|
+
* @type {string[]}
|
63
|
+
*/
|
64
|
+
get rpaths () {
|
65
|
+
return Object.keys(this.resourceBySubtype || {}).map((subtype) => {
|
66
|
+
return this.resourceBySubtype[subtype].rpath
|
67
|
+
})
|
68
|
+
}
|
69
|
+
|
70
|
+
/** Add a {@link Deconz.Resource Resource}.
|
71
|
+
*
|
72
|
+
* Updates {@link Deconz.Device#resourceBySubtype resourceBySubtype},
|
73
|
+
* {@link Deconz.Device#rpaths rpaths}, and, when the added resource
|
74
|
+
* has a higher priority, {@link Deconz.Device#primary primary} and
|
75
|
+
* {@link Deconz.Device#resource resource}.
|
76
|
+
* @param {Deconz.Resource} resource - The resource.
|
77
|
+
*/
|
78
|
+
addResource (resource) {
|
79
|
+
const { body, id, prio, rtype, subtype, zigbee } = resource
|
80
|
+
if (this.resourceBySubtype[subtype] != null) {
|
81
|
+
const r = this.resourceBySubtype[subtype]
|
82
|
+
throw new Error(
|
83
|
+
`${resource.rpath}: duplicate uniqueid ${body.uniqueid} in ${r.rpath}`
|
84
|
+
)
|
85
|
+
}
|
86
|
+
if (zigbee !== this.zigbee || (zigbee && id !== this.id)) {
|
87
|
+
const r = this.resourceBySubtype[subtype]
|
88
|
+
throw new SyntaxError(
|
89
|
+
`${resource.rpath}: cannot combine ${r.rpath}`
|
90
|
+
)
|
91
|
+
}
|
92
|
+
this.resourceBySubtype[subtype] = resource
|
93
|
+
const p = this.resourceBySubtype[this.primary]
|
94
|
+
if (p.rtype === rtype && p.prio < prio) {
|
95
|
+
this.primary = resource.subtype
|
96
|
+
}
|
97
|
+
}
|
98
|
+
}
|
99
|
+
|
100
|
+
module.exports = Device
|