@switchbot/homebridge-switchbot 3.5.1-beta.13 → 3.5.1-beta.14

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 (68) hide show
  1. package/dist/device/blindtilt.js +1 -1
  2. package/dist/device/blindtilt.js.map +1 -1
  3. package/dist/device/bot.js +1 -1
  4. package/dist/device/bot.js.map +1 -1
  5. package/dist/device/ceilinglight.js +1 -1
  6. package/dist/device/ceilinglight.js.map +1 -1
  7. package/dist/device/colorbulb.js +1 -1
  8. package/dist/device/colorbulb.js.map +1 -1
  9. package/dist/device/contact.js +1 -1
  10. package/dist/device/contact.js.map +1 -1
  11. package/dist/device/curtain.js +1 -1
  12. package/dist/device/curtain.js.map +1 -1
  13. package/dist/device/device.d.ts +0 -8
  14. package/dist/device/device.d.ts.map +1 -1
  15. package/dist/device/device.js +9 -195
  16. package/dist/device/device.js.map +1 -1
  17. package/dist/device/fan.js +1 -1
  18. package/dist/device/fan.js.map +1 -1
  19. package/dist/device/hub.js +1 -1
  20. package/dist/device/hub.js.map +1 -1
  21. package/dist/device/humidifier.js +1 -1
  22. package/dist/device/humidifier.js.map +1 -1
  23. package/dist/device/iosensor.js +1 -1
  24. package/dist/device/iosensor.js.map +1 -1
  25. package/dist/device/lightstrip.js +1 -1
  26. package/dist/device/lightstrip.js.map +1 -1
  27. package/dist/device/lock.js +1 -1
  28. package/dist/device/lock.js.map +1 -1
  29. package/dist/device/meter.js +1 -1
  30. package/dist/device/meter.js.map +1 -1
  31. package/dist/device/meterplus.js +1 -1
  32. package/dist/device/meterplus.js.map +1 -1
  33. package/dist/device/motion.js +1 -1
  34. package/dist/device/motion.js.map +1 -1
  35. package/dist/device/plug.js +1 -1
  36. package/dist/device/plug.js.map +1 -1
  37. package/dist/device/robotvacuumcleaner.js +1 -1
  38. package/dist/device/robotvacuumcleaner.js.map +1 -1
  39. package/dist/device/waterdetector.js +1 -1
  40. package/dist/device/waterdetector.js.map +1 -1
  41. package/dist/irdevice/irdevice.js +1 -1
  42. package/dist/irdevice/irdevice.js.map +1 -1
  43. package/dist/platform.d.ts +10 -1
  44. package/dist/platform.d.ts.map +1 -1
  45. package/dist/platform.js +175 -2
  46. package/dist/platform.js.map +1 -1
  47. package/package.json +1 -1
  48. package/src/device/blindtilt.ts +1 -1
  49. package/src/device/bot.ts +1 -1
  50. package/src/device/ceilinglight.ts +1 -1
  51. package/src/device/colorbulb.ts +1 -1
  52. package/src/device/contact.ts +1 -1
  53. package/src/device/curtain.ts +1 -1
  54. package/src/device/device.ts +9 -195
  55. package/src/device/fan.ts +1 -1
  56. package/src/device/hub.ts +1 -1
  57. package/src/device/humidifier.ts +1 -1
  58. package/src/device/iosensor.ts +1 -1
  59. package/src/device/lightstrip.ts +1 -1
  60. package/src/device/lock.ts +1 -1
  61. package/src/device/meter.ts +1 -1
  62. package/src/device/meterplus.ts +1 -1
  63. package/src/device/motion.ts +1 -1
  64. package/src/device/plug.ts +1 -1
  65. package/src/device/robotvacuumcleaner.ts +1 -1
  66. package/src/device/waterdetector.ts +1 -1
  67. package/src/irdevice/irdevice.ts +1 -1
  68. package/src/platform.ts +175 -2
@@ -5,17 +5,14 @@
5
5
 
6
6
  import { hostname } from 'os';
7
7
  import { request } from 'undici';
8
- import asyncmqtt from 'async-mqtt';
9
- import { createServer } from 'http';
8
+ import { Devices } from '../settings.js';
10
9
  import { BlindTiltMappingMode, sleep } from '../utils.js';
11
- import { Devices, deleteWebhook, queryWebhook, setupWebhook, updateWebhook } from '../settings.js';
12
10
  import { SwitchBotModel, SwitchBotBLEModel, SwitchBotBLEModelName, SwitchBotBLEModelFriendlyName } from 'node-switchbot';
13
11
 
14
12
  import type { MqttClient } from 'mqtt';
15
13
  import type { device } from '../types/devicelist.js';
16
14
  import type { ad } from '../types/bledevicestatus.js';
17
15
  import type { SwitchBotPlatform } from '../platform.js';
18
- import type { Server, IncomingMessage, ServerResponse } from 'http';
19
16
  import type { SwitchBotPlatformConfig, devicesConfig } from '../settings.js';
20
17
  import type { API, CharacteristicValue, HAP, Logging, PlatformAccessory, Service } from 'homebridge';
21
18
 
@@ -41,10 +38,6 @@ export abstract class deviceBase {
41
38
  protected deviceModel!: SwitchBotModel;
42
39
  protected deviceBLEModel!: SwitchBotBLEModel;
43
40
 
44
- // Webhook
45
- protected readonly webhookEventHandler: { [x: string]: (context: any) => void } = {};
46
- protected webhookEventListener: Server | null = null;
47
-
48
41
  // MQTT
49
42
  protected deviceMqttURL!: string;
50
43
  protected deviceMqttOptions!: any;
@@ -59,8 +52,6 @@ export abstract class deviceBase {
59
52
  //MQTT stuff
60
53
  protected mqttClient: MqttClient | null = null;
61
54
 
62
-
63
-
64
55
  constructor(
65
56
  protected readonly platform: SwitchBotPlatform,
66
57
  protected accessory: PlatformAccessory,
@@ -82,8 +73,6 @@ export abstract class deviceBase {
82
73
  this.getDeviceContext(accessory, device);
83
74
  this.getDeviceScanDuration(accessory, device);
84
75
  this.getMqttSettings(device);
85
- this.setupMqtt(device);
86
- this.setupwebhook();
87
76
 
88
77
  // Set accessory information
89
78
  accessory
@@ -91,7 +80,7 @@ export abstract class deviceBase {
91
80
  .setCharacteristic(this.hap.Characteristic.Manufacturer, 'SwitchBot')
92
81
  .setCharacteristic(this.hap.Characteristic.AppMatchingIdentifier, 'id1087374760')
93
82
  .setCharacteristic(this.hap.Characteristic.Name, device.deviceName ?? accessory.displayName)
94
- //.setCharacteristic(this.hap.Characteristic.ConfiguredName, device.deviceName ?? accessory.displayName)
83
+ .setCharacteristic(this.hap.Characteristic.ConfiguredName, String(device.deviceName ?? accessory.displayName))
95
84
  .setCharacteristic(this.hap.Characteristic.Model, device.model ?? accessory.context.model)
96
85
  .setCharacteristic(this.hap.Characteristic.ProductData, device.deviceId ?? accessory.context.deviceId)
97
86
  .setCharacteristic(this.hap.Characteristic.SerialNumber, device.deviceId);
@@ -203,6 +192,12 @@ export abstract class deviceBase {
203
192
  if (device.mqttURL !== '') {
204
193
  deviceConfig['mqttURL'] = device.mqttURL;
205
194
  }
195
+ if (device.mqttOptions) {
196
+ deviceConfig['mqttOptions'] = device.mqttOptions;
197
+ }
198
+ if (device.mqttPubOptions) {
199
+ deviceConfig['mqttPubOptions'] = device.mqttPubOptions;
200
+ }
206
201
  if (device.maxRetries !== 0) {
207
202
  deviceConfig['maxRetries'] = device.maxRetries;
208
203
  }
@@ -304,7 +299,7 @@ export abstract class deviceBase {
304
299
  */
305
300
  async mqttPublish(message: string, topic?: string) {
306
301
  const mac = this.device.deviceId?.toLowerCase().match(/[\s\S]{1,2}/g)?.join(':');
307
- const options = this.device.mqttPubOptions ?? {};
302
+ const options = this.deviceMqttPubOptions ?? {};
308
303
  const mqttTopic = topic ? `/${topic}` : '';
309
304
  const mqttMessageTopic = topic ? `${topic}/` : '';
310
305
  this.mqttClient?.publish(`homebridge-switchbot/${this.device.deviceType}/${mac}${mqttTopic}`, `${message}`, options);
@@ -328,187 +323,6 @@ export abstract class deviceBase {
328
323
  + ` ${mqttPubOptions} mqttPubOptions: ${JSON.stringify(this.deviceMqttPubOptions)}`);
329
324
  }
330
325
 
331
- /*
332
- * Setup MQTT hadler if URL is specified.
333
- */
334
- async setupMqtt(device: device & devicesConfig): Promise<void> {
335
- if (device.mqttURL) {
336
- try {
337
- const { connectAsync } = asyncmqtt;
338
- this.mqttClient = await connectAsync(device.mqttURL, device.mqttOptions || {});
339
- this.debugLog('MQTT connection has been established successfully.');
340
- this.mqttClient.on('error', (e: Error) => {
341
- this.errorLog(`Failed to publish MQTT messages. ${e}`);
342
- });
343
- } catch (e) {
344
- this.mqttClient = null;
345
- this.errorLog(`Failed to establish MQTT connection. ${e}`);
346
- }
347
- }
348
- }
349
-
350
- async setupDeviceMqtt(): Promise<void> {
351
- if (this.config.options?.mqttURL) {
352
- try {
353
- const { connectAsync } = asyncmqtt;
354
- this.mqttClient = await connectAsync(this.config.options?.mqttURL, this.config.options.mqttOptions || {});
355
- await this.debugLog('MQTT connection has been established successfully.');
356
- this.mqttClient.on('error', async (e: Error) => {
357
- await this.errorLog(`Failed to publish MQTT messages. ${e}`);
358
- });
359
- if (!this.config.options?.webhookURL) {
360
- // receive webhook events via MQTT
361
- await this.infoLog(`Webhook is configured to be received through ${this.config.options.mqttURL}/homebridge-switchbot/webhook.`);
362
- this.mqttClient.subscribe('homebridge-switchbot/webhook/+');
363
- this.mqttClient.on('message', async (topic: string, message) => {
364
- try {
365
- await this.debugLog(`Received Webhook via MQTT: ${topic}=${message}`);
366
- const context = JSON.parse(message.toString());
367
- this.webhookEventHandler[context.deviceMac]?.(context);
368
- } catch (e: any) {
369
- await this.errorLog(`Failed to handle webhook event. Error:${e}`);
370
- }
371
- });
372
- }
373
- } catch (e) {
374
- this.mqttClient = null;
375
- await this.errorLog(`Failed to establish MQTT connection. ${e}`);
376
- }
377
- }
378
- }
379
-
380
- async setupwebhook() {
381
- //webhook configuration
382
- if (this.config.options?.webhookURL) {
383
- const url = this.config.options?.webhookURL;
384
-
385
- try {
386
- const xurl = new URL(url);
387
- const port = Number(xurl.port);
388
- const path = xurl.pathname;
389
- this.webhookEventListener = createServer((request: IncomingMessage, response: ServerResponse) => {
390
- try {
391
- if (request.url === path && request.method === 'POST') {
392
- request.on('data', async (data) => {
393
- try {
394
- const body = JSON.parse(data);
395
- await this.debugLog(`Received Webhook: ${JSON.stringify(body)}`);
396
- if (this.config.options?.mqttURL) {
397
- const mac = body.context.deviceMac
398
- ?.toLowerCase()
399
- .match(/[\s\S]{1,2}/g)
400
- ?.join(':');
401
- const options = this.config.options?.mqttPubOptions || {};
402
- this.mqttClient?.publish(`homebridge-switchbot/webhook/${mac}`, `${JSON.stringify(body.context)}`, options);
403
- }
404
- this.webhookEventHandler[body.context.deviceMac]?.(body.context);
405
- } catch (e: any) {
406
- this.errorLog(`Failed to handle webhook event. Error:${e}`);
407
- }
408
- });
409
- response.writeHead(200, { 'Content-Type': 'text/plain' });
410
- response.end('OK');
411
- }
412
- // else {
413
- // response.writeHead(403, {'Content-Type': 'text/plain'});
414
- // response.end(`NG`);
415
- // }
416
- } catch (e: any) {
417
- this.errorLog(`Failed to handle webhook event. Error:${e}`);
418
- }
419
- }).listen(port ? port : 80);
420
- } catch (e: any) {
421
- this.errorLog(`Failed to create webhook listener. Error:${e.message}`);
422
- return;
423
- }
424
-
425
- try {
426
- const { body, statusCode } = await request(setupWebhook, {
427
- method: 'POST',
428
- headers: this.platform.generateHeaders(),
429
- body: JSON.stringify({
430
- 'action': 'setupWebhook',
431
- 'url': url,
432
- 'deviceList': 'ALL',
433
- }),
434
- });
435
- const response: any = await body.json();
436
- await this.debugLog(`setupWebhook: url:${url}`);
437
- await this.debugLog(`setupWebhook: body:${JSON.stringify(response)}`);
438
- await this.debugLog(`setupWebhook: statusCode:${statusCode}`);
439
- if (statusCode !== 200 || response?.statusCode !== 100) {
440
- this.errorLog(`Failed to configure webhook. Existing webhook well be overridden. HTTP:${statusCode} API:${response?.statusCode} `
441
- + `message:${response?.message}`);
442
- }
443
- } catch (e: any) {
444
- this.errorLog(`Failed to configure webhook. Error: ${e.message}`);
445
- }
446
-
447
- try {
448
- const { body, statusCode } = await request(updateWebhook, {
449
- method: 'POST', headers: this.platform.generateHeaders(), body: JSON.stringify({
450
- 'action': 'updateWebhook',
451
- 'config': {
452
- 'url': url,
453
- 'enable': true,
454
- },
455
- }),
456
- });
457
- const response: any = await body.json();
458
- await this.debugLog(`updateWebhook: url:${url}`);
459
- await this.debugLog(`updateWebhook: body:${JSON.stringify(response)}`);
460
- await this.debugLog(`updateWebhook: statusCode:${statusCode}`);
461
- if (statusCode !== 200 || response?.statusCode !== 100) {
462
- this.errorLog(`Failed to update webhook. HTTP:${statusCode} API:${response?.statusCode} message:${response?.message}`);
463
- }
464
- } catch (e: any) {
465
- this.errorLog(`Failed to update webhook. Error:${e.message}`);
466
- }
467
-
468
- try {
469
- const { body, statusCode } = await request(queryWebhook, {
470
- method: 'POST',
471
- headers: this.platform.generateHeaders(),
472
- body: JSON.stringify({
473
- 'action': 'queryUrl',
474
- }),
475
- });
476
- const response: any = await body.json();
477
- await this.debugLog(`queryWebhook: body:${JSON.stringify(response)}`);
478
- await this.debugLog(`queryWebhook: statusCode:${statusCode}`);
479
- if (statusCode !== 200 || response?.statusCode !== 100) {
480
- this.errorLog(`Failed to query webhook. HTTP:${statusCode} API:${response?.statusCode} message:${response?.message}`);
481
- } else {
482
- this.infoLog(`Listening webhook on ${response?.body?.urls[0]}`);
483
- }
484
- } catch (e: any) {
485
- this.errorLog(`Failed to query webhook. Error:${e}`);
486
- }
487
-
488
- this.api.on('shutdown', async () => {
489
- try {
490
- const { body, statusCode } = await request(deleteWebhook, {
491
- method: 'POST',
492
- headers: this.platform.generateHeaders(),
493
- body: JSON.stringify({
494
- 'action': 'deleteWebhook',
495
- 'url': url,
496
- }),
497
- });
498
- const response: any = await body.json();
499
- await this.debugLog(`deleteWebhook: url:${url}, body:${JSON.stringify(response)}, statusCode:${statusCode}`);
500
- if (statusCode !== 200 || response?.statusCode !== 100) {
501
- this.errorLog(`Failed to delete webhook. HTTP:${statusCode} API:${response?.statusCode} message:${response?.message}`);
502
- } else {
503
- this.infoLog('Unregistered webhook to close listening.');
504
- }
505
- } catch (e: any) {
506
- this.errorLog(`Failed to delete webhook. Error:${e.message}`);
507
- }
508
- });
509
- }
510
- }
511
-
512
326
  /*
513
327
  * Setup EVE history graph feature if enabled.
514
328
  */
package/src/device/fan.ts CHANGED
@@ -369,7 +369,7 @@ export class Fan extends deviceBase {
369
369
  async registerWebhook() {
370
370
  if (this.device.webhook) {
371
371
  await this.debugLog('is listening webhook.');
372
- this.webhookEventHandler[this.device.deviceId] = async (context: batteryCirculatorFanWebhookContext) => {
372
+ this.platform.webhookEventHandler[this.device.deviceId] = async (context: batteryCirculatorFanWebhookContext) => {
373
373
  try {
374
374
  await this.debugLog(`received Webhook: ${JSON.stringify(context)}`);
375
375
  this.webhookContext = context;
package/src/device/hub.ts CHANGED
@@ -333,7 +333,7 @@ export class Hub extends deviceBase {
333
333
  async registerWebhook(): Promise<void> {
334
334
  if (this.device.webhook) {
335
335
  await this.debugLog('is listening webhook.');
336
- this.webhookEventHandler[this.device.deviceId] = async (context: hub2WebhookContext) => {
336
+ this.platform.webhookEventHandler[this.device.deviceId] = async (context: hub2WebhookContext) => {
337
337
  try {
338
338
  await this.debugLog(`received Webhook: ${JSON.stringify(context)}`);
339
339
  this.webhookContext = context;
@@ -373,7 +373,7 @@ export class Humidifier extends deviceBase {
373
373
  async registerWebhook() {
374
374
  if (this.device.webhook) {
375
375
  await this.debugLog('is listening webhook.');
376
- this.webhookEventHandler[this.device.deviceId] = async (context: humidifierWebhookContext) => {
376
+ this.platform.webhookEventHandler[this.device.deviceId] = async (context: humidifierWebhookContext) => {
377
377
  try {
378
378
  await this.debugLog(`received Webhook: ${JSON.stringify(context)}`);
379
379
  this.webhookContext = context;
@@ -326,7 +326,7 @@ export class IOSensor extends deviceBase {
326
326
  async registerWebhook() {
327
327
  if (this.device.webhook) {
328
328
  await this.debugLog('is listening webhook.');
329
- this.webhookEventHandler[this.device.deviceId] = async (context: outdoorMeterWebhookContext) => {
329
+ this.platform.webhookEventHandler[this.device.deviceId] = async (context: outdoorMeterWebhookContext) => {
330
330
  try {
331
331
  await this.debugLog(`received Webhook: ${JSON.stringify(context)}`);
332
332
  this.webhookContext = context;
@@ -356,7 +356,7 @@ export class StripLight extends deviceBase {
356
356
  async registerWebhook() {
357
357
  if (this.device.webhook) {
358
358
  await this.debugLog('is listening webhook.');
359
- this.webhookEventHandler[this.device.deviceId] = async (context: stripLightWebhookContext) => {
359
+ this.platform.webhookEventHandler[this.device.deviceId] = async (context: stripLightWebhookContext) => {
360
360
  try {
361
361
  await this.debugLog(`received Webhook: ${JSON.stringify(context)}`);
362
362
  this.webhookContext = context;
@@ -353,7 +353,7 @@ export class Lock extends deviceBase {
353
353
  async registerWebhook() {
354
354
  if (this.device.webhook) {
355
355
  await this.debugLog('is listening webhook.');
356
- this.webhookEventHandler[this.device.deviceId] = async (context: lockWebhookContext | lockProWebhookContext) => {
356
+ this.platform.webhookEventHandler[this.device.deviceId] = async (context: lockWebhookContext | lockProWebhookContext) => {
357
357
  try {
358
358
  await this.debugLog(`received Webhook: ${JSON.stringify(context)}`);
359
359
  this.webhookContext = context;
@@ -320,7 +320,7 @@ export class Meter extends deviceBase {
320
320
  async registerWebhook() {
321
321
  if (this.device.webhook) {
322
322
  await this.debugLog('is listening webhook.');
323
- this.webhookEventHandler[this.device.deviceId] = async (context: meterWebhookContext) => {
323
+ this.platform.webhookEventHandler[this.device.deviceId] = async (context: meterWebhookContext) => {
324
324
  try {
325
325
  await this.debugLog(`received Webhook: ${JSON.stringify(context)}`);
326
326
  this.webhookContext = context;
@@ -321,7 +321,7 @@ export class MeterPlus extends deviceBase {
321
321
  async registerWebhook() {
322
322
  if (this.device.webhook) {
323
323
  await this.debugLog('is listening webhook.');
324
- this.webhookEventHandler[this.device.deviceId] = async (context: meterPlusWebhookContext) => {
324
+ this.platform.webhookEventHandler[this.device.deviceId] = async (context: meterPlusWebhookContext) => {
325
325
  try {
326
326
  await this.debugLog(`received Webhook: ${JSON.stringify(context)}`);
327
327
  this.webhookContext = context;
@@ -292,7 +292,7 @@ export class Motion extends deviceBase {
292
292
  async registerWebhook() {
293
293
  if (this.device.webhook) {
294
294
  await this.debugLog('is listening webhook.');
295
- this.webhookEventHandler[this.device.deviceId] = async (context: motionSensorWebhookContext) => {
295
+ this.platform.webhookEventHandler[this.device.deviceId] = async (context: motionSensorWebhookContext) => {
296
296
  try {
297
297
  await this.debugLog(`received Webhook: ${JSON.stringify(context)}`);
298
298
  this.webhookContext = context;
@@ -204,7 +204,7 @@ export class Plug extends deviceBase {
204
204
  async registerWebhook() {
205
205
  if (this.device.webhook) {
206
206
  await this.debugLog('is listening webhook.');
207
- this.webhookEventHandler[this.device.deviceId] = async (context: plugWebhookContext | plugMiniUSWebhookContext
207
+ this.platform.webhookEventHandler[this.device.deviceId] = async (context: plugWebhookContext | plugMiniUSWebhookContext
208
208
  | plugMiniJPWebhookContext) => {
209
209
  try {
210
210
  await this.debugLog(`received Webhook: ${JSON.stringify(context)}`);
@@ -304,7 +304,7 @@ export class RobotVacuumCleaner extends deviceBase {
304
304
  async registerWebhook() {
305
305
  if (this.device.webhook) {
306
306
  await this.debugLog('is listening webhook.');
307
- this.webhookEventHandler[this.device.deviceId] = async (context: robotVacuumCleanerS1WebhookContext
307
+ this.platform.webhookEventHandler[this.device.deviceId] = async (context: robotVacuumCleanerS1WebhookContext
308
308
  | robotVacuumCleanerS1PlusWebhookContext | floorCleaningRobotS10WebhookContext) => {
309
309
  try {
310
310
  await this.debugLog(`received Webhook: ${JSON.stringify(context)}`);
@@ -289,7 +289,7 @@ export class WaterDetector extends deviceBase {
289
289
  async registerWebhook() {
290
290
  if (this.device.webhook) {
291
291
  await this.debugLog('is listening webhook.');
292
- this.webhookEventHandler[this.device.deviceId] = async (context: waterLeakDetectorWebhookContext) => {
292
+ this.platform.webhookEventHandler[this.device.deviceId] = async (context: waterLeakDetectorWebhookContext) => {
293
293
  try {
294
294
  await this.debugLog(`received Webhook: ${JSON.stringify(context)}`);
295
295
  this.webhookContext = context;
@@ -44,7 +44,7 @@ export abstract class irdeviceBase {
44
44
  .setCharacteristic(this.hap.Characteristic.Manufacturer, 'SwitchBot')
45
45
  .setCharacteristic(this.hap.Characteristic.AppMatchingIdentifier, 'id1087374760')
46
46
  .setCharacteristic(this.hap.Characteristic.Name, accessory.context.name ?? accessory.displayName)
47
- .setCharacteristic(this.hap.Characteristic.ConfiguredName, accessory.context.name ?? accessory.displayName)
47
+ .setCharacteristic(this.hap.Characteristic.ConfiguredName, String(accessory.context.name ?? accessory.displayName))
48
48
  .setCharacteristic(this.hap.Characteristic.Model, accessory.context.model ?? 'Unknown')
49
49
  .setCharacteristic(this.hap.Characteristic.ProductData, accessory.context.deviceId)
50
50
  .setCharacteristic(this.hap.Characteristic.SerialNumber, accessory.context.deviceId);
package/src/platform.ts CHANGED
@@ -33,20 +33,24 @@ import { AirConditioner } from './irdevice/airconditioner.js';
33
33
 
34
34
  import { Buffer } from 'buffer';
35
35
  import { request } from 'undici';
36
+ import asyncmqtt from 'async-mqtt';
37
+ import { createServer } from 'http';
36
38
  import { queueScheduler } from 'rxjs';
37
39
  import fakegato from 'fakegato-history';
38
40
  import crypto, { randomUUID } from 'crypto';
39
41
  import { readFileSync, writeFileSync } from 'fs';
40
42
  import { EveHomeKitTypes } from 'homebridge-lib/EveHomeKitTypes';
41
- import { PLATFORM_NAME, PLUGIN_NAME, Devices } from './settings.js';
43
+ import { PLATFORM_NAME, PLUGIN_NAME, Devices, deleteWebhook, queryWebhook, setupWebhook, updateWebhook } from './settings.js';
42
44
  import { isBlindTiltDevice, isCurtainDevice, sleep } from './utils.js';
43
45
 
44
46
  import type { UrlObject } from 'url';
47
+ import type { MqttClient } from 'mqtt';
45
48
  import type { Dispatcher } from 'undici';
46
49
  import type { irdevice } from './types/irdevicelist.js';
47
50
  import type { blindTilt, curtain, curtain3, device } from './types/devicelist.js';
48
51
  import type { API, DynamicPlatformPlugin, Logging, PlatformAccessory } from 'homebridge';
49
52
  import type { SwitchBotPlatformConfig, devicesConfig, irDevicesConfig } from './settings.js';
53
+ import type { IncomingMessage, Server, ServerResponse } from 'http';
50
54
 
51
55
  /**
52
56
  * HomebridgePlatform
@@ -67,8 +71,12 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin {
67
71
  platformLogging!: SwitchBotPlatformConfig['logging'];
68
72
  config!: SwitchBotPlatformConfig;
69
73
 
70
- public readonly fakegatoAPI: any;
74
+ mqttClient: MqttClient | null = null;
75
+ webhookEventListener: Server | null = null;
76
+
71
77
  public readonly eve: any;
78
+ public readonly fakegatoAPI: any;
79
+ public readonly webhookEventHandler: { [x: string]: (context: any) => void } = {};
72
80
 
73
81
  constructor(
74
82
  log: Logging,
@@ -136,6 +144,171 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin {
136
144
  await this.debugErrorLog(`Failed to Discover, Error: ${e}`);
137
145
  }
138
146
  });
147
+
148
+ this.setupMqtt();
149
+ this.setupwebhook();
150
+ }
151
+
152
+ async setupMqtt(): Promise<void> {
153
+ if (this.config.options?.mqttURL) {
154
+ try {
155
+ const { connectAsync } = asyncmqtt;
156
+ this.mqttClient = await connectAsync(this.config.options?.mqttURL, this.config.options.mqttOptions || {});
157
+ await this.debugLog('MQTT connection has been established successfully.');
158
+ this.mqttClient.on('error', async (e: Error) => {
159
+ await this.errorLog(`Failed to publish MQTT messages. ${e}`);
160
+ });
161
+ if (!this.config.options?.webhookURL) {
162
+ // receive webhook events via MQTT
163
+ await this.infoLog(`Webhook is configured to be received through ${this.config.options.mqttURL}/homebridge-switchbot/webhook.`);
164
+ this.mqttClient.subscribe('homebridge-switchbot/webhook/+');
165
+ this.mqttClient.on('message', async (topic: string, message) => {
166
+ try {
167
+ await await this.debugLog(`Received Webhook via MQTT: ${topic}=${message}`);
168
+ const context = JSON.parse(message.toString());
169
+ this.webhookEventHandler[context.deviceMac]?.(context);
170
+ } catch (e: any) {
171
+ await this.errorLog(`Failed to handle webhook event. Error:${e}`);
172
+ }
173
+ });
174
+ }
175
+ } catch (e) {
176
+ this.mqttClient = null;
177
+ await this.errorLog(`Failed to establish MQTT connection. ${e}`);
178
+ }
179
+ }
180
+ }
181
+
182
+ async setupwebhook() {
183
+ //webhook configuration
184
+ if (this.config.options?.webhookURL) {
185
+ const url = this.config.options?.webhookURL;
186
+
187
+ try {
188
+ const xurl = new URL(url);
189
+ const port = Number(xurl.port);
190
+ const path = xurl.pathname;
191
+ this.webhookEventListener = createServer((request: IncomingMessage, response: ServerResponse) => {
192
+ try {
193
+ if (request.url === path && request.method === 'POST') {
194
+ request.on('data', async (data) => {
195
+ try {
196
+ const body = JSON.parse(data);
197
+ await this.debugLog(`Received Webhook: ${JSON.stringify(body)}`);
198
+ if (this.config.options?.mqttURL) {
199
+ const mac = body.context.deviceMac
200
+ ?.toLowerCase()
201
+ .match(/[\s\S]{1,2}/g)
202
+ ?.join(':');
203
+ const options = this.config.options?.mqttPubOptions || {};
204
+ this.mqttClient?.publish(`homebridge-switchbot/webhook/${mac}`, `${JSON.stringify(body.context)}`, options);
205
+ }
206
+ this.webhookEventHandler[body.context.deviceMac]?.(body.context);
207
+ } catch (e: any) {
208
+ this.errorLog(`Failed to handle webhook event. Error:${e}`);
209
+ }
210
+ });
211
+ response.writeHead(200, { 'Content-Type': 'text/plain' });
212
+ response.end('OK');
213
+ }
214
+ // else {
215
+ // response.writeHead(403, {'Content-Type': 'text/plain'});
216
+ // response.end(`NG`);
217
+ // }
218
+ } catch (e: any) {
219
+ this.errorLog(`Failed to handle webhook event. Error:${e}`);
220
+ }
221
+ }).listen(port ? port : 80);
222
+ } catch (e: any) {
223
+ this.errorLog(`Failed to create webhook listener. Error:${e.message}`);
224
+ return;
225
+ }
226
+
227
+ try {
228
+ const { body, statusCode } = await request(setupWebhook, {
229
+ method: 'POST',
230
+ headers: this.generateHeaders(),
231
+ body: JSON.stringify({
232
+ 'action': 'setupWebhook',
233
+ 'url': url,
234
+ 'deviceList': 'ALL',
235
+ }),
236
+ });
237
+ const response: any = await body.json();
238
+ await this.debugLog(`setupWebhook: url:${url}`);
239
+ await this.debugLog(`setupWebhook: body:${JSON.stringify(response)}`);
240
+ await this.debugLog(`setupWebhook: statusCode:${statusCode}`);
241
+ if (statusCode !== 200 || response?.statusCode !== 100) {
242
+ this.errorLog(`Failed to configure webhook. Existing webhook well be overridden. HTTP:${statusCode} API:${response?.statusCode} `
243
+ + `message:${response?.message}`);
244
+ }
245
+ } catch (e: any) {
246
+ this.errorLog(`Failed to configure webhook. Error: ${e.message}`);
247
+ }
248
+
249
+ try {
250
+ const { body, statusCode } = await request(updateWebhook, {
251
+ method: 'POST', headers: this.generateHeaders(), body: JSON.stringify({
252
+ 'action': 'updateWebhook',
253
+ 'config': {
254
+ 'url': url,
255
+ 'enable': true,
256
+ },
257
+ }),
258
+ });
259
+ const response: any = await body.json();
260
+ await this.debugLog(`updateWebhook: url:${url}`);
261
+ await this.debugLog(`updateWebhook: body:${JSON.stringify(response)}`);
262
+ await this.debugLog(`updateWebhook: statusCode:${statusCode}`);
263
+ if (statusCode !== 200 || response?.statusCode !== 100) {
264
+ this.errorLog(`Failed to update webhook. HTTP:${statusCode} API:${response?.statusCode} message:${response?.message}`);
265
+ }
266
+ } catch (e: any) {
267
+ this.errorLog(`Failed to update webhook. Error:${e.message}`);
268
+ }
269
+
270
+ try {
271
+ const { body, statusCode } = await request(queryWebhook, {
272
+ method: 'POST',
273
+ headers: this.generateHeaders(),
274
+ body: JSON.stringify({
275
+ 'action': 'queryUrl',
276
+ }),
277
+ });
278
+ const response: any = await body.json();
279
+ await this.debugLog(`queryWebhook: body:${JSON.stringify(response)}`);
280
+ await this.debugLog(`queryWebhook: statusCode:${statusCode}`);
281
+ if (statusCode !== 200 || response?.statusCode !== 100) {
282
+ this.errorLog(`Failed to query webhook. HTTP:${statusCode} API:${response?.statusCode} message:${response?.message}`);
283
+ } else {
284
+ this.infoLog(`Listening webhook on ${response?.body?.urls[0]}`);
285
+ }
286
+ } catch (e: any) {
287
+ this.errorLog(`Failed to query webhook. Error:${e}`);
288
+ }
289
+
290
+ this.api.on('shutdown', async () => {
291
+ try {
292
+ const { body, statusCode } = await request(deleteWebhook, {
293
+ method: 'POST',
294
+ headers: this.generateHeaders(),
295
+ body: JSON.stringify({
296
+ 'action': 'deleteWebhook',
297
+ 'url': url,
298
+ }),
299
+ });
300
+ const response: any = await body.json();
301
+ await this.debugLog(`deleteWebhook: url:${url}, body:${JSON.stringify(response)}, statusCode:${statusCode}`);
302
+ if (statusCode !== 200 || response?.statusCode !== 100) {
303
+ this.errorLog(`Failed to delete webhook. HTTP:${statusCode} API:${response?.statusCode} message:${response?.message}`);
304
+ } else {
305
+ this.infoLog('Unregistered webhook to close listening.');
306
+ }
307
+ } catch (e: any) {
308
+ this.errorLog(`Failed to delete webhook. Error:${e.message}`);
309
+ }
310
+ });
311
+ }
139
312
  }
140
313
 
141
314
  /**