@switchbot/homebridge-switchbot 3.7.1-beta.1 → 3.7.1-beta.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/CHANGELOG.md +10 -0
- package/README.md +14 -0
- package/assets/security-privacy-bluetooth.png +0 -0
- package/config.schema.json +7 -1
- package/dist/device/blindtilt.d.ts +3 -1
- package/dist/device/blindtilt.d.ts.map +1 -1
- package/dist/device/blindtilt.js +61 -28
- package/dist/device/blindtilt.js.map +1 -1
- package/dist/device/bot.d.ts +1 -0
- package/dist/device/bot.d.ts.map +1 -1
- package/dist/device/bot.js +102 -80
- package/dist/device/bot.js.map +1 -1
- package/dist/device/ceilinglight.d.ts +1 -0
- package/dist/device/ceilinglight.d.ts.map +1 -1
- package/dist/device/ceilinglight.js +53 -28
- package/dist/device/ceilinglight.js.map +1 -1
- package/dist/device/colorbulb.d.ts +1 -0
- package/dist/device/colorbulb.d.ts.map +1 -1
- package/dist/device/colorbulb.js +53 -28
- package/dist/device/colorbulb.js.map +1 -1
- package/dist/device/contact.d.ts +1 -0
- package/dist/device/contact.d.ts.map +1 -1
- package/dist/device/contact.js +53 -28
- package/dist/device/contact.js.map +1 -1
- package/dist/device/curtain.d.ts +2 -0
- package/dist/device/curtain.d.ts.map +1 -1
- package/dist/device/curtain.js +74 -43
- package/dist/device/curtain.js.map +1 -1
- package/dist/device/device.d.ts.map +1 -1
- package/dist/device/device.js +247 -394
- package/dist/device/device.js.map +1 -1
- package/dist/device/fan.d.ts +1 -0
- package/dist/device/fan.d.ts.map +1 -1
- package/dist/device/fan.js +53 -28
- package/dist/device/fan.js.map +1 -1
- package/dist/device/hub.d.ts +1 -0
- package/dist/device/hub.d.ts.map +1 -1
- package/dist/device/hub.js +53 -28
- package/dist/device/hub.js.map +1 -1
- package/dist/device/humidifier.d.ts +1 -0
- package/dist/device/humidifier.d.ts.map +1 -1
- package/dist/device/humidifier.js +53 -28
- package/dist/device/humidifier.js.map +1 -1
- package/dist/device/iosensor.d.ts +1 -0
- package/dist/device/iosensor.d.ts.map +1 -1
- package/dist/device/iosensor.js +53 -28
- package/dist/device/iosensor.js.map +1 -1
- package/dist/device/lightstrip.d.ts +1 -0
- package/dist/device/lightstrip.d.ts.map +1 -1
- package/dist/device/lightstrip.js +53 -28
- package/dist/device/lightstrip.js.map +1 -1
- package/dist/device/lock.d.ts +1 -0
- package/dist/device/lock.d.ts.map +1 -1
- package/dist/device/lock.js +54 -29
- package/dist/device/lock.js.map +1 -1
- package/dist/device/meter.d.ts +1 -0
- package/dist/device/meter.d.ts.map +1 -1
- package/dist/device/meter.js +53 -28
- package/dist/device/meter.js.map +1 -1
- package/dist/device/meterplus.d.ts +1 -0
- package/dist/device/meterplus.d.ts.map +1 -1
- package/dist/device/meterplus.js +53 -28
- package/dist/device/meterplus.js.map +1 -1
- package/dist/device/motion.d.ts +1 -0
- package/dist/device/motion.d.ts.map +1 -1
- package/dist/device/motion.js +53 -28
- package/dist/device/motion.js.map +1 -1
- package/dist/device/plug.d.ts +1 -0
- package/dist/device/plug.d.ts.map +1 -1
- package/dist/device/plug.js +54 -29
- package/dist/device/plug.js.map +1 -1
- package/dist/device/robotvacuumcleaner.d.ts +1 -0
- package/dist/device/robotvacuumcleaner.d.ts.map +1 -1
- package/dist/device/robotvacuumcleaner.js +54 -29
- package/dist/device/robotvacuumcleaner.js.map +1 -1
- package/dist/device/waterdetector.d.ts +1 -0
- package/dist/device/waterdetector.d.ts.map +1 -1
- package/dist/device/waterdetector.js +53 -28
- package/dist/device/waterdetector.js.map +1 -1
- package/dist/irdevice/irdevice.d.ts.map +1 -1
- package/dist/irdevice/irdevice.js +30 -133
- package/dist/irdevice/irdevice.js.map +1 -1
- package/dist/platform.d.ts +19 -18
- package/dist/platform.d.ts.map +1 -1
- package/dist/platform.js +369 -524
- package/dist/platform.js.map +1 -1
- package/dist/settings.d.ts +1 -0
- package/dist/settings.d.ts.map +1 -1
- package/dist/settings.js.map +1 -1
- package/package.json +20 -18
- package/src/device/blindtilt.ts +59 -27
- package/src/device/bot.ts +98 -77
- package/src/device/ceilinglight.ts +51 -27
- package/src/device/colorbulb.ts +50 -26
- package/src/device/contact.ts +50 -26
- package/src/device/curtain.ts +71 -40
- package/src/device/device.ts +283 -395
- package/src/device/fan.ts +50 -26
- package/src/device/hub.ts +50 -26
- package/src/device/humidifier.ts +50 -26
- package/src/device/iosensor.ts +50 -26
- package/src/device/lightstrip.ts +50 -26
- package/src/device/lock.ts +51 -27
- package/src/device/meter.ts +50 -26
- package/src/device/meterplus.ts +50 -26
- package/src/device/motion.ts +50 -26
- package/src/device/plug.ts +51 -27
- package/src/device/robotvacuumcleaner.ts +51 -27
- package/src/device/waterdetector.ts +50 -26
- package/src/irdevice/irdevice.ts +54 -134
- package/src/platform.ts +379 -529
- package/src/settings.ts +1 -0
package/dist/platform.js
CHANGED
|
@@ -40,6 +40,10 @@ import { readFileSync, writeFileSync } from 'fs';
|
|
|
40
40
|
import { EveHomeKitTypes } from 'homebridge-lib/EveHomeKitTypes';
|
|
41
41
|
import { PLATFORM_NAME, PLUGIN_NAME, Devices, deleteWebhook, queryWebhook, setupWebhook, updateWebhook } from './settings.js';
|
|
42
42
|
import { isBlindTiltDevice, isCurtainDevice, sleep } from './utils.js';
|
|
43
|
+
/*
|
|
44
|
+
* For Testing Locally:
|
|
45
|
+
* import { SwitchBotModel } from '/Users/Shared/GitHub/OpenWonderLabs/node-switchbot/dist/index.js';
|
|
46
|
+
*/
|
|
43
47
|
import { SwitchBotModel } from 'node-switchbot';
|
|
44
48
|
/**
|
|
45
49
|
* HomebridgePlatform
|
|
@@ -47,9 +51,11 @@ import { SwitchBotModel } from 'node-switchbot';
|
|
|
47
51
|
* parse the user config and discover/register accessories with Homebridge.
|
|
48
52
|
*/
|
|
49
53
|
export class SwitchBotPlatform {
|
|
50
|
-
|
|
54
|
+
// Platform properties
|
|
55
|
+
accessories = [];
|
|
51
56
|
api;
|
|
52
57
|
log;
|
|
58
|
+
// Configuration properties
|
|
53
59
|
version;
|
|
54
60
|
Logging;
|
|
55
61
|
debugMode;
|
|
@@ -58,15 +64,16 @@ export class SwitchBotPlatform {
|
|
|
58
64
|
platformConfig;
|
|
59
65
|
platformLogging;
|
|
60
66
|
config;
|
|
67
|
+
// MQTT and Webhook properties
|
|
61
68
|
mqttClient = null;
|
|
62
69
|
webhookEventListener = null;
|
|
70
|
+
// External APIs
|
|
63
71
|
eve;
|
|
64
72
|
fakegatoAPI;
|
|
73
|
+
// Event Handlers
|
|
65
74
|
webhookEventHandler = {};
|
|
66
75
|
bleEventHandler = {};
|
|
67
76
|
constructor(log, config, api) {
|
|
68
|
-
// initialize
|
|
69
|
-
this.accessories = [];
|
|
70
77
|
this.api = api;
|
|
71
78
|
this.log = log;
|
|
72
79
|
// only load if configured
|
|
@@ -104,27 +111,42 @@ export class SwitchBotPlatform {
|
|
|
104
111
|
// in order to ensure they weren't added to homebridge already. This event can also be used
|
|
105
112
|
// to start discovery of new accessories.
|
|
106
113
|
this.api.on('didFinishLaunching', async () => {
|
|
107
|
-
this.debugLog('Executed didFinishLaunching callback');
|
|
114
|
+
await this.debugLog('Executed didFinishLaunching callback');
|
|
108
115
|
// run the method to discover / register your devices as accessories
|
|
109
116
|
try {
|
|
110
117
|
if (this.config.credentials?.openToken && !this.config.credentials.token) {
|
|
111
118
|
await this.updateToken();
|
|
112
119
|
}
|
|
113
120
|
else if (this.config.credentials?.token && !this.config.credentials?.secret) {
|
|
114
|
-
this.errorLog('"secret" config is not populated, you must populate then please restart Homebridge.');
|
|
121
|
+
await this.errorLog('"secret" config is not populated, you must populate then please restart Homebridge.');
|
|
115
122
|
}
|
|
116
123
|
else {
|
|
117
|
-
this.discoverDevices();
|
|
124
|
+
await this.discoverDevices();
|
|
118
125
|
}
|
|
119
126
|
}
|
|
120
127
|
catch (e) {
|
|
121
|
-
this.errorLog(`Failed to Discover, Error Message: ${e.message}, Submit Bugs Here: ` + 'https://tinyurl.com/SwitchBotBug');
|
|
128
|
+
await this.errorLog(`Failed to Discover, Error Message: ${e.message}, Submit Bugs Here: ` + 'https://tinyurl.com/SwitchBotBug');
|
|
122
129
|
await this.debugErrorLog(`Failed to Discover, Error: ${e}`);
|
|
123
130
|
}
|
|
124
131
|
});
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
132
|
+
try {
|
|
133
|
+
this.setupMqtt();
|
|
134
|
+
}
|
|
135
|
+
catch (e) {
|
|
136
|
+
this.errorLog(`Setup MQTT, Error Message: ${e.message}, Submit Bugs Here: ` + 'https://tinyurl.com/SwitchBotBug');
|
|
137
|
+
}
|
|
138
|
+
try {
|
|
139
|
+
this.setupwebhook();
|
|
140
|
+
}
|
|
141
|
+
catch (e) {
|
|
142
|
+
this.errorLog(`Setup Webhook, Error Message: ${e.message}, Submit Bugs Here: ` + 'https://tinyurl.com/SwitchBotBug');
|
|
143
|
+
}
|
|
144
|
+
try {
|
|
145
|
+
this.setupBlE();
|
|
146
|
+
}
|
|
147
|
+
catch (e) {
|
|
148
|
+
this.errorLog(`Setup Platform BLE, Error Message: ${e.message}, Submit Bugs Here: ` + 'https://tinyurl.com/SwitchBotBug');
|
|
149
|
+
}
|
|
128
150
|
}
|
|
129
151
|
async setupMqtt() {
|
|
130
152
|
if (this.config.options?.mqttURL) {
|
|
@@ -141,7 +163,7 @@ export class SwitchBotPlatform {
|
|
|
141
163
|
this.mqttClient.subscribe('homebridge-switchbot/webhook/+');
|
|
142
164
|
this.mqttClient.on('message', async (topic, message) => {
|
|
143
165
|
try {
|
|
144
|
-
await
|
|
166
|
+
await this.debugLog(`Received Webhook via MQTT: ${topic}=${message}`);
|
|
145
167
|
const context = JSON.parse(message.toString());
|
|
146
168
|
this.webhookEventHandler[context.deviceMac]?.(context);
|
|
147
169
|
}
|
|
@@ -173,17 +195,14 @@ export class SwitchBotPlatform {
|
|
|
173
195
|
const body = JSON.parse(data);
|
|
174
196
|
await this.debugLog(`Received Webhook: ${JSON.stringify(body)}`);
|
|
175
197
|
if (this.config.options?.mqttURL) {
|
|
176
|
-
const mac = body.context.deviceMac
|
|
177
|
-
?.toLowerCase()
|
|
178
|
-
.match(/[\s\S]{1,2}/g)
|
|
179
|
-
?.join(':');
|
|
198
|
+
const mac = body.context.deviceMac?.toLowerCase().match(/[\s\S]{1,2}/g)?.join(':');
|
|
180
199
|
const options = this.config.options?.mqttPubOptions || {};
|
|
181
200
|
this.mqttClient?.publish(`homebridge-switchbot/webhook/${mac}`, `${JSON.stringify(body.context)}`, options);
|
|
182
201
|
}
|
|
183
202
|
this.webhookEventHandler[body.context.deviceMac]?.(body.context);
|
|
184
203
|
}
|
|
185
204
|
catch (e) {
|
|
186
|
-
this.errorLog(`Failed to handle webhook event. Error:${e}`);
|
|
205
|
+
await this.errorLog(`Failed to handle webhook event. Error:${e}`);
|
|
187
206
|
}
|
|
188
207
|
});
|
|
189
208
|
response.writeHead(200, { 'Content-Type': 'text/plain' });
|
|
@@ -200,7 +219,7 @@ export class SwitchBotPlatform {
|
|
|
200
219
|
}).listen(port ? port : 80);
|
|
201
220
|
}
|
|
202
221
|
catch (e) {
|
|
203
|
-
this.errorLog(`Failed to create webhook listener. Error:${e.message}`);
|
|
222
|
+
await this.errorLog(`Failed to create webhook listener. Error:${e.message}`);
|
|
204
223
|
return;
|
|
205
224
|
}
|
|
206
225
|
try {
|
|
@@ -214,16 +233,14 @@ export class SwitchBotPlatform {
|
|
|
214
233
|
}),
|
|
215
234
|
});
|
|
216
235
|
const response = await body.json();
|
|
217
|
-
await this.debugLog(`setupWebhook: url:${url}`);
|
|
218
|
-
await this.debugLog(`setupWebhook: body:${JSON.stringify(response)}`);
|
|
219
|
-
await this.debugLog(`setupWebhook: statusCode:${statusCode}`);
|
|
236
|
+
await this.debugLog(`setupWebhook: url:${url}, body:${JSON.stringify(response)}, statusCode:${statusCode}`);
|
|
220
237
|
if (statusCode !== 200 || response?.statusCode !== 100) {
|
|
221
|
-
this.errorLog(`Failed to configure webhook. Existing webhook well be overridden. HTTP:${statusCode} API:${response?.statusCode} `
|
|
238
|
+
await this.errorLog(`Failed to configure webhook. Existing webhook well be overridden. HTTP:${statusCode} API:${response?.statusCode} `
|
|
222
239
|
+ `message:${response?.message}`);
|
|
223
240
|
}
|
|
224
241
|
}
|
|
225
242
|
catch (e) {
|
|
226
|
-
this.errorLog(`Failed to configure webhook. Error: ${e.message}`);
|
|
243
|
+
await this.errorLog(`Failed to configure webhook. Error: ${e.message}`);
|
|
227
244
|
}
|
|
228
245
|
try {
|
|
229
246
|
const { body, statusCode } = await request(updateWebhook, {
|
|
@@ -236,15 +253,13 @@ export class SwitchBotPlatform {
|
|
|
236
253
|
}),
|
|
237
254
|
});
|
|
238
255
|
const response = await body.json();
|
|
239
|
-
await this.debugLog(`updateWebhook: url:${url}`);
|
|
240
|
-
await this.debugLog(`updateWebhook: body:${JSON.stringify(response)}`);
|
|
241
|
-
await this.debugLog(`updateWebhook: statusCode:${statusCode}`);
|
|
256
|
+
await this.debugLog(`updateWebhook: url:${url}, body:${JSON.stringify(response)}, statusCode:${statusCode}`);
|
|
242
257
|
if (statusCode !== 200 || response?.statusCode !== 100) {
|
|
243
|
-
this.errorLog(`Failed to update webhook. HTTP:${statusCode} API:${response?.statusCode} message:${response?.message}`);
|
|
258
|
+
await this.errorLog(`Failed to update webhook. HTTP:${statusCode} API:${response?.statusCode} message:${response?.message}`);
|
|
244
259
|
}
|
|
245
260
|
}
|
|
246
261
|
catch (e) {
|
|
247
|
-
this.errorLog(`Failed to update webhook. Error:${e.message}`);
|
|
262
|
+
await this.errorLog(`Failed to update webhook. Error:${e.message}`);
|
|
248
263
|
}
|
|
249
264
|
try {
|
|
250
265
|
const { body, statusCode } = await request(queryWebhook, {
|
|
@@ -258,14 +273,14 @@ export class SwitchBotPlatform {
|
|
|
258
273
|
await this.debugLog(`queryWebhook: body:${JSON.stringify(response)}`);
|
|
259
274
|
await this.debugLog(`queryWebhook: statusCode:${statusCode}`);
|
|
260
275
|
if (statusCode !== 200 || response?.statusCode !== 100) {
|
|
261
|
-
this.errorLog(`Failed to query webhook. HTTP:${statusCode} API:${response?.statusCode} message:${response?.message}`);
|
|
276
|
+
await this.errorLog(`Failed to query webhook. HTTP:${statusCode} API:${response?.statusCode} message:${response?.message}`);
|
|
262
277
|
}
|
|
263
278
|
else {
|
|
264
|
-
this.infoLog(`Listening webhook on ${response?.body?.urls[0]}`);
|
|
279
|
+
await this.infoLog(`Listening webhook on ${response?.body?.urls[0]}`);
|
|
265
280
|
}
|
|
266
281
|
}
|
|
267
282
|
catch (e) {
|
|
268
|
-
this.errorLog(`Failed to query webhook. Error:${e}`);
|
|
283
|
+
await this.errorLog(`Failed to query webhook. Error:${e}`);
|
|
269
284
|
}
|
|
270
285
|
this.api.on('shutdown', async () => {
|
|
271
286
|
try {
|
|
@@ -280,14 +295,14 @@ export class SwitchBotPlatform {
|
|
|
280
295
|
const response = await body.json();
|
|
281
296
|
await this.debugLog(`deleteWebhook: url:${url}, body:${JSON.stringify(response)}, statusCode:${statusCode}`);
|
|
282
297
|
if (statusCode !== 200 || response?.statusCode !== 100) {
|
|
283
|
-
this.errorLog(`Failed to delete webhook. HTTP:${statusCode} API:${response?.statusCode} message:${response?.message}`);
|
|
298
|
+
await this.errorLog(`Failed to delete webhook. HTTP:${statusCode} API:${response?.statusCode} message:${response?.message}`);
|
|
284
299
|
}
|
|
285
300
|
else {
|
|
286
|
-
this.infoLog('Unregistered webhook to close listening.');
|
|
301
|
+
await this.infoLog('Unregistered webhook to close listening.');
|
|
287
302
|
}
|
|
288
303
|
}
|
|
289
304
|
catch (e) {
|
|
290
|
-
this.errorLog(`Failed to delete webhook. Error:${e.message}`);
|
|
305
|
+
await this.errorLog(`Failed to delete webhook. Error:${e.message}`);
|
|
291
306
|
}
|
|
292
307
|
});
|
|
293
308
|
}
|
|
@@ -319,10 +334,10 @@ export class SwitchBotPlatform {
|
|
|
319
334
|
this.api.on('shutdown', async () => {
|
|
320
335
|
try {
|
|
321
336
|
switchbot.stopScan();
|
|
322
|
-
this.infoLog('Stopped BLE scanning to close listening.');
|
|
337
|
+
await this.infoLog('Stopped BLE scanning to close listening.');
|
|
323
338
|
}
|
|
324
339
|
catch (e) {
|
|
325
|
-
this.errorLog(`Failed to stop Platform BLE scanning. Error:${e.message}`);
|
|
340
|
+
await this.errorLog(`Failed to stop Platform BLE scanning. Error:${e.message}`);
|
|
326
341
|
}
|
|
327
342
|
});
|
|
328
343
|
}
|
|
@@ -336,7 +351,8 @@ export class SwitchBotPlatform {
|
|
|
336
351
|
* It should be used to setup event handlers for characteristics and update respective values.
|
|
337
352
|
*/
|
|
338
353
|
async configureAccessory(accessory) {
|
|
339
|
-
|
|
354
|
+
const { displayName } = accessory;
|
|
355
|
+
await this.debugLog(`Loading accessory from cache: ${displayName}`);
|
|
340
356
|
// add the restored accessory to the accessories cache so we can track if it has already been registered
|
|
341
357
|
this.accessories.push(accessory);
|
|
342
358
|
}
|
|
@@ -344,7 +360,7 @@ export class SwitchBotPlatform {
|
|
|
344
360
|
* Verify the config passed to the plugin is valid
|
|
345
361
|
*/
|
|
346
362
|
async verifyConfig() {
|
|
347
|
-
this.debugLog('Verifying Config');
|
|
363
|
+
await this.debugLog('Verifying Config');
|
|
348
364
|
this.config = this.config || {};
|
|
349
365
|
this.config.options = this.config.options || {};
|
|
350
366
|
const platformConfig = {};
|
|
@@ -379,10 +395,10 @@ export class SwitchBotPlatform {
|
|
|
379
395
|
for (const irDeviceConfig of this.config.options.irdevices) {
|
|
380
396
|
if (!irDeviceConfig.hide_device) {
|
|
381
397
|
if (!irDeviceConfig.deviceId) {
|
|
382
|
-
this.errorLog('The devices config section is missing the *Device ID* in the config. Please check your config.');
|
|
398
|
+
await this.errorLog('The devices config section is missing the *Device ID* in the config. Please check your config.');
|
|
383
399
|
}
|
|
384
400
|
if (!irDeviceConfig.deviceId && !irDeviceConfig.configRemoteType) {
|
|
385
|
-
this.errorLog('The devices config section is missing the *Device Type* in the config. Please check your config.');
|
|
401
|
+
await this.errorLog('The devices config section is missing the *Device Type* in the config. Please check your config.');
|
|
386
402
|
}
|
|
387
403
|
}
|
|
388
404
|
}
|
|
@@ -394,16 +410,16 @@ export class SwitchBotPlatform {
|
|
|
394
410
|
if (!this.config.options.refreshRate) {
|
|
395
411
|
// default 120 seconds (2 minutes)
|
|
396
412
|
this.config.options.refreshRate = 120;
|
|
397
|
-
this.debugWarnLog('Using Default Refresh Rate (2 minutes).');
|
|
413
|
+
await this.debugWarnLog('Using Default Refresh Rate (2 minutes).');
|
|
398
414
|
}
|
|
399
415
|
if (!this.config.options.pushRate) {
|
|
400
416
|
// default 100 milliseconds
|
|
401
417
|
this.config.options.pushRate = 0.1;
|
|
402
|
-
this.debugWarnLog('Using Default Push Rate.');
|
|
418
|
+
await this.debugWarnLog('Using Default Push Rate.');
|
|
403
419
|
}
|
|
404
420
|
if (!this.config.options.maxRetries) {
|
|
405
421
|
this.config.options.maxRetries = 5;
|
|
406
|
-
this.debugWarnLog('Using Default Max Retries.');
|
|
422
|
+
await this.debugWarnLog('Using Default Max Retries.');
|
|
407
423
|
}
|
|
408
424
|
else {
|
|
409
425
|
this.maxRetries = this.config.options.maxRetries;
|
|
@@ -411,23 +427,23 @@ export class SwitchBotPlatform {
|
|
|
411
427
|
if (!this.config.options.delayBetweenRetries) {
|
|
412
428
|
// default 3 seconds
|
|
413
429
|
this.config.options.delayBetweenRetries = 3000;
|
|
414
|
-
this.debugWarnLog('Using Default Delay Between Retries.');
|
|
430
|
+
await this.debugWarnLog('Using Default Delay Between Retries.');
|
|
415
431
|
}
|
|
416
432
|
else {
|
|
417
433
|
this.delayBetweenRetries = this.config.options.delayBetweenRetries * 1000;
|
|
418
434
|
}
|
|
419
435
|
if (!this.config.credentials && !this.config.options) {
|
|
420
|
-
this.debugWarnLog('Missing Credentials');
|
|
436
|
+
await this.debugWarnLog('Missing Credentials');
|
|
421
437
|
}
|
|
422
438
|
else if (this.config.credentials && !this.config.credentials.notice) {
|
|
423
439
|
if (!this.config.credentials?.token) {
|
|
424
|
-
this.debugErrorLog('Missing token');
|
|
425
|
-
this.debugWarnLog('Cloud Enabled SwitchBot Devices & IR Devices will not work');
|
|
440
|
+
await this.debugErrorLog('Missing token');
|
|
441
|
+
await this.debugWarnLog('Cloud Enabled SwitchBot Devices & IR Devices will not work');
|
|
426
442
|
}
|
|
427
443
|
if (this.config.credentials?.token) {
|
|
428
444
|
if (!this.config.credentials?.secret) {
|
|
429
|
-
this.debugErrorLog('Missing secret');
|
|
430
|
-
this.debugWarnLog('Cloud Enabled SwitchBot Devices & IR Devices will not work');
|
|
445
|
+
await this.debugErrorLog('Missing secret');
|
|
446
|
+
await this.debugWarnLog('Cloud Enabled SwitchBot Devices & IR Devices will not work');
|
|
431
447
|
}
|
|
432
448
|
}
|
|
433
449
|
}
|
|
@@ -460,12 +476,12 @@ export class SwitchBotPlatform {
|
|
|
460
476
|
}
|
|
461
477
|
// Move openToken to token
|
|
462
478
|
if (!this.config.credentials.secret) {
|
|
463
|
-
this.warnLog('This plugin has been updated to use OpenAPI v1.1, config is set with openToken,'
|
|
479
|
+
await this.warnLog('This plugin has been updated to use OpenAPI v1.1, config is set with openToken,'
|
|
464
480
|
+ ' "openToken" cconfig has been moved to the "token" config');
|
|
465
481
|
this.errorLog('"secret" config is not populated, you must populate then please restart Homebridge.');
|
|
466
482
|
}
|
|
467
483
|
else {
|
|
468
|
-
this.warnLog('This plugin has been updated to use OpenAPI v1.1, config is set with openToken, '
|
|
484
|
+
await this.warnLog('This plugin has been updated to use OpenAPI v1.1, config is set with openToken, '
|
|
469
485
|
+ '"openToken" config has been moved to the "token" config, please restart Homebridge.');
|
|
470
486
|
}
|
|
471
487
|
// set the refresh token
|
|
@@ -476,10 +492,10 @@ export class SwitchBotPlatform {
|
|
|
476
492
|
await this.debugWarnLog(`token: ${pluginConfig.credentials.token}`);
|
|
477
493
|
// save the config, ensuring we maintain pretty json
|
|
478
494
|
writeFileSync(this.api.user.configPath(), JSON.stringify(currentConfig, null, 4));
|
|
479
|
-
this.verifyConfig();
|
|
495
|
+
await this.verifyConfig();
|
|
480
496
|
}
|
|
481
497
|
catch (e) {
|
|
482
|
-
this.errorLog(`Update Token: ${e}`);
|
|
498
|
+
await this.errorLog(`Update Token: ${e}`);
|
|
483
499
|
}
|
|
484
500
|
}
|
|
485
501
|
generateHeaders = () => {
|
|
@@ -499,326 +515,215 @@ export class SwitchBotPlatform {
|
|
|
499
515
|
'Content-Type': 'application/json',
|
|
500
516
|
};
|
|
501
517
|
};
|
|
502
|
-
/**
|
|
503
|
-
* this method discovers devices
|
|
504
|
-
*
|
|
505
|
-
const t = `${Date.now()}`;
|
|
506
|
-
const nonce = 'requestID';
|
|
507
|
-
const data = this.config.credentials?.token + t + nonce;
|
|
508
|
-
const signTerm = crypto.createHmac('sha256', this.config.credentials?.secret).update(Buffer.from(data, 'utf-8')).digest();
|
|
509
|
-
const sign = signTerm.toString('base64');
|
|
510
|
-
*/
|
|
511
518
|
async discoverDevices() {
|
|
512
|
-
if (this.config.credentials?.token) {
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
await this.
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
// SwitchBot Devices
|
|
532
|
-
const deviceLists = devicesAPI.body.deviceList;
|
|
533
|
-
await this.debugWarnLog(`DeviceLists: ${JSON.stringify(deviceLists)}`);
|
|
534
|
-
await this.debugWarnLog(`DeviceLists Length: ${deviceLists.length}`);
|
|
535
|
-
if (!this.config.options?.devices) {
|
|
536
|
-
await this.debugLog(`SwitchBot Device Config Not Set: ${JSON.stringify(this.config.options?.devices)}`);
|
|
537
|
-
if (deviceLists.length === 0) {
|
|
538
|
-
await this.debugLog(`SwitchBot API Has No Devices With Cloud Services Enabled: ${JSON.stringify(devicesAPI.body)}`);
|
|
539
|
-
}
|
|
540
|
-
else {
|
|
541
|
-
const devices = deviceLists.map((v) => v);
|
|
542
|
-
for (const device of devices) {
|
|
543
|
-
if (device.deviceType) {
|
|
544
|
-
if (device.configDeviceName) {
|
|
545
|
-
device.deviceName = device.configDeviceName;
|
|
546
|
-
}
|
|
547
|
-
this.createDevice(device);
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
else if (this.config.credentials?.token && this.config.options.devices) {
|
|
553
|
-
await this.debugLog(`SwitchBot Device Config Set: ${JSON.stringify(this.config.options?.devices)}`);
|
|
554
|
-
if (deviceLists.length === 0) {
|
|
555
|
-
await this.debugLog(`SwitchBot API Has No Devices With Cloud Services Enabled: ${JSON.stringify(devicesAPI.body)}`);
|
|
556
|
-
}
|
|
557
|
-
else {
|
|
558
|
-
const deviceConfigs = this.config.options?.devices;
|
|
559
|
-
const mergeBydeviceId = (a1, a2) => a1.map((itm) => ({
|
|
560
|
-
...a2.find((item) => item.deviceId.toUpperCase().replace(/[^A-Z0-9]+/g, '') === itm.deviceId.toUpperCase().replace(/[^A-Z0-9]+/g, '') && item),
|
|
561
|
-
...itm,
|
|
562
|
-
}));
|
|
563
|
-
const devices = mergeBydeviceId(deviceLists, deviceConfigs);
|
|
564
|
-
await this.debugLog(`SwitchBot Devices: ${JSON.stringify(devices)}`);
|
|
565
|
-
for (const device of devices) {
|
|
566
|
-
if (!device.deviceType) {
|
|
567
|
-
device.deviceType = device.configDeviceType;
|
|
568
|
-
this.errorLog(`API has displaying no deviceType: ${device.deviceType}, So using configDeviceType: ${device.configDeviceType}`);
|
|
569
|
-
}
|
|
570
|
-
if (device.deviceType) {
|
|
571
|
-
if (device.configDeviceName) {
|
|
572
|
-
device.deviceName = device.configDeviceName;
|
|
573
|
-
}
|
|
574
|
-
this.createDevice(device);
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
else {
|
|
580
|
-
this.errorLog('SwitchBot Token Supplied, Issue with Auth.');
|
|
581
|
-
}
|
|
582
|
-
if (devicesAPI.body.deviceList.length !== 0) {
|
|
583
|
-
this.infoLog(`Total SwitchBot Devices Found: ${devicesAPI.body.deviceList.length}`);
|
|
584
|
-
}
|
|
585
|
-
else {
|
|
586
|
-
await this.debugLog(`Total SwitchBot Devices Found: ${devicesAPI.body.deviceList.length}`);
|
|
587
|
-
}
|
|
588
|
-
// IR Devices
|
|
589
|
-
const irDeviceLists = devicesAPI.body.infraredRemoteList;
|
|
590
|
-
if (!this.config.options?.irdevices) {
|
|
591
|
-
await this.debugLog(`IR Device Config Not Set: ${JSON.stringify(this.config.options?.irdevices)}`);
|
|
592
|
-
const devices = irDeviceLists.map((v) => v);
|
|
593
|
-
for (const device of devices) {
|
|
594
|
-
if (device.remoteType) {
|
|
595
|
-
this.createIRDevice(device);
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
else {
|
|
600
|
-
await this.debugLog(`IR Device Config Set: ${JSON.stringify(this.config.options?.irdevices)}`);
|
|
601
|
-
const irDeviceConfig = this.config.options?.irdevices;
|
|
602
|
-
const mergeIRBydeviceId = (a1, a2) => a1.map((itm) => ({
|
|
603
|
-
...a2.find((item) => item.deviceId.toUpperCase().replace(/[^A-Z0-9]+/g, '') === itm.deviceId.toUpperCase().replace(/[^A-Z0-9]+/g, '') && item),
|
|
604
|
-
...itm,
|
|
605
|
-
}));
|
|
606
|
-
const devices = mergeIRBydeviceId(irDeviceLists, irDeviceConfig);
|
|
607
|
-
await this.debugLog(`IR Devices: ${JSON.stringify(devices)}`);
|
|
608
|
-
for (const device of devices) {
|
|
609
|
-
this.createIRDevice(device);
|
|
610
|
-
}
|
|
611
|
-
}
|
|
612
|
-
if (devicesAPI.body.infraredRemoteList.length !== 0) {
|
|
613
|
-
this.infoLog(`Total IR Devices Found: ${devicesAPI.body.infraredRemoteList.length}`);
|
|
614
|
-
}
|
|
615
|
-
else {
|
|
616
|
-
await this.debugLog(`Total IR Devices Found: ${devicesAPI.body.infraredRemoteList.length}`);
|
|
617
|
-
}
|
|
618
|
-
break;
|
|
619
|
-
}
|
|
620
|
-
else {
|
|
621
|
-
this.statusCode(statusCode);
|
|
622
|
-
this.statusCode(devicesAPI.statusCode);
|
|
623
|
-
if (statusCode === 500) {
|
|
624
|
-
retryCount++;
|
|
625
|
-
this.infoLog(`statusCode: ${statusCode} Attempt ${retryCount} of ${maxRetries}`);
|
|
626
|
-
await sleep(delayBetweenRetries);
|
|
627
|
-
}
|
|
628
|
-
}
|
|
519
|
+
if (!this.config.credentials?.token) {
|
|
520
|
+
return this.handleManualConfig();
|
|
521
|
+
}
|
|
522
|
+
let retryCount = 0;
|
|
523
|
+
const maxRetries = this.maxRetries;
|
|
524
|
+
const delayBetweenRetries = this.delayBetweenRetries;
|
|
525
|
+
await this.debugWarnLog(`Retry Count: ${retryCount}`);
|
|
526
|
+
await this.debugWarnLog(`Max Retries: ${maxRetries}`);
|
|
527
|
+
await this.debugWarnLog(`Delay Between Retries: ${delayBetweenRetries}`);
|
|
528
|
+
while (retryCount < maxRetries) {
|
|
529
|
+
try {
|
|
530
|
+
const { body, statusCode } = await request(Devices, { headers: this.generateHeaders() });
|
|
531
|
+
await this.debugWarnLog(`statusCode: ${statusCode}`);
|
|
532
|
+
const devicesAPI = await body.json();
|
|
533
|
+
await this.debugWarnLog(`devicesAPI: ${JSON.stringify(devicesAPI)}`);
|
|
534
|
+
if (this.isSuccessfulResponse(statusCode, devicesAPI.statusCode)) {
|
|
535
|
+
await this.handleDevices(devicesAPI.body.deviceList);
|
|
536
|
+
await this.handleIRDevices(devicesAPI.body.infraredRemoteList);
|
|
537
|
+
break;
|
|
629
538
|
}
|
|
630
|
-
|
|
539
|
+
else {
|
|
540
|
+
await this.handleErrorResponse(statusCode, devicesAPI.statusCode, retryCount, maxRetries, delayBetweenRetries);
|
|
631
541
|
retryCount++;
|
|
632
|
-
this.debugErrorLog(`Failed to Discover Devices, Error Message: ${JSON.stringify(e.message)}, Submit Bugs Here: ` + 'https://tinyurl.com/SwitchBotBug');
|
|
633
|
-
await this.debugErrorLog(`Failed to Discover Devices, Error: ${e}`);
|
|
634
542
|
}
|
|
635
543
|
}
|
|
544
|
+
catch (e) {
|
|
545
|
+
retryCount++;
|
|
546
|
+
await this.debugErrorLog(`Failed to Discover Devices, Error Message: ${JSON.stringify(e.message)},`
|
|
547
|
+
+ ' Submit Bugs Here: https://tinyurl.com/SwitchBotBug');
|
|
548
|
+
await this.debugErrorLog(`Failed to Discover Devices, Error: ${e}`);
|
|
549
|
+
}
|
|
636
550
|
}
|
|
637
|
-
|
|
551
|
+
}
|
|
552
|
+
async handleManualConfig() {
|
|
553
|
+
if (this.config.options?.devices) {
|
|
638
554
|
await this.debugLog(`SwitchBot Device Manual Config Set: ${JSON.stringify(this.config.options?.devices)}`);
|
|
639
|
-
const
|
|
640
|
-
const devices = deviceConfigs.map((v) => v);
|
|
555
|
+
const devices = this.config.options.devices.map((v) => v);
|
|
641
556
|
for (const device of devices) {
|
|
642
557
|
device.deviceType = device.configDeviceType;
|
|
643
558
|
device.deviceName = device.configDeviceName;
|
|
644
559
|
if (device.deviceType) {
|
|
645
|
-
this.createDevice(device);
|
|
560
|
+
await this.createDevice(device);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
else {
|
|
565
|
+
await this.errorLog('Neither SwitchBot Token or Device Config are set.');
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
isSuccessfulResponse(statusCode, apiStatusCode) {
|
|
569
|
+
return (statusCode === 200 || statusCode === 100) && (apiStatusCode === 200 || apiStatusCode === 100);
|
|
570
|
+
}
|
|
571
|
+
async handleDevices(deviceLists) {
|
|
572
|
+
if (!this.config.options?.devices) {
|
|
573
|
+
await this.debugLog(`SwitchBot Device Config Not Set: ${JSON.stringify(this.config.options?.devices)}`);
|
|
574
|
+
if (deviceLists.length === 0) {
|
|
575
|
+
await this.debugLog('SwitchBot API Has No Devices With Cloud Services Enabled');
|
|
576
|
+
}
|
|
577
|
+
else {
|
|
578
|
+
for (const device of deviceLists) {
|
|
579
|
+
if (device.deviceType) {
|
|
580
|
+
if (device.configDeviceName) {
|
|
581
|
+
device.deviceName = device.configDeviceName;
|
|
582
|
+
}
|
|
583
|
+
await this.createDevice(device);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
else {
|
|
589
|
+
await this.debugLog(`SwitchBot Device Config Set: ${JSON.stringify(this.config.options?.devices)}`);
|
|
590
|
+
const devices = this.mergeByDeviceId(deviceLists, this.config.options.devices);
|
|
591
|
+
await this.debugLog(`SwitchBot Devices: ${JSON.stringify(devices)}`);
|
|
592
|
+
for (const device of devices) {
|
|
593
|
+
if (!device.deviceType && device.configDeviceType) {
|
|
594
|
+
device.deviceType = device.configDeviceType;
|
|
595
|
+
await this.warnLog(`API is displaying no deviceType: ${device.deviceType}, So using configDeviceType: ${device.configDeviceType}`);
|
|
596
|
+
}
|
|
597
|
+
else if (!device.deviceType && !device.configDeviceName) {
|
|
598
|
+
await this.errorLog('No deviceType or configDeviceType for device. No device will be created.');
|
|
599
|
+
}
|
|
600
|
+
if (device.deviceType) {
|
|
601
|
+
if (device.configDeviceName) {
|
|
602
|
+
device.deviceName = device.configDeviceName;
|
|
603
|
+
}
|
|
604
|
+
await this.createDevice(device);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
async handleIRDevices(irDeviceLists) {
|
|
610
|
+
if (!this.config.options?.irdevices) {
|
|
611
|
+
await this.debugLog(`IR Device Config Not Set: ${JSON.stringify(this.config.options?.irdevices)}`);
|
|
612
|
+
for (const device of irDeviceLists) {
|
|
613
|
+
if (device.remoteType) {
|
|
614
|
+
await this.createIRDevice(device);
|
|
646
615
|
}
|
|
647
616
|
}
|
|
648
617
|
}
|
|
649
618
|
else {
|
|
650
|
-
this.
|
|
619
|
+
await this.debugLog(`IR Device Config Set: ${JSON.stringify(this.config.options?.irdevices)}`);
|
|
620
|
+
const devices = this.mergeByDeviceId(irDeviceLists, this.config.options.irdevices);
|
|
621
|
+
await this.debugLog(`IR Devices: ${JSON.stringify(devices)}`);
|
|
622
|
+
for (const device of devices) {
|
|
623
|
+
await this.createIRDevice(device);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
mergeByDeviceId(a1, a2) {
|
|
628
|
+
const normalizeDeviceId = (deviceId) => deviceId.toUpperCase().replace(/[^A-Z0-9]+/g, '');
|
|
629
|
+
return a1.map(itm => {
|
|
630
|
+
const matchingItem = a2.find(item => normalizeDeviceId(item.deviceId) === normalizeDeviceId(itm.deviceId));
|
|
631
|
+
return { ...matchingItem, ...itm };
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
async handleErrorResponse(statusCode, apiStatusCode, retryCount, maxRetries, delayBetweenRetries) {
|
|
635
|
+
await this.statusCode(statusCode);
|
|
636
|
+
await this.statusCode(apiStatusCode);
|
|
637
|
+
if (statusCode === 500) {
|
|
638
|
+
this.infoLog(`statusCode: ${statusCode} Attempt ${retryCount + 1} of ${maxRetries}`);
|
|
639
|
+
await sleep(delayBetweenRetries);
|
|
651
640
|
}
|
|
652
641
|
}
|
|
653
642
|
async createDevice(device) {
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
await this.debugLog(`Discovered ${device.deviceType}: ${device.deviceId}`);
|
|
696
|
-
this.createContact(device);
|
|
697
|
-
break;
|
|
698
|
-
case 'Curtain':
|
|
699
|
-
case 'Curtain3':
|
|
700
|
-
await this.debugLog(`Discovered ${device.deviceType} ${device.deviceName}: ${device.deviceId}`);
|
|
701
|
-
this.createCurtain(device);
|
|
702
|
-
break;
|
|
703
|
-
case 'Blind Tilt':
|
|
704
|
-
await this.debugLog(`Discovered ${device.deviceType} ${device.deviceName}: ${device.deviceId}`);
|
|
705
|
-
this.createBlindTilt(device);
|
|
706
|
-
break;
|
|
707
|
-
case 'Plug':
|
|
708
|
-
case 'Plug Mini (US)':
|
|
709
|
-
case 'Plug Mini (JP)':
|
|
710
|
-
await this.debugLog(`Discovered ${device.deviceType}: ${device.deviceId}`);
|
|
711
|
-
this.createPlug(device);
|
|
712
|
-
break;
|
|
713
|
-
case 'Smart Lock':
|
|
714
|
-
case 'Smart Lock Pro':
|
|
715
|
-
await this.debugLog(`Discovered ${device.deviceType}: ${device.deviceId}`);
|
|
716
|
-
this.createLock(device);
|
|
717
|
-
break;
|
|
718
|
-
case 'Color Bulb':
|
|
719
|
-
await this.debugLog(`Discovered ${device.deviceType}: ${device.deviceId}`);
|
|
720
|
-
this.createColorBulb(device);
|
|
721
|
-
break;
|
|
722
|
-
case 'K10+':
|
|
723
|
-
case 'WoSweeper':
|
|
724
|
-
case 'WoSweeperMini':
|
|
725
|
-
case 'Robot Vacuum Cleaner S1':
|
|
726
|
-
case 'Robot Vacuum Cleaner S1 Plus':
|
|
727
|
-
case 'Robot Vacuum Cleaner S10':
|
|
728
|
-
await this.debugLog(`Discovered ${device.deviceType}: ${device.deviceId}`);
|
|
729
|
-
this.createRobotVacuumCleaner(device);
|
|
730
|
-
break;
|
|
731
|
-
case 'Ceiling Light':
|
|
732
|
-
case 'Ceiling Light Pro':
|
|
733
|
-
await this.debugLog(`Discovered ${device.deviceType}: ${device.deviceId}`);
|
|
734
|
-
this.createCeilingLight(device);
|
|
735
|
-
break;
|
|
736
|
-
case 'Strip Light':
|
|
737
|
-
await this.debugLog(`Discovered ${device.deviceType}: ${device.deviceId}`);
|
|
738
|
-
this.createStripLight(device);
|
|
739
|
-
break;
|
|
740
|
-
case 'Battery Circulator Fan':
|
|
741
|
-
await this.debugLog(`Discovered ${device.deviceType}: ${device.deviceId}`);
|
|
742
|
-
this.createFan(device);
|
|
743
|
-
break;
|
|
744
|
-
case 'Remote':
|
|
745
|
-
case 'Indoor Cam':
|
|
746
|
-
case 'remote with screen+':
|
|
747
|
-
await this.debugLog(`Discovered ${device.deviceType}: ${device.deviceId}, is currently not supported, device: ${JSON.stringify(device)}`);
|
|
748
|
-
break;
|
|
749
|
-
default:
|
|
750
|
-
this.warnLog(`Device: ${device.deviceName} with Device Type: ${device.deviceType}, is currently not supported.`, +`Submit Feature Requests Here: https://tinyurl.com/SwitchBotFeatureRequest, device: ${JSON.stringify(device)}`);
|
|
643
|
+
const deviceTypeHandlers = {
|
|
644
|
+
'Humidifier': this.createHumidifier.bind(this),
|
|
645
|
+
'Hub 2': this.createHub2.bind(this),
|
|
646
|
+
'Bot': this.createBot.bind(this),
|
|
647
|
+
'Meter': this.createMeter.bind(this),
|
|
648
|
+
'MeterPlus': this.createMeterPlus.bind(this),
|
|
649
|
+
'Meter Plus (JP)': this.createMeterPlus.bind(this),
|
|
650
|
+
'WoIOSensor': this.createIOSensor.bind(this),
|
|
651
|
+
'Water Detector': this.createWaterDetector.bind(this),
|
|
652
|
+
'Motion Sensor': this.createMotion.bind(this),
|
|
653
|
+
'Contact Sensor': this.createContact.bind(this),
|
|
654
|
+
'Curtain': this.createCurtain.bind(this),
|
|
655
|
+
'Curtain3': this.createCurtain.bind(this),
|
|
656
|
+
'Blind Tilt': this.createBlindTilt.bind(this),
|
|
657
|
+
'Plug': this.createPlug.bind(this),
|
|
658
|
+
'Plug Mini (US)': this.createPlug.bind(this),
|
|
659
|
+
'Plug Mini (JP)': this.createPlug.bind(this),
|
|
660
|
+
'Smart Lock': this.createLock.bind(this),
|
|
661
|
+
'Smart Lock Pro': this.createLock.bind(this),
|
|
662
|
+
'Color Bulb': this.createColorBulb.bind(this),
|
|
663
|
+
'K10+': this.createRobotVacuumCleaner.bind(this),
|
|
664
|
+
'WoSweeper': this.createRobotVacuumCleaner.bind(this),
|
|
665
|
+
'WoSweeperMini': this.createRobotVacuumCleaner.bind(this),
|
|
666
|
+
'Robot Vacuum Cleaner S1': this.createRobotVacuumCleaner.bind(this),
|
|
667
|
+
'Robot Vacuum Cleaner S1 Plus': this.createRobotVacuumCleaner.bind(this),
|
|
668
|
+
'Robot Vacuum Cleaner S10': this.createRobotVacuumCleaner.bind(this),
|
|
669
|
+
'Ceiling Light': this.createCeilingLight.bind(this),
|
|
670
|
+
'Ceiling Light Pro': this.createCeilingLight.bind(this),
|
|
671
|
+
'Strip Light': this.createStripLight.bind(this),
|
|
672
|
+
'Battery Circulator Fan': this.createFan.bind(this),
|
|
673
|
+
};
|
|
674
|
+
if (deviceTypeHandlers[device.deviceType]) {
|
|
675
|
+
await this.debugLog(`Discovered ${device.deviceType}: ${device.deviceId}`);
|
|
676
|
+
await deviceTypeHandlers[device.deviceType](device);
|
|
677
|
+
}
|
|
678
|
+
else if (['Hub Mini', 'Hub Plus', 'Remote', 'Indoor Cam', 'remote with screen'].includes(device.deviceType)) {
|
|
679
|
+
await this.debugLog(`Discovered ${device.deviceType}: ${device.deviceId}, is currently not supported, device: ${JSON.stringify(device)}`);
|
|
680
|
+
}
|
|
681
|
+
else {
|
|
682
|
+
await this.warnLog(`Device: ${device.deviceName} with Device Type: ${device.deviceType}, is currently not supported.`
|
|
683
|
+
+ ` Submit Feature Requests Here: https://tinyurl.com/SwitchBotFeatureRequest, device: ${JSON.stringify(device)}`);
|
|
751
684
|
}
|
|
752
685
|
}
|
|
753
686
|
async createIRDevice(device) {
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
case 'Air Purifier':
|
|
795
|
-
case 'DIY Air Purifier':
|
|
796
|
-
await this.debugLog(`Discovered ${device.remoteType}: ${device.deviceId}`);
|
|
797
|
-
this.createAirPurifier(device);
|
|
798
|
-
break;
|
|
799
|
-
case 'Water Heater':
|
|
800
|
-
case 'DIY Water Heater':
|
|
801
|
-
await this.debugLog(`Discovered ${device.remoteType}: ${device.deviceId}`);
|
|
802
|
-
this.createWaterHeater(device);
|
|
803
|
-
break;
|
|
804
|
-
case 'Vacuum Cleaner':
|
|
805
|
-
case 'DIY Vacuum Cleaner':
|
|
806
|
-
await this.debugLog(`Discovered ${device.remoteType}: ${device.deviceId}`);
|
|
807
|
-
this.createVacuumCleaner(device);
|
|
808
|
-
break;
|
|
809
|
-
case 'Camera':
|
|
810
|
-
case 'DIY Camera':
|
|
811
|
-
await this.debugLog(`Discovered ${device.remoteType}: ${device.deviceId}`);
|
|
812
|
-
this.createCamera(device);
|
|
813
|
-
break;
|
|
814
|
-
case 'Others':
|
|
815
|
-
await this.debugLog(`Discovered ${device.remoteType}: ${device.deviceId}`);
|
|
816
|
-
this.createOthers(device);
|
|
817
|
-
break;
|
|
818
|
-
default:
|
|
819
|
-
await this.debugLog(`Unsupported Device: ${JSON.stringify(device)}`);
|
|
820
|
-
this.warnLog(`Device: ${device.deviceName} with Device Type: ${device.remoteType}, is currently not supported.`);
|
|
821
|
-
this.warnLog('Submit Feature Requests Here: ' + 'https://tinyurl.com/SwitchBotFeatureRequest');
|
|
687
|
+
device.connectionType = device.connectionType ?? 'OpenAPI';
|
|
688
|
+
const deviceTypeHandlers = {
|
|
689
|
+
'TV': this.createTV.bind(this),
|
|
690
|
+
'DIY TV': this.createTV.bind(this),
|
|
691
|
+
'Projector': this.createTV.bind(this),
|
|
692
|
+
'DIY Projector': this.createTV.bind(this),
|
|
693
|
+
'Set Top Box': this.createTV.bind(this),
|
|
694
|
+
'DIY Set Top Box': this.createTV.bind(this),
|
|
695
|
+
'IPTV': this.createTV.bind(this),
|
|
696
|
+
'DIY IPTV': this.createTV.bind(this),
|
|
697
|
+
'DVD': this.createTV.bind(this),
|
|
698
|
+
'DIY DVD': this.createTV.bind(this),
|
|
699
|
+
'Speaker': this.createTV.bind(this),
|
|
700
|
+
'DIY Speaker': this.createTV.bind(this),
|
|
701
|
+
'Fan': this.createIRFan.bind(this),
|
|
702
|
+
'DIY Fan': this.createIRFan.bind(this),
|
|
703
|
+
'Air Conditioner': this.createAirConditioner.bind(this),
|
|
704
|
+
'DIY Air Conditioner': this.createAirConditioner.bind(this),
|
|
705
|
+
'Light': this.createLight.bind(this),
|
|
706
|
+
'DIY Light': this.createLight.bind(this),
|
|
707
|
+
'Air Purifier': this.createAirPurifier.bind(this),
|
|
708
|
+
'DIY Air Purifier': this.createAirPurifier.bind(this),
|
|
709
|
+
'Water Heater': this.createWaterHeater.bind(this),
|
|
710
|
+
'DIY Water Heater': this.createWaterHeater.bind(this),
|
|
711
|
+
'Vacuum Cleaner': this.createVacuumCleaner.bind(this),
|
|
712
|
+
'DIY Vacuum Cleaner': this.createVacuumCleaner.bind(this),
|
|
713
|
+
'Camera': this.createCamera.bind(this),
|
|
714
|
+
'DIY Camera': this.createCamera.bind(this),
|
|
715
|
+
'Others': this.createOthers.bind(this),
|
|
716
|
+
};
|
|
717
|
+
if (deviceTypeHandlers[device.remoteType]) {
|
|
718
|
+
await this.debugLog(`Discovered ${device.remoteType}: ${device.deviceId}`);
|
|
719
|
+
if (device.remoteType.startsWith('DIY') && device.external === undefined) {
|
|
720
|
+
device.external = true;
|
|
721
|
+
}
|
|
722
|
+
await deviceTypeHandlers[device.remoteType](device);
|
|
723
|
+
}
|
|
724
|
+
else {
|
|
725
|
+
await this.warnLog(`Device: ${device.deviceName} with Device Type: ${device.remoteType}, is currently not supported.`
|
|
726
|
+
+ ` Submit Feature Requests Here: https://tinyurl.com/SwitchBotFeatureRequest, device: ${JSON.stringify(device)}`);
|
|
822
727
|
}
|
|
823
728
|
}
|
|
824
729
|
async createHumidifier(device) {
|
|
@@ -1979,7 +1884,6 @@ export class SwitchBotPlatform {
|
|
|
1979
1884
|
? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName)
|
|
1980
1885
|
: await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName);
|
|
1981
1886
|
accessory.context.connectionType = await this.connectionType(device);
|
|
1982
|
-
accessory.context.connectionType = device.connectionType;
|
|
1983
1887
|
accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0';
|
|
1984
1888
|
const newOrExternal = !device.external ? 'Adding new' : 'Loading external';
|
|
1985
1889
|
await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`);
|
|
@@ -1987,7 +1891,7 @@ export class SwitchBotPlatform {
|
|
|
1987
1891
|
// this is imported from `platformAccessory.ts`
|
|
1988
1892
|
new TV(this, accessory, device);
|
|
1989
1893
|
await this.debugLog(`${device.remoteType} uuid: ${device.deviceId}-${device.remoteType}, (${accessory.UUID})`);
|
|
1990
|
-
this.
|
|
1894
|
+
this.externalOrPlatform(device, accessory);
|
|
1991
1895
|
this.accessories.push(accessory);
|
|
1992
1896
|
}
|
|
1993
1897
|
else {
|
|
@@ -2037,7 +1941,6 @@ export class SwitchBotPlatform {
|
|
|
2037
1941
|
? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName)
|
|
2038
1942
|
: await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName);
|
|
2039
1943
|
accessory.context.connectionType = await this.connectionType(device);
|
|
2040
|
-
accessory.context.connectionType = device.connectionType;
|
|
2041
1944
|
accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0';
|
|
2042
1945
|
const newOrExternal = !device.external ? 'Adding new' : 'Loading external';
|
|
2043
1946
|
await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`);
|
|
@@ -2046,7 +1949,7 @@ export class SwitchBotPlatform {
|
|
|
2046
1949
|
new IRFan(this, accessory, device);
|
|
2047
1950
|
await this.debugLog(`${device.remoteType} uuid: ${device.deviceId}-${device.remoteType}, (${accessory.UUID})`);
|
|
2048
1951
|
// publish device externally or link the accessory to your platform
|
|
2049
|
-
this.
|
|
1952
|
+
this.externalOrPlatform(device, accessory);
|
|
2050
1953
|
this.accessories.push(accessory);
|
|
2051
1954
|
}
|
|
2052
1955
|
else {
|
|
@@ -2096,7 +1999,6 @@ export class SwitchBotPlatform {
|
|
|
2096
1999
|
? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName)
|
|
2097
2000
|
: await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName);
|
|
2098
2001
|
accessory.context.connectionType = await this.connectionType(device);
|
|
2099
|
-
accessory.context.connectionType = device.connectionType;
|
|
2100
2002
|
accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0';
|
|
2101
2003
|
const newOrExternal = !device.external ? 'Adding new' : 'Loading external';
|
|
2102
2004
|
await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`);
|
|
@@ -2105,7 +2007,7 @@ export class SwitchBotPlatform {
|
|
|
2105
2007
|
new Light(this, accessory, device);
|
|
2106
2008
|
await this.debugLog(`${device.remoteType} uuid: ${device.deviceId}-${device.remoteType}, (${accessory.UUID})`);
|
|
2107
2009
|
// publish device externally or link the accessory to your platform
|
|
2108
|
-
this.
|
|
2010
|
+
this.externalOrPlatform(device, accessory);
|
|
2109
2011
|
this.accessories.push(accessory);
|
|
2110
2012
|
}
|
|
2111
2013
|
else {
|
|
@@ -2155,7 +2057,6 @@ export class SwitchBotPlatform {
|
|
|
2155
2057
|
? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName)
|
|
2156
2058
|
: await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName);
|
|
2157
2059
|
accessory.context.connectionType = await this.connectionType(device);
|
|
2158
|
-
accessory.context.connectionType = device.connectionType;
|
|
2159
2060
|
accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0';
|
|
2160
2061
|
const newOrExternal = !device.external ? 'Adding new' : 'Loading external';
|
|
2161
2062
|
await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`);
|
|
@@ -2164,7 +2065,7 @@ export class SwitchBotPlatform {
|
|
|
2164
2065
|
new AirConditioner(this, accessory, device);
|
|
2165
2066
|
await this.debugLog(`${device.remoteType} uuid: ${device.deviceId}-${device.remoteType}, (${accessory.UUID})`);
|
|
2166
2067
|
// publish device externally or link the accessory to your platform
|
|
2167
|
-
this.
|
|
2068
|
+
this.externalOrPlatform(device, accessory);
|
|
2168
2069
|
this.accessories.push(accessory);
|
|
2169
2070
|
}
|
|
2170
2071
|
else {
|
|
@@ -2214,7 +2115,6 @@ export class SwitchBotPlatform {
|
|
|
2214
2115
|
? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName)
|
|
2215
2116
|
: await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName);
|
|
2216
2117
|
accessory.context.connectionType = await this.connectionType(device);
|
|
2217
|
-
accessory.context.connectionType = device.connectionType;
|
|
2218
2118
|
accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0';
|
|
2219
2119
|
const newOrExternal = !device.external ? 'Adding new' : 'Loading external';
|
|
2220
2120
|
await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`);
|
|
@@ -2223,7 +2123,7 @@ export class SwitchBotPlatform {
|
|
|
2223
2123
|
new AirPurifier(this, accessory, device);
|
|
2224
2124
|
await this.debugLog(`${device.remoteType} uuid: ${device.deviceId}-${device.remoteType}, (${accessory.UUID})`);
|
|
2225
2125
|
// publish device externally or link the accessory to your platform
|
|
2226
|
-
this.
|
|
2126
|
+
this.externalOrPlatform(device, accessory);
|
|
2227
2127
|
this.accessories.push(accessory);
|
|
2228
2128
|
}
|
|
2229
2129
|
else {
|
|
@@ -2273,7 +2173,6 @@ export class SwitchBotPlatform {
|
|
|
2273
2173
|
? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName)
|
|
2274
2174
|
: await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName);
|
|
2275
2175
|
accessory.context.connectionType = await this.connectionType(device);
|
|
2276
|
-
accessory.context.connectionType = device.connectionType;
|
|
2277
2176
|
accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0';
|
|
2278
2177
|
const newOrExternal = !device.external ? 'Adding new' : 'Loading external';
|
|
2279
2178
|
await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`);
|
|
@@ -2282,7 +2181,7 @@ export class SwitchBotPlatform {
|
|
|
2282
2181
|
new WaterHeater(this, accessory, device);
|
|
2283
2182
|
await this.debugLog(`${device.remoteType} uuid: ${device.deviceId}-${device.remoteType}, (${accessory.UUID})`);
|
|
2284
2183
|
// publish device externally or link the accessory to your platform
|
|
2285
|
-
this.
|
|
2184
|
+
this.externalOrPlatform(device, accessory);
|
|
2286
2185
|
this.accessories.push(accessory);
|
|
2287
2186
|
}
|
|
2288
2187
|
else {
|
|
@@ -2332,7 +2231,6 @@ export class SwitchBotPlatform {
|
|
|
2332
2231
|
? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName)
|
|
2333
2232
|
: await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName);
|
|
2334
2233
|
accessory.context.connectionType = await this.connectionType(device);
|
|
2335
|
-
accessory.context.connectionType = device.connectionType;
|
|
2336
2234
|
accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0';
|
|
2337
2235
|
const newOrExternal = !device.external ? 'Adding new' : 'Loading external';
|
|
2338
2236
|
await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`);
|
|
@@ -2341,7 +2239,7 @@ export class SwitchBotPlatform {
|
|
|
2341
2239
|
new VacuumCleaner(this, accessory, device);
|
|
2342
2240
|
await this.debugLog(`${device.remoteType} uuid: ${device.deviceId}-${device.remoteType}, (${accessory.UUID})`);
|
|
2343
2241
|
// publish device externally or link the accessory to your platform
|
|
2344
|
-
this.
|
|
2242
|
+
this.externalOrPlatform(device, accessory);
|
|
2345
2243
|
this.accessories.push(accessory);
|
|
2346
2244
|
}
|
|
2347
2245
|
else {
|
|
@@ -2391,7 +2289,6 @@ export class SwitchBotPlatform {
|
|
|
2391
2289
|
? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName)
|
|
2392
2290
|
: await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName);
|
|
2393
2291
|
accessory.context.connectionType = await this.connectionType(device);
|
|
2394
|
-
accessory.context.connectionType = device.connectionType;
|
|
2395
2292
|
accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0';
|
|
2396
2293
|
const newOrExternal = !device.external ? 'Adding new' : 'Loading external';
|
|
2397
2294
|
await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`);
|
|
@@ -2400,7 +2297,7 @@ export class SwitchBotPlatform {
|
|
|
2400
2297
|
new Camera(this, accessory, device);
|
|
2401
2298
|
await this.debugLog(`${device.remoteType} uuid: ${device.deviceId}-${device.remoteType}, (${accessory.UUID})`);
|
|
2402
2299
|
// publish device externally or link the accessory to your platform
|
|
2403
|
-
this.
|
|
2300
|
+
this.externalOrPlatform(device, accessory);
|
|
2404
2301
|
this.accessories.push(accessory);
|
|
2405
2302
|
}
|
|
2406
2303
|
else {
|
|
@@ -2450,7 +2347,6 @@ export class SwitchBotPlatform {
|
|
|
2450
2347
|
? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName)
|
|
2451
2348
|
: await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName);
|
|
2452
2349
|
accessory.context.connectionType = await this.connectionType(device);
|
|
2453
|
-
accessory.context.connectionType = device.connectionType;
|
|
2454
2350
|
accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0';
|
|
2455
2351
|
const newOrExternal = !device.external ? 'Adding new' : 'Loading external';
|
|
2456
2352
|
await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`);
|
|
@@ -2459,7 +2355,7 @@ export class SwitchBotPlatform {
|
|
|
2459
2355
|
new Others(this, accessory, device);
|
|
2460
2356
|
await this.debugLog(`${device.remoteType} uuid: ${device.deviceId}-${device.remoteType}, (${accessory.UUID})`);
|
|
2461
2357
|
// publish device externally or link the accessory to your platform
|
|
2462
|
-
this.
|
|
2358
|
+
this.externalOrPlatform(device, accessory);
|
|
2463
2359
|
this.accessories.push(accessory);
|
|
2464
2360
|
}
|
|
2465
2361
|
else {
|
|
@@ -2467,13 +2363,6 @@ export class SwitchBotPlatform {
|
|
|
2467
2363
|
}
|
|
2468
2364
|
}
|
|
2469
2365
|
async registerCurtains(device) {
|
|
2470
|
-
/*function isCurtainDevice(device: device & devicesConfig): device is (curtain | curtain3) & devicesConfig {
|
|
2471
|
-
return device.deviceType === 'Curtain' || device.deviceType === 'Curtain3';
|
|
2472
|
-
}
|
|
2473
|
-
|
|
2474
|
-
function isBlindTiltDevice(device: device & devicesConfig): device is blindTilt & devicesConfig {
|
|
2475
|
-
return device.deviceType === 'Blind Tilt';
|
|
2476
|
-
}*/
|
|
2477
2366
|
let registerWindowCovering;
|
|
2478
2367
|
if (isCurtainDevice(device)) {
|
|
2479
2368
|
await this.debugWarnLog(`deviceName: ${device.deviceName} deviceId: ${device.deviceId}, curtainDevicesIds: ${device.curtainDevicesIds},`
|
|
@@ -2631,103 +2520,62 @@ export class SwitchBotPlatform {
|
|
|
2631
2520
|
}
|
|
2632
2521
|
return registerDevice;
|
|
2633
2522
|
}
|
|
2634
|
-
async externalOrPlatformIR(device, accessory) {
|
|
2635
|
-
/**
|
|
2636
|
-
* Publish as external accessory
|
|
2637
|
-
* Only one TV can exist per bridge, to bypass this limitation, you should
|
|
2638
|
-
* publish your TV as an external accessory.
|
|
2639
|
-
*/
|
|
2640
|
-
if (device.external) {
|
|
2641
|
-
await this.debugWarnLog(`${accessory.displayName} External Accessory Mode`);
|
|
2642
|
-
this.externalAccessory(accessory);
|
|
2643
|
-
}
|
|
2644
|
-
else {
|
|
2645
|
-
await this.debugLog(`${accessory.displayName} External Accessory Mode: ${device.external}`);
|
|
2646
|
-
this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);
|
|
2647
|
-
}
|
|
2648
|
-
}
|
|
2649
2523
|
async externalOrPlatform(device, accessory) {
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2524
|
+
const { displayName } = accessory;
|
|
2525
|
+
const isExternal = device.external ?? false;
|
|
2526
|
+
if (isExternal) {
|
|
2527
|
+
await this.debugWarnLog(`${displayName} External Accessory Mode`);
|
|
2528
|
+
this.api.publishExternalAccessories(PLUGIN_NAME, [accessory]);
|
|
2653
2529
|
}
|
|
2654
2530
|
else {
|
|
2655
|
-
await this.debugLog(`${
|
|
2531
|
+
await this.debugLog(`${displayName} External Accessory Mode: ${isExternal}`);
|
|
2656
2532
|
this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);
|
|
2657
2533
|
}
|
|
2658
2534
|
}
|
|
2659
|
-
async externalAccessory(accessory) {
|
|
2660
|
-
this.api.publishExternalAccessories(PLUGIN_NAME, [accessory]);
|
|
2661
|
-
}
|
|
2662
2535
|
unregisterPlatformAccessories(existingAccessory) {
|
|
2536
|
+
const { displayName } = existingAccessory;
|
|
2663
2537
|
// remove platform accessories when no longer present
|
|
2664
2538
|
this.api.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [existingAccessory]);
|
|
2665
|
-
this.warnLog(`Removing existing accessory from cache: ${
|
|
2539
|
+
this.warnLog(`Removing existing accessory from cache: ${displayName}`);
|
|
2666
2540
|
}
|
|
2541
|
+
/**
|
|
2542
|
+
* Handles the status codes returned by the device and logs appropriate messages.
|
|
2543
|
+
*
|
|
2544
|
+
* @param statusCode - The status code returned by the device.
|
|
2545
|
+
* @returns A promise that resolves when the logging is complete.
|
|
2546
|
+
*/
|
|
2667
2547
|
async statusCode(statusCode) {
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
await this.errorLog('Unauthorized, Authorization for the API is required, but the request has not been authenticated, '
|
|
2700
|
-
+ `statusCode: ${statusCode}`);
|
|
2701
|
-
break;
|
|
2702
|
-
case 403:
|
|
2703
|
-
await this.errorLog('Forbidden, The request has been authenticated but does not have appropriate permissions, or a requested resource is not '
|
|
2704
|
-
+ `found, statusCode: ${statusCode}`);
|
|
2705
|
-
break;
|
|
2706
|
-
case 404:
|
|
2707
|
-
await this.errorLog(`Not Found, Specifies the requested path does not exist, statusCode: ${statusCode}`);
|
|
2708
|
-
break;
|
|
2709
|
-
case 406:
|
|
2710
|
-
await this.errorLog('Not Acceptable, The client has requested a MIME type via the Accept header for a value not supported by the server, '
|
|
2711
|
-
+ `statusCode: ${statusCode}`);
|
|
2712
|
-
break;
|
|
2713
|
-
case 415:
|
|
2714
|
-
await this.errorLog('Unsupported Media Type, The client has defined a contentType header that is not supported by the server, '
|
|
2715
|
-
+ `statusCode: ${statusCode}`);
|
|
2716
|
-
break;
|
|
2717
|
-
case 422:
|
|
2718
|
-
await this.errorLog('Unprocessable Entity, The client has made a valid request, but the server cannot process it. This is often used for '
|
|
2719
|
-
+ `APIs for which certain limits have been exceeded, statusCode: ${statusCode}`);
|
|
2720
|
-
break;
|
|
2721
|
-
case 429:
|
|
2722
|
-
await this.errorLog('Too Many Requests, The client has exceeded the number of requests allowed for a given time window, '
|
|
2723
|
-
+ `statusCode: ${statusCode}`);
|
|
2724
|
-
break;
|
|
2725
|
-
case 500:
|
|
2726
|
-
await this.errorLog('Internal Server Error, An unexpected error on the SmartThings servers has occurred. These errors should be rare, '
|
|
2727
|
-
+ `statusCode: ${statusCode}`);
|
|
2728
|
-
break;
|
|
2729
|
-
default:
|
|
2730
|
-
await this.errorLog(`Unknown statusCode, statusCode: ${statusCode}, Submit Bugs Here: ' + 'https://tinyurl.com/SwitchBotBug`);
|
|
2548
|
+
const messages = {
|
|
2549
|
+
151: `Command not supported by this device type, statusCode: ${statusCode}, Submit Feature Request Here:
|
|
2550
|
+
https://tinyurl.com/SwitchBotFeatureRequest`,
|
|
2551
|
+
152: `Device not found, statusCode: ${statusCode}`,
|
|
2552
|
+
160: `Command is not supported, statusCode: ${statusCode}, Submit Bugs Here: https://tinyurl.com/SwitchBotBug`,
|
|
2553
|
+
161: `Device is offline, statusCode: ${statusCode}`,
|
|
2554
|
+
171: `is offline, statusCode: ${statusCode}`,
|
|
2555
|
+
190: `Requests reached the daily limit, statusCode: ${statusCode}`,
|
|
2556
|
+
100: `Command successfully sent, statusCode: ${statusCode}`,
|
|
2557
|
+
200: `Request successful, statusCode: ${statusCode}`,
|
|
2558
|
+
400: `Bad Request, The client has issued an invalid request. This is commonly used to specify validation errors in a request payload,
|
|
2559
|
+
statusCode: ${statusCode}`,
|
|
2560
|
+
401: `Unauthorized, Authorization for the API is required, but the request has not been authenticated, statusCode: ${statusCode}`,
|
|
2561
|
+
403: `Forbidden, The request has been authenticated but does not have appropriate permissions, or a requested resource is not found,
|
|
2562
|
+
statusCode: ${statusCode}`,
|
|
2563
|
+
404: `Not Found, Specifies the requested path does not exist, statusCode: ${statusCode}`,
|
|
2564
|
+
406: `Not Acceptable, The client has requested a MIME type via the Accept header for a value not supported by the server,
|
|
2565
|
+
statusCode: ${statusCode}`,
|
|
2566
|
+
415: `Unsupported Media Type, The client has defined a contentType header that is not supported by the server, statusCode: ${statusCode}`,
|
|
2567
|
+
422: `Unprocessable Entity, The client has made a valid request, but the server cannot process it. This is often used for APIs for which
|
|
2568
|
+
certain limits have been exceeded, statusCode: ${statusCode}`,
|
|
2569
|
+
429: `Too Many Requests, The client has exceeded the number of requests allowed for a given time window, statusCode: ${statusCode}`,
|
|
2570
|
+
500: `Internal Server Error, An unexpected error on the SmartThings servers has occurred. These errors should be rare,
|
|
2571
|
+
statusCode: ${statusCode}`,
|
|
2572
|
+
};
|
|
2573
|
+
const message = messages[statusCode] ?? `Unknown statusCode, statusCode: ${statusCode}, Submit Bugs Here: https://tinyurl.com/SwitchBotBug`;
|
|
2574
|
+
if ([100, 200].includes(statusCode)) {
|
|
2575
|
+
await this.debugLog(message);
|
|
2576
|
+
}
|
|
2577
|
+
else {
|
|
2578
|
+
await this.errorLog(message);
|
|
2731
2579
|
}
|
|
2732
2580
|
}
|
|
2733
2581
|
async retryRequest(deviceMaxRetries, deviceDelayBetweenRetries, url, options) {
|
|
@@ -2755,57 +2603,43 @@ export class SwitchBotPlatform {
|
|
|
2755
2603
|
}
|
|
2756
2604
|
// BLE Connection
|
|
2757
2605
|
async connectBLE(accessory, device) {
|
|
2758
|
-
let switchbot;
|
|
2759
2606
|
try {
|
|
2760
|
-
const SwitchBot =
|
|
2761
|
-
|
|
2607
|
+
const { SwitchBot } = await import('node-switchbot');
|
|
2608
|
+
const switchbot = new SwitchBot();
|
|
2609
|
+
queueScheduler.schedule(async () => switchbot);
|
|
2762
2610
|
await this.debugLog(`${device.deviceType}: ${accessory.displayName} 'node-switchbot' found: ${switchbot}`);
|
|
2611
|
+
return switchbot;
|
|
2763
2612
|
}
|
|
2764
2613
|
catch (e) {
|
|
2765
|
-
switchbot
|
|
2766
|
-
|
|
2614
|
+
await this.errorLog(`${device.deviceType}: ${accessory.displayName} 'node-switchbot' not found, Error: ${e}`);
|
|
2615
|
+
return false;
|
|
2767
2616
|
}
|
|
2768
|
-
return switchbot;
|
|
2769
2617
|
}
|
|
2770
2618
|
async getVersion() {
|
|
2771
2619
|
const json = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf-8'));
|
|
2772
2620
|
await this.debugLog(`Plugin Version: ${json.version}`);
|
|
2773
2621
|
this.version = json.version;
|
|
2622
|
+
return json.version;
|
|
2774
2623
|
}
|
|
2775
2624
|
async getPlatformConfigSettings() {
|
|
2625
|
+
const { options } = this.config;
|
|
2776
2626
|
const platformConfig = {};
|
|
2777
|
-
if (
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
if (
|
|
2785
|
-
|
|
2786
|
-
}
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
}
|
|
2794
|
-
else {
|
|
2795
|
-
this.maxRetries = 3;
|
|
2796
|
-
this.debugWarnLog('Using Default Max Retries');
|
|
2797
|
-
platformConfig.maxRetries = this.maxRetries;
|
|
2798
|
-
}
|
|
2799
|
-
if (this.config.options.delayBetweenRetries) {
|
|
2800
|
-
this.delayBetweenRetries = this.config.options.delayBetweenRetries * 1000;
|
|
2801
|
-
platformConfig.delayBetweenRetries = this.config.options.delayBetweenRetries;
|
|
2802
|
-
}
|
|
2803
|
-
else {
|
|
2804
|
-
this.delayBetweenRetries = 3000;
|
|
2805
|
-
this.debugWarnLog('Using Default Delay Between Retries');
|
|
2806
|
-
platformConfig.delayBetweenRetries = this.delayBetweenRetries / 1000;
|
|
2807
|
-
}
|
|
2808
|
-
if (Object.entries(platformConfig).length !== 0) {
|
|
2627
|
+
if (options) {
|
|
2628
|
+
platformConfig.logging = options.logging;
|
|
2629
|
+
platformConfig.refreshRate = options.refreshRate;
|
|
2630
|
+
platformConfig.updateRate = options.updateRate;
|
|
2631
|
+
platformConfig.pushRate = options.pushRate;
|
|
2632
|
+
this.maxRetries = options.maxRetries || 3;
|
|
2633
|
+
platformConfig.maxRetries = this.maxRetries;
|
|
2634
|
+
if (!options.maxRetries) {
|
|
2635
|
+
await this.debugWarnLog('Using Default Max Retries');
|
|
2636
|
+
}
|
|
2637
|
+
this.delayBetweenRetries = (options.delayBetweenRetries || 3) * 1000;
|
|
2638
|
+
platformConfig.delayBetweenRetries = this.delayBetweenRetries / 1000;
|
|
2639
|
+
if (!options.delayBetweenRetries) {
|
|
2640
|
+
await this.debugWarnLog('Using Default Delay Between Retries');
|
|
2641
|
+
}
|
|
2642
|
+
if (Object.keys(platformConfig).length) {
|
|
2809
2643
|
await this.debugLog(`Platform Config: ${JSON.stringify(platformConfig)}`);
|
|
2810
2644
|
}
|
|
2811
2645
|
this.platformConfig = platformConfig;
|
|
@@ -2828,31 +2662,42 @@ export class SwitchBotPlatform {
|
|
|
2828
2662
|
}
|
|
2829
2663
|
/**
|
|
2830
2664
|
* Validate and clean a string value for a Name Characteristic.
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2665
|
+
* @param displayName - The display name of the accessory.
|
|
2666
|
+
* @param name - The name of the characteristic.
|
|
2667
|
+
* @param value - The value to be validated and cleaned.
|
|
2668
|
+
* @returns The cleaned string value.
|
|
2669
|
+
*/
|
|
2836
2670
|
async validateAndCleanDisplayName(displayName, name, value) {
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2671
|
+
if (this.config.options?.allowInvalidCharacters) {
|
|
2672
|
+
return value;
|
|
2673
|
+
}
|
|
2674
|
+
else {
|
|
2675
|
+
const validPattern = new RegExp(/^[\p{L}\p{N}][\p{L}\p{N} ']*[\p{L}\p{N}]$/u);
|
|
2676
|
+
const invalidCharsPattern = /[^\p{L}\p{N} ']/gu;
|
|
2677
|
+
const invalidStartEndPattern = /^[^\p{L}\p{N}]+|[^\p{L}\p{N}]+$/gu;
|
|
2678
|
+
if (typeof value === 'string' && !validPattern.test(value)) {
|
|
2679
|
+
await this.warnLog(`WARNING: The accessory '${displayName}' has an invalid '${name}' characteristic ('${value}').`
|
|
2680
|
+
+ ' Please use only alphanumeric, space, and apostrophe characters. Ensure it starts and ends with an alphabetic or numeric character,'
|
|
2681
|
+
+ ' and avoid emojis. This may prevent the accessory from being added in the Home App or cause unresponsiveness.');
|
|
2682
|
+
// Remove invalid characters
|
|
2683
|
+
if (invalidCharsPattern.test(value)) {
|
|
2684
|
+
const before = value;
|
|
2685
|
+
await this.warnLog(`Removing invalid characters from '${name}' characteristic, if you feel this is incorrect,`
|
|
2686
|
+
+ ' please enable \'allowInvalidCharacter\' in the config to allow all characters');
|
|
2687
|
+
value = value.replace(invalidCharsPattern, '');
|
|
2688
|
+
await this.warnLog(`${name} Before: '${before}' After: '${value}'`);
|
|
2689
|
+
}
|
|
2690
|
+
// Ensure it starts and ends with an alphanumeric character
|
|
2691
|
+
if (invalidStartEndPattern.test(value)) {
|
|
2692
|
+
const before = value;
|
|
2693
|
+
await this.warnLog(`Removing invalid starting or ending characters from '${name}' characteristic, if you feel this is incorrect,`
|
|
2694
|
+
+ ' please enable \'allowInvalidCharacter\' in the config to allow all characters');
|
|
2695
|
+
value = value.replace(invalidStartEndPattern, '');
|
|
2696
|
+
await this.warnLog(`${name} Before: '${before}' After: '${value}'`);
|
|
2697
|
+
}
|
|
2698
|
+
}
|
|
2699
|
+
return value;
|
|
2700
|
+
}
|
|
2856
2701
|
}
|
|
2857
2702
|
/**
|
|
2858
2703
|
* If device level logging is turned on, log to log.warn
|