@switchbot/homebridge-switchbot 3.7.1-beta.1 → 3.7.1-beta.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/config.schema.json +7 -1
  3. package/dist/device/blindtilt.d.ts +3 -1
  4. package/dist/device/blindtilt.d.ts.map +1 -1
  5. package/dist/device/blindtilt.js +61 -28
  6. package/dist/device/blindtilt.js.map +1 -1
  7. package/dist/device/bot.d.ts +1 -0
  8. package/dist/device/bot.d.ts.map +1 -1
  9. package/dist/device/bot.js +102 -80
  10. package/dist/device/bot.js.map +1 -1
  11. package/dist/device/ceilinglight.d.ts +1 -0
  12. package/dist/device/ceilinglight.d.ts.map +1 -1
  13. package/dist/device/ceilinglight.js +53 -28
  14. package/dist/device/ceilinglight.js.map +1 -1
  15. package/dist/device/colorbulb.d.ts +1 -0
  16. package/dist/device/colorbulb.d.ts.map +1 -1
  17. package/dist/device/colorbulb.js +53 -28
  18. package/dist/device/colorbulb.js.map +1 -1
  19. package/dist/device/contact.d.ts +1 -0
  20. package/dist/device/contact.d.ts.map +1 -1
  21. package/dist/device/contact.js +53 -28
  22. package/dist/device/contact.js.map +1 -1
  23. package/dist/device/curtain.d.ts +2 -0
  24. package/dist/device/curtain.d.ts.map +1 -1
  25. package/dist/device/curtain.js +74 -43
  26. package/dist/device/curtain.js.map +1 -1
  27. package/dist/device/device.d.ts.map +1 -1
  28. package/dist/device/device.js +247 -394
  29. package/dist/device/device.js.map +1 -1
  30. package/dist/device/fan.d.ts +1 -0
  31. package/dist/device/fan.d.ts.map +1 -1
  32. package/dist/device/fan.js +53 -28
  33. package/dist/device/fan.js.map +1 -1
  34. package/dist/device/hub.d.ts +1 -0
  35. package/dist/device/hub.d.ts.map +1 -1
  36. package/dist/device/hub.js +53 -28
  37. package/dist/device/hub.js.map +1 -1
  38. package/dist/device/humidifier.d.ts +1 -0
  39. package/dist/device/humidifier.d.ts.map +1 -1
  40. package/dist/device/humidifier.js +53 -28
  41. package/dist/device/humidifier.js.map +1 -1
  42. package/dist/device/iosensor.d.ts +1 -0
  43. package/dist/device/iosensor.d.ts.map +1 -1
  44. package/dist/device/iosensor.js +53 -28
  45. package/dist/device/iosensor.js.map +1 -1
  46. package/dist/device/lightstrip.d.ts +1 -0
  47. package/dist/device/lightstrip.d.ts.map +1 -1
  48. package/dist/device/lightstrip.js +53 -28
  49. package/dist/device/lightstrip.js.map +1 -1
  50. package/dist/device/lock.d.ts +1 -0
  51. package/dist/device/lock.d.ts.map +1 -1
  52. package/dist/device/lock.js +54 -29
  53. package/dist/device/lock.js.map +1 -1
  54. package/dist/device/meter.d.ts +1 -0
  55. package/dist/device/meter.d.ts.map +1 -1
  56. package/dist/device/meter.js +53 -28
  57. package/dist/device/meter.js.map +1 -1
  58. package/dist/device/meterplus.d.ts +1 -0
  59. package/dist/device/meterplus.d.ts.map +1 -1
  60. package/dist/device/meterplus.js +53 -28
  61. package/dist/device/meterplus.js.map +1 -1
  62. package/dist/device/motion.d.ts +1 -0
  63. package/dist/device/motion.d.ts.map +1 -1
  64. package/dist/device/motion.js +53 -28
  65. package/dist/device/motion.js.map +1 -1
  66. package/dist/device/plug.d.ts +1 -0
  67. package/dist/device/plug.d.ts.map +1 -1
  68. package/dist/device/plug.js +54 -29
  69. package/dist/device/plug.js.map +1 -1
  70. package/dist/device/robotvacuumcleaner.d.ts +1 -0
  71. package/dist/device/robotvacuumcleaner.d.ts.map +1 -1
  72. package/dist/device/robotvacuumcleaner.js +53 -28
  73. package/dist/device/robotvacuumcleaner.js.map +1 -1
  74. package/dist/device/waterdetector.d.ts +1 -0
  75. package/dist/device/waterdetector.d.ts.map +1 -1
  76. package/dist/device/waterdetector.js +53 -28
  77. package/dist/device/waterdetector.js.map +1 -1
  78. package/dist/irdevice/irdevice.d.ts.map +1 -1
  79. package/dist/irdevice/irdevice.js +30 -133
  80. package/dist/irdevice/irdevice.js.map +1 -1
  81. package/dist/platform.d.ts +19 -18
  82. package/dist/platform.d.ts.map +1 -1
  83. package/dist/platform.js +369 -524
  84. package/dist/platform.js.map +1 -1
  85. package/dist/settings.d.ts +1 -0
  86. package/dist/settings.d.ts.map +1 -1
  87. package/dist/settings.js.map +1 -1
  88. package/package.json +13 -13
  89. package/src/device/blindtilt.ts +59 -27
  90. package/src/device/bot.ts +98 -77
  91. package/src/device/ceilinglight.ts +51 -27
  92. package/src/device/colorbulb.ts +50 -26
  93. package/src/device/contact.ts +50 -26
  94. package/src/device/curtain.ts +71 -40
  95. package/src/device/device.ts +283 -395
  96. package/src/device/fan.ts +50 -26
  97. package/src/device/hub.ts +50 -26
  98. package/src/device/humidifier.ts +50 -26
  99. package/src/device/iosensor.ts +50 -26
  100. package/src/device/lightstrip.ts +50 -26
  101. package/src/device/lock.ts +51 -27
  102. package/src/device/meter.ts +50 -26
  103. package/src/device/meterplus.ts +50 -26
  104. package/src/device/motion.ts +50 -26
  105. package/src/device/plug.ts +51 -27
  106. package/src/device/robotvacuumcleaner.ts +50 -26
  107. package/src/device/waterdetector.ts +50 -26
  108. package/src/irdevice/irdevice.ts +54 -134
  109. package/src/platform.ts +379 -529
  110. package/src/settings.ts +1 -0
package/src/platform.ts CHANGED
@@ -51,6 +51,10 @@ import type { blindTilt, curtain, curtain3, device } from './types/devicelist.js
51
51
  import type { API, DynamicPlatformPlugin, Logging, PlatformAccessory } from 'homebridge';
52
52
  import type { SwitchBotPlatformConfig, devicesConfig, irDevicesConfig } from './settings.js';
53
53
  import type { IncomingMessage, Server, ServerResponse } from 'http';
54
+ /*
55
+ * For Testing Locally:
56
+ * import { SwitchBotModel } from '/Users/Shared/GitHub/OpenWonderLabs/node-switchbot/dist/index.js';
57
+ */
54
58
  import { SwitchBotModel } from 'node-switchbot';
55
59
 
56
60
  /**
@@ -59,10 +63,12 @@ import { SwitchBotModel } from 'node-switchbot';
59
63
  * parse the user config and discover/register accessories with Homebridge.
60
64
  */
61
65
  export class SwitchBotPlatform implements DynamicPlatformPlugin {
62
- public accessories: PlatformAccessory[];
66
+ // Platform properties
67
+ public accessories: PlatformAccessory[] = [];
63
68
  public readonly api: API;
64
69
  public readonly log: Logging;
65
70
 
71
+ // Configuration properties
66
72
  version!: string;
67
73
  Logging?: string;
68
74
  debugMode!: boolean;
@@ -72,11 +78,15 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin {
72
78
  platformLogging!: SwitchBotPlatformConfig['logging'];
73
79
  config!: SwitchBotPlatformConfig;
74
80
 
81
+ // MQTT and Webhook properties
75
82
  mqttClient: MqttClient | null = null;
76
83
  webhookEventListener: Server | null = null;
77
84
 
85
+ // External APIs
78
86
  public readonly eve: any;
79
87
  public readonly fakegatoAPI: any;
88
+
89
+ // Event Handlers
80
90
  public readonly webhookEventHandler: { [x: string]: (context: any) => void } = {};
81
91
  public readonly bleEventHandler: { [x: string]: (context: any) => void } = {};
82
92
 
@@ -85,8 +95,6 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin {
85
95
  config: SwitchBotPlatformConfig,
86
96
  api: API,
87
97
  ) {
88
- // initialize
89
- this.accessories = [];
90
98
  this.api = api;
91
99
  this.log = log;
92
100
 
@@ -130,26 +138,38 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin {
130
138
  // in order to ensure they weren't added to homebridge already. This event can also be used
131
139
  // to start discovery of new accessories.
132
140
  this.api.on('didFinishLaunching', async () => {
133
- this.debugLog('Executed didFinishLaunching callback');
141
+ await this.debugLog('Executed didFinishLaunching callback');
134
142
  // run the method to discover / register your devices as accessories
135
143
  try {
136
144
  if (this.config.credentials?.openToken && !this.config.credentials.token) {
137
145
  await this.updateToken();
138
146
  } else if (this.config.credentials?.token && !this.config.credentials?.secret) {
139
147
 
140
- this.errorLog('"secret" config is not populated, you must populate then please restart Homebridge.');
148
+ await this.errorLog('"secret" config is not populated, you must populate then please restart Homebridge.');
141
149
  } else {
142
- this.discoverDevices();
150
+ await this.discoverDevices();
143
151
  }
144
152
  } catch (e: any) {
145
- this.errorLog(`Failed to Discover, Error Message: ${e.message}, Submit Bugs Here: ` + 'https://tinyurl.com/SwitchBotBug');
153
+ await this.errorLog(`Failed to Discover, Error Message: ${e.message}, Submit Bugs Here: ` + 'https://tinyurl.com/SwitchBotBug');
146
154
  await this.debugErrorLog(`Failed to Discover, Error: ${e}`);
147
155
  }
148
156
  });
149
157
 
150
- this.setupMqtt();
151
- this.setupwebhook();
152
- this.setupBlE();
158
+ try {
159
+ this.setupMqtt();
160
+ } catch (e: any) {
161
+ this.errorLog(`Setup MQTT, Error Message: ${e.message}, Submit Bugs Here: ` + 'https://tinyurl.com/SwitchBotBug');
162
+ }
163
+ try {
164
+ this.setupwebhook();
165
+ } catch (e: any) {
166
+ this.errorLog(`Setup Webhook, Error Message: ${e.message}, Submit Bugs Here: ` + 'https://tinyurl.com/SwitchBotBug');
167
+ }
168
+ try {
169
+ this.setupBlE();
170
+ } catch (e: any) {
171
+ this.errorLog(`Setup Platform BLE, Error Message: ${e.message}, Submit Bugs Here: ` + 'https://tinyurl.com/SwitchBotBug');
172
+ }
153
173
  }
154
174
 
155
175
  async setupMqtt(): Promise<void> {
@@ -167,7 +187,7 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin {
167
187
  this.mqttClient.subscribe('homebridge-switchbot/webhook/+');
168
188
  this.mqttClient.on('message', async (topic: string, message) => {
169
189
  try {
170
- await await this.debugLog(`Received Webhook via MQTT: ${topic}=${message}`);
190
+ await this.debugLog(`Received Webhook via MQTT: ${topic}=${message}`);
171
191
  const context = JSON.parse(message.toString());
172
192
  this.webhookEventHandler[context.deviceMac]?.(context);
173
193
  } catch (e: any) {
@@ -199,16 +219,13 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin {
199
219
  const body = JSON.parse(data);
200
220
  await this.debugLog(`Received Webhook: ${JSON.stringify(body)}`);
201
221
  if (this.config.options?.mqttURL) {
202
- const mac = body.context.deviceMac
203
- ?.toLowerCase()
204
- .match(/[\s\S]{1,2}/g)
205
- ?.join(':');
222
+ const mac = body.context.deviceMac?.toLowerCase().match(/[\s\S]{1,2}/g)?.join(':');
206
223
  const options = this.config.options?.mqttPubOptions || {};
207
224
  this.mqttClient?.publish(`homebridge-switchbot/webhook/${mac}`, `${JSON.stringify(body.context)}`, options);
208
225
  }
209
226
  this.webhookEventHandler[body.context.deviceMac]?.(body.context);
210
227
  } catch (e: any) {
211
- this.errorLog(`Failed to handle webhook event. Error:${e}`);
228
+ await this.errorLog(`Failed to handle webhook event. Error:${e}`);
212
229
  }
213
230
  });
214
231
  response.writeHead(200, { 'Content-Type': 'text/plain' });
@@ -223,7 +240,7 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin {
223
240
  }
224
241
  }).listen(port ? port : 80);
225
242
  } catch (e: any) {
226
- this.errorLog(`Failed to create webhook listener. Error:${e.message}`);
243
+ await this.errorLog(`Failed to create webhook listener. Error:${e.message}`);
227
244
  return;
228
245
  }
229
246
 
@@ -238,15 +255,13 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin {
238
255
  }),
239
256
  });
240
257
  const response: any = await body.json();
241
- await this.debugLog(`setupWebhook: url:${url}`);
242
- await this.debugLog(`setupWebhook: body:${JSON.stringify(response)}`);
243
- await this.debugLog(`setupWebhook: statusCode:${statusCode}`);
258
+ await this.debugLog(`setupWebhook: url:${url}, body:${JSON.stringify(response)}, statusCode:${statusCode}`);
244
259
  if (statusCode !== 200 || response?.statusCode !== 100) {
245
- this.errorLog(`Failed to configure webhook. Existing webhook well be overridden. HTTP:${statusCode} API:${response?.statusCode} `
260
+ await this.errorLog(`Failed to configure webhook. Existing webhook well be overridden. HTTP:${statusCode} API:${response?.statusCode} `
246
261
  + `message:${response?.message}`);
247
262
  }
248
263
  } catch (e: any) {
249
- this.errorLog(`Failed to configure webhook. Error: ${e.message}`);
264
+ await this.errorLog(`Failed to configure webhook. Error: ${e.message}`);
250
265
  }
251
266
 
252
267
  try {
@@ -260,14 +275,12 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin {
260
275
  }),
261
276
  });
262
277
  const response: any = await body.json();
263
- await this.debugLog(`updateWebhook: url:${url}`);
264
- await this.debugLog(`updateWebhook: body:${JSON.stringify(response)}`);
265
- await this.debugLog(`updateWebhook: statusCode:${statusCode}`);
278
+ await this.debugLog(`updateWebhook: url:${url}, body:${JSON.stringify(response)}, statusCode:${statusCode}`);
266
279
  if (statusCode !== 200 || response?.statusCode !== 100) {
267
- this.errorLog(`Failed to update webhook. HTTP:${statusCode} API:${response?.statusCode} message:${response?.message}`);
280
+ await this.errorLog(`Failed to update webhook. HTTP:${statusCode} API:${response?.statusCode} message:${response?.message}`);
268
281
  }
269
282
  } catch (e: any) {
270
- this.errorLog(`Failed to update webhook. Error:${e.message}`);
283
+ await this.errorLog(`Failed to update webhook. Error:${e.message}`);
271
284
  }
272
285
 
273
286
  try {
@@ -282,12 +295,12 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin {
282
295
  await this.debugLog(`queryWebhook: body:${JSON.stringify(response)}`);
283
296
  await this.debugLog(`queryWebhook: statusCode:${statusCode}`);
284
297
  if (statusCode !== 200 || response?.statusCode !== 100) {
285
- this.errorLog(`Failed to query webhook. HTTP:${statusCode} API:${response?.statusCode} message:${response?.message}`);
298
+ await this.errorLog(`Failed to query webhook. HTTP:${statusCode} API:${response?.statusCode} message:${response?.message}`);
286
299
  } else {
287
- this.infoLog(`Listening webhook on ${response?.body?.urls[0]}`);
300
+ await this.infoLog(`Listening webhook on ${response?.body?.urls[0]}`);
288
301
  }
289
302
  } catch (e: any) {
290
- this.errorLog(`Failed to query webhook. Error:${e}`);
303
+ await this.errorLog(`Failed to query webhook. Error:${e}`);
291
304
  }
292
305
 
293
306
  this.api.on('shutdown', async () => {
@@ -303,12 +316,12 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin {
303
316
  const response: any = await body.json();
304
317
  await this.debugLog(`deleteWebhook: url:${url}, body:${JSON.stringify(response)}, statusCode:${statusCode}`);
305
318
  if (statusCode !== 200 || response?.statusCode !== 100) {
306
- this.errorLog(`Failed to delete webhook. HTTP:${statusCode} API:${response?.statusCode} message:${response?.message}`);
319
+ await this.errorLog(`Failed to delete webhook. HTTP:${statusCode} API:${response?.statusCode} message:${response?.message}`);
307
320
  } else {
308
- this.infoLog('Unregistered webhook to close listening.');
321
+ await this.infoLog('Unregistered webhook to close listening.');
309
322
  }
310
323
  } catch (e: any) {
311
- this.errorLog(`Failed to delete webhook. Error:${e.message}`);
324
+ await this.errorLog(`Failed to delete webhook. Error:${e.message}`);
312
325
  }
313
326
  });
314
327
  }
@@ -340,9 +353,9 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin {
340
353
  this.api.on('shutdown', async () => {
341
354
  try {
342
355
  switchbot.stopScan();
343
- this.infoLog('Stopped BLE scanning to close listening.');
356
+ await this.infoLog('Stopped BLE scanning to close listening.');
344
357
  } catch (e: any) {
345
- this.errorLog(`Failed to stop Platform BLE scanning. Error:${e.message}`);
358
+ await this.errorLog(`Failed to stop Platform BLE scanning. Error:${e.message}`);
346
359
  }
347
360
  });
348
361
  }
@@ -356,7 +369,8 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin {
356
369
  * It should be used to setup event handlers for characteristics and update respective values.
357
370
  */
358
371
  async configureAccessory(accessory: PlatformAccessory) {
359
- await this.debugLog(`Loading accessory from cache: ${accessory.displayName}`);
372
+ const { displayName } = accessory;
373
+ await this.debugLog(`Loading accessory from cache: ${displayName}`);
360
374
 
361
375
  // add the restored accessory to the accessories cache so we can track if it has already been registered
362
376
  this.accessories.push(accessory);
@@ -366,7 +380,7 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin {
366
380
  * Verify the config passed to the plugin is valid
367
381
  */
368
382
  async verifyConfig() {
369
- this.debugLog('Verifying Config');
383
+ await this.debugLog('Verifying Config');
370
384
  this.config = this.config || {};
371
385
  this.config.options = this.config.options || {};
372
386
 
@@ -404,10 +418,10 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin {
404
418
  for (const irDeviceConfig of this.config.options.irdevices) {
405
419
  if (!irDeviceConfig.hide_device) {
406
420
  if (!irDeviceConfig.deviceId) {
407
- this.errorLog('The devices config section is missing the *Device ID* in the config. Please check your config.');
421
+ await this.errorLog('The devices config section is missing the *Device ID* in the config. Please check your config.');
408
422
  }
409
423
  if (!irDeviceConfig.deviceId && !irDeviceConfig.configRemoteType) {
410
- this.errorLog('The devices config section is missing the *Device Type* in the config. Please check your config.');
424
+ await this.errorLog('The devices config section is missing the *Device Type* in the config. Please check your config.');
411
425
  }
412
426
  }
413
427
  }
@@ -421,18 +435,18 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin {
421
435
  if (!this.config.options.refreshRate) {
422
436
  // default 120 seconds (2 minutes)
423
437
  this.config.options!.refreshRate! = 120;
424
- this.debugWarnLog('Using Default Refresh Rate (2 minutes).');
438
+ await this.debugWarnLog('Using Default Refresh Rate (2 minutes).');
425
439
  }
426
440
 
427
441
  if (!this.config.options.pushRate) {
428
442
  // default 100 milliseconds
429
443
  this.config.options!.pushRate! = 0.1;
430
- this.debugWarnLog('Using Default Push Rate.');
444
+ await this.debugWarnLog('Using Default Push Rate.');
431
445
  }
432
446
 
433
447
  if (!this.config.options.maxRetries) {
434
448
  this.config.options.maxRetries = 5;
435
- this.debugWarnLog('Using Default Max Retries.');
449
+ await this.debugWarnLog('Using Default Max Retries.');
436
450
  } else {
437
451
  this.maxRetries = this.config.options.maxRetries;
438
452
  }
@@ -440,22 +454,22 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin {
440
454
  if (!this.config.options.delayBetweenRetries) {
441
455
  // default 3 seconds
442
456
  this.config.options!.delayBetweenRetries! = 3000;
443
- this.debugWarnLog('Using Default Delay Between Retries.');
457
+ await this.debugWarnLog('Using Default Delay Between Retries.');
444
458
  } else {
445
459
  this.delayBetweenRetries = this.config.options.delayBetweenRetries * 1000;
446
460
  }
447
461
 
448
462
  if (!this.config.credentials && !this.config.options) {
449
- this.debugWarnLog('Missing Credentials');
463
+ await this.debugWarnLog('Missing Credentials');
450
464
  } else if (this.config.credentials && !this.config.credentials.notice) {
451
465
  if (!this.config.credentials?.token) {
452
- this.debugErrorLog('Missing token');
453
- this.debugWarnLog('Cloud Enabled SwitchBot Devices & IR Devices will not work');
466
+ await this.debugErrorLog('Missing token');
467
+ await this.debugWarnLog('Cloud Enabled SwitchBot Devices & IR Devices will not work');
454
468
  }
455
469
  if (this.config.credentials?.token) {
456
470
  if (!this.config.credentials?.secret) {
457
- this.debugErrorLog('Missing secret');
458
- this.debugWarnLog('Cloud Enabled SwitchBot Devices & IR Devices will not work');
471
+ await this.debugErrorLog('Missing secret');
472
+ await this.debugWarnLog('Cloud Enabled SwitchBot Devices & IR Devices will not work');
459
473
  }
460
474
  }
461
475
  }
@@ -494,11 +508,11 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin {
494
508
  }
495
509
  // Move openToken to token
496
510
  if (!this.config.credentials.secret) {
497
- this.warnLog('This plugin has been updated to use OpenAPI v1.1, config is set with openToken,'
511
+ await this.warnLog('This plugin has been updated to use OpenAPI v1.1, config is set with openToken,'
498
512
  + ' "openToken" cconfig has been moved to the "token" config');
499
513
  this.errorLog('"secret" config is not populated, you must populate then please restart Homebridge.');
500
514
  } else {
501
- this.warnLog('This plugin has been updated to use OpenAPI v1.1, config is set with openToken, '
515
+ await this.warnLog('This plugin has been updated to use OpenAPI v1.1, config is set with openToken, '
502
516
  + '"openToken" config has been moved to the "token" config, please restart Homebridge.');
503
517
  }
504
518
 
@@ -512,9 +526,9 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin {
512
526
 
513
527
  // save the config, ensuring we maintain pretty json
514
528
  writeFileSync(this.api.user.configPath(), JSON.stringify(currentConfig, null, 4));
515
- this.verifyConfig();
529
+ await this.verifyConfig();
516
530
  } catch (e: any) {
517
- this.errorLog(`Update Token: ${e}`);
531
+ await this.errorLog(`Update Token: ${e}`);
518
532
  }
519
533
  }
520
534
 
@@ -537,331 +551,219 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin {
537
551
  };
538
552
  };
539
553
 
540
- /**
541
- * this method discovers devices
542
- *
543
- const t = `${Date.now()}`;
544
- const nonce = 'requestID';
545
- const data = this.config.credentials?.token + t + nonce;
546
- const signTerm = crypto.createHmac('sha256', this.config.credentials?.secret).update(Buffer.from(data, 'utf-8')).digest();
547
- const sign = signTerm.toString('base64');
548
- */
549
554
  async discoverDevices() {
550
- if (this.config.credentials?.token) {
551
- let retryCount = 0;
552
- const maxRetries = this.maxRetries; // Maximum number of retries
553
- const delayBetweenRetries = this.delayBetweenRetries; // Delay between retries in milliseconds
554
- await this.debugWarnLog(`Retry Count: ${retryCount}`);
555
- await this.debugWarnLog(`Max Retries: ${maxRetries}`);
556
- await this.debugWarnLog(`Delay Between Retries: ${delayBetweenRetries}`);
557
- while (retryCount < maxRetries) {
558
- try {
559
- const { body, statusCode } = await request(Devices, {
560
- headers: this.generateHeaders(),
561
- });
562
- await this.debugWarnLog(`statusCode: ${statusCode}`);
563
- const devicesAPI: any = await body.json();
564
- await this.debugWarnLog(`devicesAPI: ${JSON.stringify(devicesAPI)}`);
565
- await this.debugWarnLog(`devicesAPI Body: ${JSON.stringify(devicesAPI.body)}`);
566
- await this.debugWarnLog(`devicesAPI StatusCode: ${devicesAPI.statusCode}`);
567
- if ((statusCode === 200 || statusCode === 100) && (devicesAPI.statusCode === 200 || devicesAPI.statusCode === 100)) {
568
- await this.debugErrorLog(`statusCode: ${statusCode} & devicesAPI StatusCode: ${devicesAPI.statusCode}`);
569
- // SwitchBot Devices
570
- const deviceLists = devicesAPI.body.deviceList;
571
- await this.debugWarnLog(`DeviceLists: ${JSON.stringify(deviceLists)}`);
572
- await this.debugWarnLog(`DeviceLists Length: ${deviceLists.length}`);
573
- if (!this.config.options?.devices) {
574
- await this.debugLog(`SwitchBot Device Config Not Set: ${JSON.stringify(this.config.options?.devices)}`);
575
- if (deviceLists.length === 0) {
576
- await this.debugLog(`SwitchBot API Has No Devices With Cloud Services Enabled: ${JSON.stringify(devicesAPI.body)}`);
577
- } else {
578
- const devices = deviceLists.map((v: any) => v);
579
- for (const device of devices) {
580
- if (device.deviceType) {
581
- if (device.configDeviceName) {
582
- device.deviceName = device.configDeviceName;
583
- }
584
- this.createDevice(device);
585
- }
586
- }
587
- }
588
- } else if (this.config.credentials?.token && this.config.options.devices) {
589
- await this.debugLog(`SwitchBot Device Config Set: ${JSON.stringify(this.config.options?.devices)}`);
590
- if (deviceLists.length === 0) {
591
- await this.debugLog(`SwitchBot API Has No Devices With Cloud Services Enabled: ${JSON.stringify(devicesAPI.body)}`);
592
- } else {
593
- const deviceConfigs = this.config.options?.devices;
594
-
595
- const mergeBydeviceId = (a1: { deviceId: string }[], a2: any[]) =>
596
- a1.map((itm: { deviceId: string }) => ({
597
- ...a2.find(
598
- (item: { deviceId: string }) =>
599
- item.deviceId.toUpperCase().replace(/[^A-Z0-9]+/g, '') === itm.deviceId.toUpperCase().replace(/[^A-Z0-9]+/g, '') && item,
600
- ),
601
- ...itm,
602
- }));
603
-
604
- const devices = mergeBydeviceId(deviceLists, deviceConfigs);
605
- await this.debugLog(`SwitchBot Devices: ${JSON.stringify(devices)}`);
606
- for (const device of devices) {
607
- if (!device.deviceType) {
608
- device.deviceType = device.configDeviceType;
609
- this.errorLog(`API has displaying no deviceType: ${device.deviceType}, So using configDeviceType: ${device.configDeviceType}`);
610
- }
611
- if (device.deviceType) {
612
- if (device.configDeviceName) {
613
- device.deviceName = device.configDeviceName;
614
- }
615
- this.createDevice(device);
616
- }
617
- }
618
- }
619
- } else {
620
- this.errorLog('SwitchBot Token Supplied, Issue with Auth.');
621
- }
622
- if (devicesAPI.body.deviceList.length !== 0) {
623
- this.infoLog(`Total SwitchBot Devices Found: ${devicesAPI.body.deviceList.length}`);
624
- } else {
625
- await this.debugLog(`Total SwitchBot Devices Found: ${devicesAPI.body.deviceList.length}`);
626
- }
555
+ if (!this.config.credentials?.token) {
556
+ return this.handleManualConfig();
557
+ }
627
558
 
628
- // IR Devices
629
- const irDeviceLists = devicesAPI.body.infraredRemoteList;
630
- if (!this.config.options?.irdevices) {
631
- await this.debugLog(`IR Device Config Not Set: ${JSON.stringify(this.config.options?.irdevices)}`);
632
- const devices = irDeviceLists.map((v: any) => v);
633
- for (const device of devices) {
634
- if (device.remoteType) {
635
- this.createIRDevice(device);
636
- }
637
- }
638
- } else {
639
- await this.debugLog(`IR Device Config Set: ${JSON.stringify(this.config.options?.irdevices)}`);
640
- const irDeviceConfig = this.config.options?.irdevices;
641
-
642
- const mergeIRBydeviceId = (a1: { deviceId: string }[], a2: any[]) =>
643
- a1.map((itm: { deviceId: string }) => ({
644
- ...a2.find(
645
- (item: { deviceId: string }) =>
646
- item.deviceId.toUpperCase().replace(/[^A-Z0-9]+/g, '') === itm.deviceId.toUpperCase().replace(/[^A-Z0-9]+/g, '') && item,
647
- ),
648
- ...itm,
649
- }));
650
-
651
- const devices = mergeIRBydeviceId(irDeviceLists, irDeviceConfig);
652
- await this.debugLog(`IR Devices: ${JSON.stringify(devices)}`);
653
- for (const device of devices) {
654
- this.createIRDevice(device);
655
- }
656
- }
657
- if (devicesAPI.body.infraredRemoteList.length !== 0) {
658
- this.infoLog(`Total IR Devices Found: ${devicesAPI.body.infraredRemoteList.length}`);
659
- } else {
660
- await this.debugLog(`Total IR Devices Found: ${devicesAPI.body.infraredRemoteList.length}`);
661
- }
662
- break;
663
- } else {
664
- this.statusCode(statusCode);
665
- this.statusCode(devicesAPI.statusCode);
666
- if (statusCode === 500) {
667
- retryCount++;
668
- this.infoLog(`statusCode: ${statusCode} Attempt ${retryCount} of ${maxRetries}`);
669
- await sleep(delayBetweenRetries);
670
- }
671
- }
672
- } catch (e: any) {
559
+ let retryCount = 0;
560
+ const maxRetries = this.maxRetries;
561
+ const delayBetweenRetries = this.delayBetweenRetries;
562
+
563
+ await this.debugWarnLog(`Retry Count: ${retryCount}`);
564
+ await this.debugWarnLog(`Max Retries: ${maxRetries}`);
565
+ await this.debugWarnLog(`Delay Between Retries: ${delayBetweenRetries}`);
566
+
567
+ while (retryCount < maxRetries) {
568
+ try {
569
+ const { body, statusCode } = await request(Devices, { headers: this.generateHeaders() });
570
+ await this.debugWarnLog(`statusCode: ${statusCode}`);
571
+ const devicesAPI: any = await body.json();
572
+ await this.debugWarnLog(`devicesAPI: ${JSON.stringify(devicesAPI)}`);
573
+
574
+ if (this.isSuccessfulResponse(statusCode, devicesAPI.statusCode)) {
575
+ await this.handleDevices(devicesAPI.body.deviceList);
576
+ await this.handleIRDevices(devicesAPI.body.infraredRemoteList);
577
+ break;
578
+ } else {
579
+ await this.handleErrorResponse(statusCode, devicesAPI.statusCode, retryCount, maxRetries, delayBetweenRetries);
673
580
  retryCount++;
674
- this.debugErrorLog(
675
- `Failed to Discover Devices, Error Message: ${JSON.stringify(e.message)}, Submit Bugs Here: ` + 'https://tinyurl.com/SwitchBotBug');
676
- await this.debugErrorLog(`Failed to Discover Devices, Error: ${e}`);
677
581
  }
582
+ } catch (e: any) {
583
+ retryCount++;
584
+ await this.debugErrorLog(`Failed to Discover Devices, Error Message: ${JSON.stringify(e.message)},`
585
+ + ' Submit Bugs Here: https://tinyurl.com/SwitchBotBug');
586
+ await this.debugErrorLog(`Failed to Discover Devices, Error: ${e}`);
678
587
  }
679
- } else if (!this.config.credentials?.token && this.config.options?.devices) {
588
+ }
589
+ }
590
+
591
+ private async handleManualConfig() {
592
+ if (this.config.options?.devices) {
680
593
  await this.debugLog(`SwitchBot Device Manual Config Set: ${JSON.stringify(this.config.options?.devices)}`);
681
- const deviceConfigs = this.config.options?.devices;
682
- const devices = deviceConfigs.map((v: any) => v);
594
+ const devices = this.config.options.devices.map((v: any) => v);
683
595
  for (const device of devices) {
684
596
  device.deviceType = device.configDeviceType;
685
597
  device.deviceName = device.configDeviceName;
686
598
  if (device.deviceType) {
687
- this.createDevice(device);
599
+ await this.createDevice(device);
600
+ }
601
+ }
602
+ } else {
603
+ await this.errorLog('Neither SwitchBot Token or Device Config are set.');
604
+ }
605
+ }
606
+
607
+ private isSuccessfulResponse(statusCode: number, apiStatusCode: number): boolean {
608
+ return (statusCode === 200 || statusCode === 100) && (apiStatusCode === 200 || apiStatusCode === 100);
609
+ }
610
+
611
+ private async handleDevices(deviceLists: any[]) {
612
+ if (!this.config.options?.devices) {
613
+ await this.debugLog(`SwitchBot Device Config Not Set: ${JSON.stringify(this.config.options?.devices)}`);
614
+ if (deviceLists.length === 0) {
615
+ await this.debugLog('SwitchBot API Has No Devices With Cloud Services Enabled');
616
+ } else {
617
+ for (const device of deviceLists) {
618
+ if (device.deviceType) {
619
+ if (device.configDeviceName) {
620
+ device.deviceName = device.configDeviceName;
621
+ }
622
+ await this.createDevice(device);
623
+ }
688
624
  }
689
625
  }
690
626
  } else {
691
- this.errorLog('Neither SwitchBot Token or Device Config are not set.');
627
+ await this.debugLog(`SwitchBot Device Config Set: ${JSON.stringify(this.config.options?.devices)}`);
628
+ const devices = this.mergeByDeviceId(deviceLists, this.config.options.devices);
629
+ await this.debugLog(`SwitchBot Devices: ${JSON.stringify(devices)}`);
630
+ for (const device of devices) {
631
+ if (!device.deviceType && device.configDeviceType) {
632
+ device.deviceType = device.configDeviceType;
633
+ await this.warnLog(`API is displaying no deviceType: ${device.deviceType}, So using configDeviceType: ${device.configDeviceType}`);
634
+ } else if (!device.deviceType && !device.configDeviceName) {
635
+ await this.errorLog('No deviceType or configDeviceType for device. No device will be created.');
636
+ }
637
+ if (device.deviceType) {
638
+ if (device.configDeviceName) {
639
+ device.deviceName = device.configDeviceName;
640
+ }
641
+ await this.createDevice(device);
642
+ }
643
+ }
644
+ }
645
+ }
646
+
647
+ private async handleIRDevices(irDeviceLists: any[]) {
648
+ if (!this.config.options?.irdevices) {
649
+ await this.debugLog(`IR Device Config Not Set: ${JSON.stringify(this.config.options?.irdevices)}`);
650
+ for (const device of irDeviceLists) {
651
+ if (device.remoteType) {
652
+ await this.createIRDevice(device);
653
+ }
654
+ }
655
+ } else {
656
+ await this.debugLog(`IR Device Config Set: ${JSON.stringify(this.config.options?.irdevices)}`);
657
+ const devices = this.mergeByDeviceId(irDeviceLists, this.config.options.irdevices);
658
+ await this.debugLog(`IR Devices: ${JSON.stringify(devices)}`);
659
+ for (const device of devices) {
660
+ await this.createIRDevice(device);
661
+ }
662
+ }
663
+ }
664
+
665
+ private mergeByDeviceId(a1: { deviceId: string }[], a2: any[]) {
666
+ const normalizeDeviceId = (deviceId: string) => deviceId.toUpperCase().replace(/[^A-Z0-9]+/g, '');
667
+ return a1.map(itm => {
668
+ const matchingItem = a2.find(item => normalizeDeviceId(item.deviceId) === normalizeDeviceId(itm.deviceId));
669
+ return { ...matchingItem, ...itm };
670
+ });
671
+ }
672
+
673
+ private async handleErrorResponse(statusCode: number, apiStatusCode: number, retryCount: number, maxRetries: number, delayBetweenRetries: number) {
674
+ await this.statusCode(statusCode);
675
+ await this.statusCode(apiStatusCode);
676
+ if (statusCode === 500) {
677
+ this.infoLog(`statusCode: ${statusCode} Attempt ${retryCount + 1} of ${maxRetries}`);
678
+ await sleep(delayBetweenRetries);
692
679
  }
693
680
  }
694
681
 
695
682
  private async createDevice(device: device & devicesConfig) {
696
- switch (device.deviceType!) {
697
- case 'Humidifier':
698
- await this.debugLog(`Discovered ${device.deviceType}: ${device.deviceId}`);
699
- this.createHumidifier(device);
700
- break;
701
- case 'Hub Mini':
702
- await this.debugLog(`Discovered ${device.deviceType}: ${device.deviceId}`);
703
- break;
704
- case 'Hub Plus':
705
- await this.debugLog(`Discovered ${device.deviceType}: ${device.deviceId}`);
706
- break;
707
- case 'Hub 2':
708
- await this.debugLog(`Discovered ${device.deviceType}: ${device.deviceId}`);
709
- this.createHub2(device);
710
- break;
711
- case 'Bot':
712
- await this.debugLog(`Discovered ${device.deviceType}: ${device.deviceId}`);
713
- this.createBot(device);
714
- break;
715
- case 'Meter':
716
- await this.debugLog(`Discovered ${device.deviceType}: ${device.deviceId}`);
717
- this.createMeter(device);
718
- break;
719
- case 'MeterPlus':
720
- case 'Meter Plus (JP)':
721
- await this.debugLog(`Discovered ${device.deviceType}: ${device.deviceId}`);
722
- this.createMeterPlus(device);
723
- break;
724
- case 'WoIOSensor':
725
- await this.debugLog(`Discovered ${device.deviceType}: ${device.deviceId}`);
726
- this.createIOSensor(device);
727
- break;
728
- case 'Water Detector':
729
- await this.debugLog(`Discovered ${device.deviceType}: ${device.deviceId}`);
730
- this.createWaterDetector(device);
731
- break;
732
- case 'Motion Sensor':
733
- await this.debugLog(`Discovered ${device.deviceType}: ${device.deviceId}`);
734
- this.createMotion(device);
735
- break;
736
- case 'Contact Sensor':
737
- await this.debugLog(`Discovered ${device.deviceType}: ${device.deviceId}`);
738
- this.createContact(device);
739
- break;
740
- case 'Curtain':
741
- case 'Curtain3':
742
- await this.debugLog(`Discovered ${device.deviceType} ${device.deviceName}: ${device.deviceId}`);
743
- this.createCurtain(device);
744
- break;
745
- case 'Blind Tilt':
746
- await this.debugLog(`Discovered ${device.deviceType} ${device.deviceName}: ${device.deviceId}`);
747
- this.createBlindTilt(device);
748
- break;
749
- case 'Plug':
750
- case 'Plug Mini (US)':
751
- case 'Plug Mini (JP)':
752
- await this.debugLog(`Discovered ${device.deviceType}: ${device.deviceId}`);
753
- this.createPlug(device);
754
- break;
755
- case 'Smart Lock':
756
- case 'Smart Lock Pro':
757
- await this.debugLog(`Discovered ${device.deviceType}: ${device.deviceId}`);
758
- this.createLock(device);
759
- break;
760
- case 'Color Bulb':
761
- await this.debugLog(`Discovered ${device.deviceType}: ${device.deviceId}`);
762
- this.createColorBulb(device);
763
- break;
764
- case 'K10+':
765
- case 'WoSweeper':
766
- case 'WoSweeperMini':
767
- case 'Robot Vacuum Cleaner S1':
768
- case 'Robot Vacuum Cleaner S1 Plus':
769
- case 'Robot Vacuum Cleaner S10':
770
- await this.debugLog(`Discovered ${device.deviceType}: ${device.deviceId}`);
771
- this.createRobotVacuumCleaner(device);
772
- break;
773
- case 'Ceiling Light':
774
- case 'Ceiling Light Pro':
775
- await this.debugLog(`Discovered ${device.deviceType}: ${device.deviceId}`);
776
- this.createCeilingLight(device);
777
- break;
778
- case 'Strip Light':
779
- await this.debugLog(`Discovered ${device.deviceType}: ${device.deviceId}`);
780
- this.createStripLight(device);
781
- break;
782
- case 'Battery Circulator Fan':
783
- await this.debugLog(`Discovered ${device.deviceType}: ${device.deviceId}`);
784
- this.createFan(device);
785
- break;
786
- case 'Remote':
787
- case 'Indoor Cam':
788
- case 'remote with screen+':
789
- await this.debugLog(`Discovered ${device.deviceType}: ${device.deviceId}, is currently not supported, device: ${JSON.stringify(device)}`);
790
- break;
791
- default:
792
- this.warnLog(`Device: ${device.deviceName} with Device Type: ${device.deviceType}, is currently not supported.`,
793
- + `Submit Feature Requests Here: https://tinyurl.com/SwitchBotFeatureRequest, device: ${JSON.stringify(device)}`);
683
+ const deviceTypeHandlers: { [key: string]: (device: device & devicesConfig) => Promise<void> } = {
684
+ 'Humidifier': this.createHumidifier.bind(this),
685
+ 'Hub 2': this.createHub2.bind(this),
686
+ 'Bot': this.createBot.bind(this),
687
+ 'Meter': this.createMeter.bind(this),
688
+ 'MeterPlus': this.createMeterPlus.bind(this),
689
+ 'Meter Plus (JP)': this.createMeterPlus.bind(this),
690
+ 'WoIOSensor': this.createIOSensor.bind(this),
691
+ 'Water Detector': this.createWaterDetector.bind(this),
692
+ 'Motion Sensor': this.createMotion.bind(this),
693
+ 'Contact Sensor': this.createContact.bind(this),
694
+ 'Curtain': this.createCurtain.bind(this),
695
+ 'Curtain3': this.createCurtain.bind(this),
696
+ 'Blind Tilt': this.createBlindTilt.bind(this),
697
+ 'Plug': this.createPlug.bind(this),
698
+ 'Plug Mini (US)': this.createPlug.bind(this),
699
+ 'Plug Mini (JP)': this.createPlug.bind(this),
700
+ 'Smart Lock': this.createLock.bind(this),
701
+ 'Smart Lock Pro': this.createLock.bind(this),
702
+ 'Color Bulb': this.createColorBulb.bind(this),
703
+ 'K10+': this.createRobotVacuumCleaner.bind(this),
704
+ 'WoSweeper': this.createRobotVacuumCleaner.bind(this),
705
+ 'WoSweeperMini': this.createRobotVacuumCleaner.bind(this),
706
+ 'Robot Vacuum Cleaner S1': this.createRobotVacuumCleaner.bind(this),
707
+ 'Robot Vacuum Cleaner S1 Plus': this.createRobotVacuumCleaner.bind(this),
708
+ 'Robot Vacuum Cleaner S10': this.createRobotVacuumCleaner.bind(this),
709
+ 'Ceiling Light': this.createCeilingLight.bind(this),
710
+ 'Ceiling Light Pro': this.createCeilingLight.bind(this),
711
+ 'Strip Light': this.createStripLight.bind(this),
712
+ 'Battery Circulator Fan': this.createFan.bind(this),
713
+ };
714
+
715
+ if (deviceTypeHandlers[device.deviceType!]) {
716
+ await this.debugLog(`Discovered ${device.deviceType}: ${device.deviceId}`);
717
+ await deviceTypeHandlers[device.deviceType!](device);
718
+ } else if (['Hub Mini', 'Hub Plus', 'Remote', 'Indoor Cam', 'remote with screen'].includes(device.deviceType!)) {
719
+ await this.debugLog(`Discovered ${device.deviceType}: ${device.deviceId}, is currently not supported, device: ${JSON.stringify(device)}`);
720
+ } else {
721
+ await this.warnLog(`Device: ${device.deviceName} with Device Type: ${device.deviceType}, is currently not supported.`
722
+ + ` Submit Feature Requests Here: https://tinyurl.com/SwitchBotFeatureRequest, device: ${JSON.stringify(device)}`);
794
723
  }
795
724
  }
796
725
 
797
726
  private async createIRDevice(device: irdevice & devicesConfig) {
798
- if (device.connectionType === undefined) {
799
- device.connectionType = 'OpenAPI';
800
- }
801
- switch (device.remoteType) {
802
- case 'TV':
803
- case 'DIY TV':
804
- case 'Projector':
805
- case 'DIY Projector':
806
- case 'Set Top Box':
807
- case 'DIY Set Top Box':
808
- case 'IPTV':
809
- case 'DIY IPTV':
810
- case 'DVD':
811
- case 'DIY DVD':
812
- case 'Speaker':
813
- case 'DIY Speaker':
814
- await this.debugLog(`Discovered ${device.remoteType}: ${device.deviceId}`);
815
- if (device.external === undefined) {
816
- device.external = true;
817
- this.createTV(device);
818
- } else {
819
- this.createTV(device);
820
- }
821
- break;
822
- case 'Fan':
823
- case 'DIY Fan':
824
- await this.debugLog(`Discovered ${device.remoteType}: ${device.deviceId}`);
825
- this.createIRFan(device);
826
- break;
827
- case 'Air Conditioner':
828
- case 'DIY Air Conditioner':
829
- await this.debugLog(`Discovered ${device.remoteType}: ${device.deviceId}`);
830
- this.createAirConditioner(device);
831
- break;
832
- case 'Light':
833
- case 'DIY Light':
834
- await this.debugLog(`Discovered ${device.remoteType}: ${device.deviceId}`);
835
- this.createLight(device);
836
- break;
837
- case 'Air Purifier':
838
- case 'DIY Air Purifier':
839
- await this.debugLog(`Discovered ${device.remoteType}: ${device.deviceId}`);
840
- this.createAirPurifier(device);
841
- break;
842
- case 'Water Heater':
843
- case 'DIY Water Heater':
844
- await this.debugLog(`Discovered ${device.remoteType}: ${device.deviceId}`);
845
- this.createWaterHeater(device);
846
- break;
847
- case 'Vacuum Cleaner':
848
- case 'DIY Vacuum Cleaner':
849
- await this.debugLog(`Discovered ${device.remoteType}: ${device.deviceId}`);
850
- this.createVacuumCleaner(device);
851
- break;
852
- case 'Camera':
853
- case 'DIY Camera':
854
- await this.debugLog(`Discovered ${device.remoteType}: ${device.deviceId}`);
855
- this.createCamera(device);
856
- break;
857
- case 'Others':
858
- await this.debugLog(`Discovered ${device.remoteType}: ${device.deviceId}`);
859
- this.createOthers(device);
860
- break;
861
- default:
862
- await this.debugLog(`Unsupported Device: ${JSON.stringify(device)}`);
863
- this.warnLog(`Device: ${device.deviceName} with Device Type: ${device.remoteType}, is currently not supported.`);
864
- this.warnLog('Submit Feature Requests Here: ' + 'https://tinyurl.com/SwitchBotFeatureRequest');
727
+ device.connectionType = device.connectionType ?? 'OpenAPI';
728
+ const deviceTypeHandlers: { [key: string]: (device: irdevice & devicesConfig) => Promise<void> } = {
729
+ 'TV': this.createTV.bind(this),
730
+ 'DIY TV': this.createTV.bind(this),
731
+ 'Projector': this.createTV.bind(this),
732
+ 'DIY Projector': this.createTV.bind(this),
733
+ 'Set Top Box': this.createTV.bind(this),
734
+ 'DIY Set Top Box': this.createTV.bind(this),
735
+ 'IPTV': this.createTV.bind(this),
736
+ 'DIY IPTV': this.createTV.bind(this),
737
+ 'DVD': this.createTV.bind(this),
738
+ 'DIY DVD': this.createTV.bind(this),
739
+ 'Speaker': this.createTV.bind(this),
740
+ 'DIY Speaker': this.createTV.bind(this),
741
+ 'Fan': this.createIRFan.bind(this),
742
+ 'DIY Fan': this.createIRFan.bind(this),
743
+ 'Air Conditioner': this.createAirConditioner.bind(this),
744
+ 'DIY Air Conditioner': this.createAirConditioner.bind(this),
745
+ 'Light': this.createLight.bind(this),
746
+ 'DIY Light': this.createLight.bind(this),
747
+ 'Air Purifier': this.createAirPurifier.bind(this),
748
+ 'DIY Air Purifier': this.createAirPurifier.bind(this),
749
+ 'Water Heater': this.createWaterHeater.bind(this),
750
+ 'DIY Water Heater': this.createWaterHeater.bind(this),
751
+ 'Vacuum Cleaner': this.createVacuumCleaner.bind(this),
752
+ 'DIY Vacuum Cleaner': this.createVacuumCleaner.bind(this),
753
+ 'Camera': this.createCamera.bind(this),
754
+ 'DIY Camera': this.createCamera.bind(this),
755
+ 'Others': this.createOthers.bind(this),
756
+ };
757
+
758
+ if (deviceTypeHandlers[device.remoteType!]) {
759
+ await this.debugLog(`Discovered ${device.remoteType}: ${device.deviceId}`);
760
+ if (device.remoteType.startsWith('DIY') && device.external === undefined) {
761
+ device.external = true;
762
+ }
763
+ await deviceTypeHandlers[device.remoteType!](device);
764
+ } else {
765
+ await this.warnLog(`Device: ${device.deviceName} with Device Type: ${device.remoteType}, is currently not supported.`
766
+ + ` Submit Feature Requests Here: https://tinyurl.com/SwitchBotFeatureRequest, device: ${JSON.stringify(device)}`);
865
767
  }
866
768
  }
867
769
 
@@ -2060,7 +1962,6 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin {
2060
1962
  ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName)
2061
1963
  : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName);
2062
1964
  accessory.context.connectionType = await this.connectionType(device);
2063
- accessory.context.connectionType = device.connectionType;
2064
1965
  accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0';
2065
1966
  const newOrExternal = !device.external ? 'Adding new' : 'Loading external';
2066
1967
  await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`);
@@ -2069,7 +1970,7 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin {
2069
1970
  new TV(this, accessory, device);
2070
1971
  await this.debugLog(`${device.remoteType} uuid: ${device.deviceId}-${device.remoteType}, (${accessory.UUID})`);
2071
1972
 
2072
- this.externalOrPlatformIR(device, accessory);
1973
+ this.externalOrPlatform(device, accessory);
2073
1974
  this.accessories.push(accessory);
2074
1975
  } else {
2075
1976
  await this.debugLog(`Device not registered: ${device.deviceName} ${device.remoteType} deviceId: ${device.deviceId}`);
@@ -2120,7 +2021,6 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin {
2120
2021
  ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName)
2121
2022
  : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName);
2122
2023
  accessory.context.connectionType = await this.connectionType(device);
2123
- accessory.context.connectionType = device.connectionType;
2124
2024
  accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0';
2125
2025
  const newOrExternal = !device.external ? 'Adding new' : 'Loading external';
2126
2026
  await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`);
@@ -2130,7 +2030,7 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin {
2130
2030
  await this.debugLog(`${device.remoteType} uuid: ${device.deviceId}-${device.remoteType}, (${accessory.UUID})`);
2131
2031
 
2132
2032
  // publish device externally or link the accessory to your platform
2133
- this.externalOrPlatformIR(device, accessory);
2033
+ this.externalOrPlatform(device, accessory);
2134
2034
  this.accessories.push(accessory);
2135
2035
  } else {
2136
2036
  await this.debugLog(`Device not registered: ${device.deviceName} ${device.remoteType} deviceId: ${device.deviceId}`);
@@ -2181,7 +2081,6 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin {
2181
2081
  ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName)
2182
2082
  : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName);
2183
2083
  accessory.context.connectionType = await this.connectionType(device);
2184
- accessory.context.connectionType = device.connectionType;
2185
2084
  accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0';
2186
2085
  const newOrExternal = !device.external ? 'Adding new' : 'Loading external';
2187
2086
  await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`);
@@ -2191,7 +2090,7 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin {
2191
2090
  await this.debugLog(`${device.remoteType} uuid: ${device.deviceId}-${device.remoteType}, (${accessory.UUID})`);
2192
2091
 
2193
2092
  // publish device externally or link the accessory to your platform
2194
- this.externalOrPlatformIR(device, accessory);
2093
+ this.externalOrPlatform(device, accessory);
2195
2094
  this.accessories.push(accessory);
2196
2095
  } else {
2197
2096
  await this.debugLog(`Device not registered: ${device.deviceName} ${device.remoteType} deviceId: ${device.deviceId}`);
@@ -2242,7 +2141,6 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin {
2242
2141
  ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName)
2243
2142
  : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName);
2244
2143
  accessory.context.connectionType = await this.connectionType(device);
2245
- accessory.context.connectionType = device.connectionType;
2246
2144
  accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0';
2247
2145
  const newOrExternal = !device.external ? 'Adding new' : 'Loading external';
2248
2146
  await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`);
@@ -2252,7 +2150,7 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin {
2252
2150
  await this.debugLog(`${device.remoteType} uuid: ${device.deviceId}-${device.remoteType}, (${accessory.UUID})`);
2253
2151
 
2254
2152
  // publish device externally or link the accessory to your platform
2255
- this.externalOrPlatformIR(device, accessory);
2153
+ this.externalOrPlatform(device, accessory);
2256
2154
  this.accessories.push(accessory);
2257
2155
  } else {
2258
2156
  await this.debugLog(`Device not registered: ${device.deviceName} ${device.remoteType} deviceId: ${device.deviceId}`);
@@ -2303,7 +2201,6 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin {
2303
2201
  ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName)
2304
2202
  : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName);
2305
2203
  accessory.context.connectionType = await this.connectionType(device);
2306
- accessory.context.connectionType = device.connectionType;
2307
2204
  accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0';
2308
2205
  const newOrExternal = !device.external ? 'Adding new' : 'Loading external';
2309
2206
  await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`);
@@ -2313,7 +2210,7 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin {
2313
2210
  await this.debugLog(`${device.remoteType} uuid: ${device.deviceId}-${device.remoteType}, (${accessory.UUID})`);
2314
2211
 
2315
2212
  // publish device externally or link the accessory to your platform
2316
- this.externalOrPlatformIR(device, accessory);
2213
+ this.externalOrPlatform(device, accessory);
2317
2214
  this.accessories.push(accessory);
2318
2215
  } else {
2319
2216
  await this.debugLog(`Device not registered: ${device.deviceName} ${device.remoteType} deviceId: ${device.deviceId}`);
@@ -2364,7 +2261,6 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin {
2364
2261
  ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName)
2365
2262
  : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName);
2366
2263
  accessory.context.connectionType = await this.connectionType(device);
2367
- accessory.context.connectionType = device.connectionType;
2368
2264
  accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0';
2369
2265
  const newOrExternal = !device.external ? 'Adding new' : 'Loading external';
2370
2266
  await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`);
@@ -2374,7 +2270,7 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin {
2374
2270
  await this.debugLog(`${device.remoteType} uuid: ${device.deviceId}-${device.remoteType}, (${accessory.UUID})`);
2375
2271
 
2376
2272
  // publish device externally or link the accessory to your platform
2377
- this.externalOrPlatformIR(device, accessory);
2273
+ this.externalOrPlatform(device, accessory);
2378
2274
  this.accessories.push(accessory);
2379
2275
  } else {
2380
2276
  await this.debugLog(`Device not registered: ${device.deviceName} ${device.remoteType} deviceId: ${device.deviceId}`);
@@ -2425,7 +2321,6 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin {
2425
2321
  ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName)
2426
2322
  : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName);
2427
2323
  accessory.context.connectionType = await this.connectionType(device);
2428
- accessory.context.connectionType = device.connectionType;
2429
2324
  accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0';
2430
2325
  const newOrExternal = !device.external ? 'Adding new' : 'Loading external';
2431
2326
  await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`);
@@ -2435,7 +2330,7 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin {
2435
2330
  await this.debugLog(`${device.remoteType} uuid: ${device.deviceId}-${device.remoteType}, (${accessory.UUID})`);
2436
2331
 
2437
2332
  // publish device externally or link the accessory to your platform
2438
- this.externalOrPlatformIR(device, accessory);
2333
+ this.externalOrPlatform(device, accessory);
2439
2334
  this.accessories.push(accessory);
2440
2335
  } else {
2441
2336
  await this.debugLog(`Device not registered: ${device.deviceName} ${device.remoteType} deviceId: ${device.deviceId}`);
@@ -2486,7 +2381,6 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin {
2486
2381
  ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName)
2487
2382
  : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName);
2488
2383
  accessory.context.connectionType = await this.connectionType(device);
2489
- accessory.context.connectionType = device.connectionType;
2490
2384
  accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0';
2491
2385
  const newOrExternal = !device.external ? 'Adding new' : 'Loading external';
2492
2386
  await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`);
@@ -2496,7 +2390,7 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin {
2496
2390
  await this.debugLog(`${device.remoteType} uuid: ${device.deviceId}-${device.remoteType}, (${accessory.UUID})`);
2497
2391
 
2498
2392
  // publish device externally or link the accessory to your platform
2499
- this.externalOrPlatformIR(device, accessory);
2393
+ this.externalOrPlatform(device, accessory);
2500
2394
  this.accessories.push(accessory);
2501
2395
  } else {
2502
2396
  await this.debugLog(`Device not registered: ${device.deviceName} ${device.remoteType} deviceId: ${device.deviceId}`);
@@ -2547,7 +2441,6 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin {
2547
2441
  ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName)
2548
2442
  : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName);
2549
2443
  accessory.context.connectionType = await this.connectionType(device);
2550
- accessory.context.connectionType = device.connectionType;
2551
2444
  accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0';
2552
2445
  const newOrExternal = !device.external ? 'Adding new' : 'Loading external';
2553
2446
  await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`);
@@ -2557,7 +2450,7 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin {
2557
2450
  await this.debugLog(`${device.remoteType} uuid: ${device.deviceId}-${device.remoteType}, (${accessory.UUID})`);
2558
2451
 
2559
2452
  // publish device externally or link the accessory to your platform
2560
- this.externalOrPlatformIR(device, accessory);
2453
+ this.externalOrPlatform(device, accessory);
2561
2454
  this.accessories.push(accessory);
2562
2455
  } else {
2563
2456
  await this.debugLog(`Device not registered: ${device.deviceName} ${device.remoteType} deviceId: ${device.deviceId}`);
@@ -2565,13 +2458,6 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin {
2565
2458
  }
2566
2459
 
2567
2460
  async registerCurtains(device: device & devicesConfig): Promise<boolean> {
2568
- /*function isCurtainDevice(device: device & devicesConfig): device is (curtain | curtain3) & devicesConfig {
2569
- return device.deviceType === 'Curtain' || device.deviceType === 'Curtain3';
2570
- }
2571
-
2572
- function isBlindTiltDevice(device: device & devicesConfig): device is blindTilt & devicesConfig {
2573
- return device.deviceType === 'Blind Tilt';
2574
- }*/
2575
2461
  let registerWindowCovering: boolean;
2576
2462
  if (isCurtainDevice(device)) {
2577
2463
  await this.debugWarnLog(`deviceName: ${device.deviceName} deviceId: ${device.deviceId}, curtainDevicesIds: ${device.curtainDevicesIds},`
@@ -2717,111 +2603,72 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin {
2717
2603
  return registerDevice;
2718
2604
  }
2719
2605
 
2720
- public async externalOrPlatformIR(device: device & irDevicesConfig, accessory: PlatformAccessory) {
2721
- /**
2722
- * Publish as external accessory
2723
- * Only one TV can exist per bridge, to bypass this limitation, you should
2724
- * publish your TV as an external accessory.
2725
- */
2726
- if (device.external) {
2727
- await this.debugWarnLog(`${accessory.displayName} External Accessory Mode`);
2728
- this.externalAccessory(accessory);
2729
- } else {
2730
- await this.debugLog(`${accessory.displayName} External Accessory Mode: ${device.external}`);
2731
- this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);
2732
- }
2733
- }
2606
+ public async externalOrPlatform(device: device & (irDevicesConfig | devicesConfig), accessory: PlatformAccessory) {
2607
+ const { displayName } = accessory;
2608
+ const isExternal = device.external ?? false;
2734
2609
 
2735
- public async externalOrPlatform(device: device & devicesConfig, accessory: PlatformAccessory) {
2736
- if (device.external) {
2737
- await this.debugWarnLog(`${accessory.displayName} External Accessory Mode`);
2738
- await this.externalAccessory(accessory);
2610
+ if (isExternal) {
2611
+ await this.debugWarnLog(`${displayName} External Accessory Mode`);
2612
+ this.api.publishExternalAccessories(PLUGIN_NAME, [accessory]);
2739
2613
  } else {
2740
- await this.debugLog(`${accessory.displayName} External Accessory Mode: ${device.external}`);
2614
+ await this.debugLog(`${displayName} External Accessory Mode: ${isExternal}`);
2741
2615
  this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);
2742
2616
  }
2743
2617
  }
2744
2618
 
2745
- public async externalAccessory(accessory: PlatformAccessory) {
2746
- this.api.publishExternalAccessories(PLUGIN_NAME, [accessory]);
2747
- }
2748
-
2749
2619
  public unregisterPlatformAccessories(existingAccessory: PlatformAccessory) {
2620
+ const { displayName } = existingAccessory;
2750
2621
  // remove platform accessories when no longer present
2751
2622
  this.api.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [existingAccessory]);
2752
- this.warnLog(`Removing existing accessory from cache: ${existingAccessory.displayName}`);
2623
+ this.warnLog(`Removing existing accessory from cache: ${displayName}`);
2753
2624
  }
2754
2625
 
2626
+ /**
2627
+ * Handles the status codes returned by the device and logs appropriate messages.
2628
+ *
2629
+ * @param statusCode - The status code returned by the device.
2630
+ * @returns A promise that resolves when the logging is complete.
2631
+ */
2755
2632
  async statusCode(statusCode: number): Promise<void> {
2756
- switch (statusCode) {
2757
- case 151:
2758
- await this.errorLog(`Command not supported by this device type, statusCode: ${statusCode}, Submit Feature Request Here: ` +
2759
- 'https://tinyurl.com/SwitchBotFeatureRequest');
2760
- break;
2761
- case 152:
2762
- await this.errorLog(`Device not found, statusCode: ${statusCode}`);
2763
- break;
2764
- case 160:
2765
- await this.errorLog(`Command is not supported, statusCode: ${statusCode}, Submit Bugs Here: https://tinyurl.com/SwitchBotBug`);
2766
- break;
2767
- case 161:
2768
- await this.errorLog(`Device is offline, statusCode: ${statusCode}`);
2769
- break;
2770
- case 171:
2771
- await this.errorLog(`is offline, statusCode: ${statusCode}`);
2772
- break;
2773
- case 190:
2774
- await this.errorLog(`Requests reached the daily limit, statusCode: ${statusCode}`);
2775
- break;
2776
- case 100:
2777
- await this.debugLog(`Command successfully sent, statusCode: ${statusCode}`);
2778
- break;
2779
- case 200:
2780
- await this.debugLog(`Request successful, statusCode: ${statusCode}`);
2781
- break;
2782
- case 400:
2783
- await this.errorLog('Bad Request, The client has issued an invalid request. This is commonly used to specify validation errors in a request '
2784
- + `payload, statusCode: ${statusCode}`);
2785
- break;
2786
- case 401:
2787
- await this.errorLog('Unauthorized, Authorization for the API is required, but the request has not been authenticated, '
2788
- + `statusCode: ${statusCode}`);
2789
- break;
2790
- case 403:
2791
- await this.errorLog('Forbidden, The request has been authenticated but does not have appropriate permissions, or a requested resource is not '
2792
- + `found, statusCode: ${statusCode}`);
2793
- break;
2794
- case 404:
2795
- await this.errorLog(`Not Found, Specifies the requested path does not exist, statusCode: ${statusCode}`);
2796
- break;
2797
- case 406:
2798
- await this.errorLog('Not Acceptable, The client has requested a MIME type via the Accept header for a value not supported by the server, '
2799
- + `statusCode: ${statusCode}`);
2800
- break;
2801
- case 415:
2802
- await this.errorLog('Unsupported Media Type, The client has defined a contentType header that is not supported by the server, '
2803
- + `statusCode: ${statusCode}`);
2804
- break;
2805
- case 422:
2806
- await this.errorLog('Unprocessable Entity, The client has made a valid request, but the server cannot process it. This is often used for '
2807
- + `APIs for which certain limits have been exceeded, statusCode: ${statusCode}`);
2808
- break;
2809
- case 429:
2810
- await this.errorLog('Too Many Requests, The client has exceeded the number of requests allowed for a given time window, '
2811
- + `statusCode: ${statusCode}`);
2812
- break;
2813
- case 500:
2814
- await this.errorLog('Internal Server Error, An unexpected error on the SmartThings servers has occurred. These errors should be rare, '
2815
- + `statusCode: ${statusCode}`);
2816
- break;
2817
- default:
2818
- await this.errorLog(`Unknown statusCode, statusCode: ${statusCode}, Submit Bugs Here: ' + 'https://tinyurl.com/SwitchBotBug`);
2633
+ const messages: { [key: number]: string } = {
2634
+ 151: `Command not supported by this device type, statusCode: ${statusCode}, Submit Feature Request Here:
2635
+ https://tinyurl.com/SwitchBotFeatureRequest`,
2636
+ 152: `Device not found, statusCode: ${statusCode}`,
2637
+ 160: `Command is not supported, statusCode: ${statusCode}, Submit Bugs Here: https://tinyurl.com/SwitchBotBug`,
2638
+ 161: `Device is offline, statusCode: ${statusCode}`,
2639
+ 171: `is offline, statusCode: ${statusCode}`,
2640
+ 190: `Requests reached the daily limit, statusCode: ${statusCode}`,
2641
+ 100: `Command successfully sent, statusCode: ${statusCode}`,
2642
+ 200: `Request successful, statusCode: ${statusCode}`,
2643
+ 400: `Bad Request, The client has issued an invalid request. This is commonly used to specify validation errors in a request payload,
2644
+ statusCode: ${statusCode}`,
2645
+ 401: `Unauthorized, Authorization for the API is required, but the request has not been authenticated, statusCode: ${statusCode}`,
2646
+ 403: `Forbidden, The request has been authenticated but does not have appropriate permissions, or a requested resource is not found,
2647
+ statusCode: ${statusCode}`,
2648
+ 404: `Not Found, Specifies the requested path does not exist, statusCode: ${statusCode}`,
2649
+ 406: `Not Acceptable, The client has requested a MIME type via the Accept header for a value not supported by the server,
2650
+ statusCode: ${statusCode}`,
2651
+ 415: `Unsupported Media Type, The client has defined a contentType header that is not supported by the server, statusCode: ${statusCode}`,
2652
+ 422: `Unprocessable Entity, The client has made a valid request, but the server cannot process it. This is often used for APIs for which
2653
+ certain limits have been exceeded, statusCode: ${statusCode}`,
2654
+ 429: `Too Many Requests, The client has exceeded the number of requests allowed for a given time window, statusCode: ${statusCode}`,
2655
+ 500: `Internal Server Error, An unexpected error on the SmartThings servers has occurred. These errors should be rare,
2656
+ statusCode: ${statusCode}`,
2657
+ };
2658
+
2659
+ const message = messages[statusCode] ?? `Unknown statusCode, statusCode: ${statusCode}, Submit Bugs Here: https://tinyurl.com/SwitchBotBug`;
2660
+
2661
+ if ([100, 200].includes(statusCode)) {
2662
+ await this.debugLog(message);
2663
+ } else {
2664
+ await this.errorLog(message);
2819
2665
  }
2820
2666
  }
2821
2667
 
2822
2668
  async retryRequest(deviceMaxRetries: number, deviceDelayBetweenRetries: number, url: string | URL | UrlObject,
2823
2669
  options?: { dispatcher?: Dispatcher } & Omit<Dispatcher.RequestOptions,
2824
- 'origin' | 'path' | 'method'> & Partial<Pick<Dispatcher.RequestOptions, 'method'>>): Promise<{ body: any; statusCode: number }> {
2670
+ 'origin' | 'path' | 'method'> & Partial<Pick<Dispatcher.RequestOptions,
2671
+ 'method'>>): Promise<{ body: any; statusCode: number }> {
2825
2672
  let retryCount = 0;
2826
2673
  const maxRetries = deviceMaxRetries;
2827
2674
  const delayBetweenRetries = deviceDelayBetweenRetries;
@@ -2844,20 +2691,20 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin {
2844
2691
  }
2845
2692
 
2846
2693
  // BLE Connection
2847
- async connectBLE(accessory: PlatformAccessory, device: device & devicesConfig) {
2848
- let switchbot: any;
2694
+ async connectBLE(accessory: PlatformAccessory, device: device & devicesConfig): Promise<any> {
2849
2695
  try {
2850
- const SwitchBot = (await import('node-switchbot')).SwitchBot;
2851
- queueScheduler.schedule(() => (switchbot = new SwitchBot()));
2696
+ const { SwitchBot } = await import('node-switchbot');
2697
+ const switchbot = new SwitchBot();
2698
+ queueScheduler.schedule(async () => switchbot);
2852
2699
  await this.debugLog(`${device.deviceType}: ${accessory.displayName} 'node-switchbot' found: ${switchbot}`);
2700
+ return switchbot;
2853
2701
  } catch (e: any) {
2854
- switchbot = false;
2855
- await this.errorLog(`${device.deviceType}: ${accessory.displayName} 'node-switchbot' found: ${switchbot}, Error: ${e}`);
2702
+ await this.errorLog(`${device.deviceType}: ${accessory.displayName} 'node-switchbot' not found, Error: ${e}`);
2703
+ return false;
2856
2704
  }
2857
- return switchbot;
2858
2705
  }
2859
2706
 
2860
- async getVersion() {
2707
+ async getVersion(): Promise<string> {
2861
2708
  const json = JSON.parse(
2862
2709
  readFileSync(
2863
2710
  new URL('../package.json', import.meta.url),
@@ -2866,42 +2713,35 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin {
2866
2713
  );
2867
2714
  await this.debugLog(`Plugin Version: ${json.version}`);
2868
2715
  this.version = json.version;
2716
+ return json.version;
2869
2717
  }
2870
2718
 
2871
2719
  async getPlatformConfigSettings() {
2720
+ const { options } = this.config;
2872
2721
  const platformConfig: SwitchBotPlatformConfig['options'] = {};
2873
- if (this.config.options) {
2874
- if (this.config.options.logging) {
2875
- platformConfig.logging = this.config.options.logging;
2876
- }
2877
- if (this.config.options.refreshRate) {
2878
- platformConfig.refreshRate = this.config.options.refreshRate;
2879
- }
2880
- if (this.config.options.updateRate) {
2881
- platformConfig.updateRate = this.config.options.updateRate;
2882
- }
2883
- if (this.config.options.pushRate) {
2884
- platformConfig.pushRate = this.config.options.pushRate;
2885
- }
2886
- if (this.config.options.maxRetries) {
2887
- this.maxRetries = this.config.options.maxRetries;
2888
- platformConfig.maxRetries = this.config.options.maxRetries;
2889
- } else {
2890
- this.maxRetries = 3;
2891
- this.debugWarnLog('Using Default Max Retries');
2892
- platformConfig.maxRetries = this.maxRetries;
2722
+
2723
+ if (options) {
2724
+ platformConfig.logging = options.logging;
2725
+ platformConfig.refreshRate = options.refreshRate;
2726
+ platformConfig.updateRate = options.updateRate;
2727
+ platformConfig.pushRate = options.pushRate;
2728
+
2729
+ this.maxRetries = options.maxRetries || 3;
2730
+ platformConfig.maxRetries = this.maxRetries;
2731
+ if (!options.maxRetries) {
2732
+ await this.debugWarnLog('Using Default Max Retries');
2893
2733
  }
2894
- if (this.config.options.delayBetweenRetries) {
2895
- this.delayBetweenRetries = this.config.options.delayBetweenRetries * 1000;
2896
- platformConfig.delayBetweenRetries = this.config.options.delayBetweenRetries;
2897
- } else {
2898
- this.delayBetweenRetries = 3000;
2899
- this.debugWarnLog('Using Default Delay Between Retries');
2900
- platformConfig.delayBetweenRetries = this.delayBetweenRetries / 1000;
2734
+
2735
+ this.delayBetweenRetries = (options.delayBetweenRetries || 3) * 1000;
2736
+ platformConfig.delayBetweenRetries = this.delayBetweenRetries / 1000;
2737
+ if (!options.delayBetweenRetries) {
2738
+ await this.debugWarnLog('Using Default Delay Between Retries');
2901
2739
  }
2902
- if (Object.entries(platformConfig).length !== 0) {
2740
+
2741
+ if (Object.keys(platformConfig).length) {
2903
2742
  await this.debugLog(`Platform Config: ${JSON.stringify(platformConfig)}`);
2904
2743
  }
2744
+
2905
2745
  this.platformConfig = platformConfig;
2906
2746
  }
2907
2747
  }
@@ -2922,35 +2762,45 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin {
2922
2762
 
2923
2763
  /**
2924
2764
  * Validate and clean a string value for a Name Characteristic.
2925
- * @param displayName
2926
- * @param name
2927
- * @param value
2928
- * @returns string value
2929
- */
2765
+ * @param displayName - The display name of the accessory.
2766
+ * @param name - The name of the characteristic.
2767
+ * @param value - The value to be validated and cleaned.
2768
+ * @returns The cleaned string value.
2769
+ */
2930
2770
  async validateAndCleanDisplayName(displayName: string, name: string, value: string): Promise<string> {
2931
- const validPattern = /^[a-zA-Z0-9][a-zA-Z0-9 ']*[a-zA-Z0-9]$/;
2932
- const invalidCharsPattern = /[^a-zA-Z0-9 ']/g;
2933
- const invalidStartEndPattern = /^[^a-zA-Z0-9]+|[^a-zA-Z0-9]+$/g;
2934
-
2935
- if (!validPattern.test(value)) {
2936
- this.warnLog(`WARNING: The accessory '${displayName}' has an invalid '${name}' characteristic ('${value}'). Please use only alphanumeric,`
2937
- + ' space, and apostrophe characters. Ensure it starts and ends with an alphabetic or numeric character, and avoid emojis. This may'
2938
- + ' prevent the accessory from being added in the Home App or cause unresponsiveness.');
2771
+ if (this.config.options?.allowInvalidCharacters) {
2772
+ return value;
2773
+ } else {
2774
+ const validPattern = new RegExp(/^[\p{L}\p{N}][\p{L}\p{N} ']*[\p{L}\p{N}]$/u);
2775
+ const invalidCharsPattern = /[^\p{L}\p{N} ']/gu;
2776
+ const invalidStartEndPattern = /^[^\p{L}\p{N}]+|[^\p{L}\p{N}]+$/gu;
2777
+
2778
+ if (typeof value === 'string' && !validPattern.test(value)) {
2779
+ await this.warnLog(`WARNING: The accessory '${displayName}' has an invalid '${name}' characteristic ('${value}').`
2780
+ + ' Please use only alphanumeric, space, and apostrophe characters. Ensure it starts and ends with an alphabetic or numeric character,'
2781
+ + ' and avoid emojis. This may prevent the accessory from being added in the Home App or cause unresponsiveness.');
2782
+
2783
+ // Remove invalid characters
2784
+ if (invalidCharsPattern.test(value)) {
2785
+ const before = value;
2786
+ await this.warnLog(`Removing invalid characters from '${name}' characteristic, if you feel this is incorrect,`
2787
+ + ' please enable \'allowInvalidCharacter\' in the config to allow all characters');
2788
+ value = value.replace(invalidCharsPattern, '');
2789
+ await this.warnLog(`${name} Before: '${before}' After: '${value}'`);
2790
+ }
2939
2791
 
2940
- // Remove invalid characters
2941
- if (invalidCharsPattern.test(value)) {
2942
- this.warnLog(`Removing invalid characters from '${name}' characteristic`);
2943
- value = value.replace(invalidCharsPattern, '');
2792
+ // Ensure it starts and ends with an alphanumeric character
2793
+ if (invalidStartEndPattern.test(value)) {
2794
+ const before = value;
2795
+ await this.warnLog(`Removing invalid starting or ending characters from '${name}' characteristic, if you feel this is incorrect,`
2796
+ + ' please enable \'allowInvalidCharacter\' in the config to allow all characters');
2797
+ value = value.replace(invalidStartEndPattern, '');
2798
+ await this.warnLog(`${name} Before: '${before}' After: '${value}'`);
2799
+ }
2944
2800
  }
2945
2801
 
2946
- // Ensure it starts and ends with an alphanumeric character
2947
- if (invalidStartEndPattern.test(value)) {
2948
- this.warnLog(`Removing invalid starting or ending characters from '${name}' characteristic`);
2949
- value = value.replace(invalidStartEndPattern, '');
2950
- }
2802
+ return value;
2951
2803
  }
2952
-
2953
- return value;
2954
2804
  }
2955
2805
 
2956
2806
  /**