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

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 (121) hide show
  1. package/dist/device/blindtilt.d.ts.map +1 -1
  2. package/dist/device/blindtilt.js +3 -1
  3. package/dist/device/blindtilt.js.map +1 -1
  4. package/dist/device/bot.d.ts.map +1 -1
  5. package/dist/device/bot.js +21 -1
  6. package/dist/device/bot.js.map +1 -1
  7. package/dist/device/ceilinglight.d.ts.map +1 -1
  8. package/dist/device/ceilinglight.js +3 -1
  9. package/dist/device/ceilinglight.js.map +1 -1
  10. package/dist/device/colorbulb.d.ts.map +1 -1
  11. package/dist/device/colorbulb.js +3 -1
  12. package/dist/device/colorbulb.js.map +1 -1
  13. package/dist/device/contact.d.ts.map +1 -1
  14. package/dist/device/contact.js +3 -1
  15. package/dist/device/contact.js.map +1 -1
  16. package/dist/device/curtain.d.ts.map +1 -1
  17. package/dist/device/curtain.js +3 -1
  18. package/dist/device/curtain.js.map +1 -1
  19. package/dist/device/device.d.ts +0 -8
  20. package/dist/device/device.d.ts.map +1 -1
  21. package/dist/device/device.js +9 -195
  22. package/dist/device/device.js.map +1 -1
  23. package/dist/device/fan.d.ts.map +1 -1
  24. package/dist/device/fan.js +3 -1
  25. package/dist/device/fan.js.map +1 -1
  26. package/dist/device/hub.d.ts.map +1 -1
  27. package/dist/device/hub.js +3 -1
  28. package/dist/device/hub.js.map +1 -1
  29. package/dist/device/humidifier.d.ts.map +1 -1
  30. package/dist/device/humidifier.js +3 -1
  31. package/dist/device/humidifier.js.map +1 -1
  32. package/dist/device/iosensor.d.ts.map +1 -1
  33. package/dist/device/iosensor.js +3 -1
  34. package/dist/device/iosensor.js.map +1 -1
  35. package/dist/device/lightstrip.d.ts.map +1 -1
  36. package/dist/device/lightstrip.js +3 -1
  37. package/dist/device/lightstrip.js.map +1 -1
  38. package/dist/device/lock.d.ts.map +1 -1
  39. package/dist/device/lock.js +3 -1
  40. package/dist/device/lock.js.map +1 -1
  41. package/dist/device/meter.d.ts.map +1 -1
  42. package/dist/device/meter.js +3 -1
  43. package/dist/device/meter.js.map +1 -1
  44. package/dist/device/meterplus.d.ts.map +1 -1
  45. package/dist/device/meterplus.js +3 -1
  46. package/dist/device/meterplus.js.map +1 -1
  47. package/dist/device/motion.d.ts.map +1 -1
  48. package/dist/device/motion.js +3 -1
  49. package/dist/device/motion.js.map +1 -1
  50. package/dist/device/plug.d.ts.map +1 -1
  51. package/dist/device/plug.js +3 -1
  52. package/dist/device/plug.js.map +1 -1
  53. package/dist/device/robotvacuumcleaner.d.ts.map +1 -1
  54. package/dist/device/robotvacuumcleaner.js +3 -1
  55. package/dist/device/robotvacuumcleaner.js.map +1 -1
  56. package/dist/device/waterdetector.d.ts.map +1 -1
  57. package/dist/device/waterdetector.js +3 -1
  58. package/dist/device/waterdetector.js.map +1 -1
  59. package/dist/irdevice/airconditioner.d.ts.map +1 -1
  60. package/dist/irdevice/airconditioner.js +2 -0
  61. package/dist/irdevice/airconditioner.js.map +1 -1
  62. package/dist/irdevice/airpurifier.d.ts.map +1 -1
  63. package/dist/irdevice/airpurifier.js +2 -0
  64. package/dist/irdevice/airpurifier.js.map +1 -1
  65. package/dist/irdevice/camera.d.ts.map +1 -1
  66. package/dist/irdevice/camera.js +2 -0
  67. package/dist/irdevice/camera.js.map +1 -1
  68. package/dist/irdevice/fan.d.ts.map +1 -1
  69. package/dist/irdevice/fan.js +2 -0
  70. package/dist/irdevice/fan.js.map +1 -1
  71. package/dist/irdevice/irdevice.js +1 -1
  72. package/dist/irdevice/irdevice.js.map +1 -1
  73. package/dist/irdevice/light.d.ts.map +1 -1
  74. package/dist/irdevice/light.js +2 -0
  75. package/dist/irdevice/light.js.map +1 -1
  76. package/dist/irdevice/other.d.ts.map +1 -1
  77. package/dist/irdevice/other.js +2 -0
  78. package/dist/irdevice/other.js.map +1 -1
  79. package/dist/irdevice/tv.js +4 -4
  80. package/dist/irdevice/tv.js.map +1 -1
  81. package/dist/irdevice/vacuumcleaner.d.ts.map +1 -1
  82. package/dist/irdevice/vacuumcleaner.js +2 -0
  83. package/dist/irdevice/vacuumcleaner.js.map +1 -1
  84. package/dist/irdevice/waterheater.d.ts.map +1 -1
  85. package/dist/irdevice/waterheater.js +2 -0
  86. package/dist/irdevice/waterheater.js.map +1 -1
  87. package/dist/platform.d.ts +10 -1
  88. package/dist/platform.d.ts.map +1 -1
  89. package/dist/platform.js +175 -2
  90. package/dist/platform.js.map +1 -1
  91. package/package.json +1 -1
  92. package/src/device/blindtilt.ts +4 -1
  93. package/src/device/bot.ts +32 -1
  94. package/src/device/ceilinglight.ts +4 -1
  95. package/src/device/colorbulb.ts +4 -1
  96. package/src/device/contact.ts +4 -1
  97. package/src/device/curtain.ts +3 -1
  98. package/src/device/device.ts +9 -195
  99. package/src/device/fan.ts +4 -1
  100. package/src/device/hub.ts +4 -1
  101. package/src/device/humidifier.ts +4 -1
  102. package/src/device/iosensor.ts +4 -1
  103. package/src/device/lightstrip.ts +4 -1
  104. package/src/device/lock.ts +4 -1
  105. package/src/device/meter.ts +4 -1
  106. package/src/device/meterplus.ts +4 -1
  107. package/src/device/motion.ts +4 -1
  108. package/src/device/plug.ts +4 -1
  109. package/src/device/robotvacuumcleaner.ts +4 -1
  110. package/src/device/waterdetector.ts +4 -1
  111. package/src/irdevice/airconditioner.ts +2 -1
  112. package/src/irdevice/airpurifier.ts +2 -0
  113. package/src/irdevice/camera.ts +2 -0
  114. package/src/irdevice/fan.ts +2 -0
  115. package/src/irdevice/irdevice.ts +1 -1
  116. package/src/irdevice/light.ts +2 -0
  117. package/src/irdevice/other.ts +2 -0
  118. package/src/irdevice/tv.ts +4 -4
  119. package/src/irdevice/vacuumcleaner.ts +2 -0
  120. package/src/irdevice/waterheater.ts +2 -0
  121. package/src/platform.ts +175 -2
@@ -55,6 +55,9 @@ export class WaterDetector extends deviceBase {
55
55
  device: device & devicesConfig,
56
56
  ) {
57
57
  super(platform, accessory, device);
58
+ // Set category
59
+ accessory.category = this.hap.Categories.SENSOR;
60
+
58
61
  // this is subject we use to track when we need to POST changes to the SwitchBot API
59
62
  this.doWaterDetectorUpdate = new Subject();
60
63
  this.WaterDetectorUpdateInProgress = false;
@@ -289,7 +292,7 @@ export class WaterDetector extends deviceBase {
289
292
  async registerWebhook() {
290
293
  if (this.device.webhook) {
291
294
  await this.debugLog('is listening webhook.');
292
- this.webhookEventHandler[this.device.deviceId] = async (context: waterLeakDetectorWebhookContext) => {
295
+ this.platform.webhookEventHandler[this.device.deviceId] = async (context: waterLeakDetectorWebhookContext) => {
293
296
  try {
294
297
  await this.debugLog(`received Webhook: ${JSON.stringify(context)}`);
295
298
  this.webhookContext = context;
@@ -56,13 +56,14 @@ export class AirConditioner extends irdeviceBase {
56
56
  device: irdevice & irDevicesConfig,
57
57
  ) {
58
58
  super(platform, accessory, device);
59
+ // Set category
60
+ accessory.category = this.hap.Categories.AIR_CONDITIONER;
59
61
 
60
62
  // default placeholders
61
63
  this.getAirConditionerConfigSettings(accessory, device);
62
64
 
63
65
  this.ValidValues = this.hide_automode ? [1, 2] : [0, 1, 2];
64
66
 
65
-
66
67
  // Initialize HeaterCooler Service
67
68
  accessory.context.HeaterCooler = accessory.context.HeaterCooler ?? {};
68
69
  this.HeaterCooler = {
@@ -54,6 +54,8 @@ export class AirPurifier extends irdeviceBase {
54
54
  device: irdevice & irDevicesConfig,
55
55
  ) {
56
56
  super(platform, accessory, device);
57
+ // Set category
58
+ accessory.category = this.hap.Categories.AIR_PURIFIER;
57
59
 
58
60
  // Initialize AirPurifier Service
59
61
  accessory.context.AirPurifier = accessory.context.AirPurifier ?? {};
@@ -28,6 +28,8 @@ export class Camera extends irdeviceBase {
28
28
  device: irdevice & irDevicesConfig,
29
29
  ) {
30
30
  super(platform, accessory, device);
31
+ // Set category
32
+ accessory.category = this.hap.Categories.CAMERA;
31
33
 
32
34
  // Initialize Switch Service
33
35
  accessory.context.Switch = accessory.context.Switch ?? {};
@@ -31,6 +31,8 @@ export class IRFan extends irdeviceBase {
31
31
  device: irdevice & irDevicesConfig,
32
32
  ) {
33
33
  super(platform, accessory, device);
34
+ // Set category
35
+ accessory.category = this.hap.Categories.FAN;
34
36
 
35
37
  // Initialize Switch Service
36
38
  accessory.context.Fan = accessory.context.Fan ?? {};
@@ -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);
@@ -42,6 +42,8 @@ export class Light extends irdeviceBase {
42
42
  device: irdevice & irDevicesConfig,
43
43
  ) {
44
44
  super(platform, accessory, device);
45
+ // Set category
46
+ accessory.category = this.hap.Categories.LIGHTBULB;
45
47
 
46
48
  if (!device.irlight?.stateless) {
47
49
  // Initialize LightBulb Service
@@ -85,6 +85,8 @@ export class Others extends irdeviceBase {
85
85
  device: irdevice & irDevicesConfig,
86
86
  ) {
87
87
  super(platform, accessory, device);
88
+ // Set category
89
+ accessory.category = this.hap.Categories.OTHER;
88
90
 
89
91
  // default placeholders
90
92
  this.getOtherConfigSettings(device);
@@ -59,20 +59,20 @@ export class TV extends irdeviceBase {
59
59
  switch (device.remoteType) {
60
60
  case 'Speaker':
61
61
  case 'DIY Speaker':
62
- accessory.category = this.platform.api.hap.Categories.SPEAKER;
62
+ accessory.category = this.hap.Categories.SPEAKER;
63
63
  break;
64
64
  case 'IPTV':
65
65
  case 'DIY IPTV':
66
- accessory.category = this.platform.api.hap.Categories.TV_STREAMING_STICK;
66
+ accessory.category = this.hap.Categories.TV_STREAMING_STICK;
67
67
  break;
68
68
  case 'DVD':
69
69
  case 'DIY DVD':
70
70
  case 'Set Top Box':
71
71
  case 'DIY Set Top Box':
72
- accessory.category = this.platform.api.hap.Categories.TV_SET_TOP_BOX;
72
+ accessory.category = this.hap.Categories.TV_SET_TOP_BOX;
73
73
  break;
74
74
  default:
75
- accessory.category = this.platform.api.hap.Categories.TELEVISION;
75
+ accessory.category = this.hap.Categories.TELEVISION;
76
76
  }
77
77
 
78
78
  this.Television.Service
@@ -28,6 +28,8 @@ export class VacuumCleaner extends irdeviceBase {
28
28
  device: irdevice & irDevicesConfig,
29
29
  ) {
30
30
  super(platform, accessory, device);
31
+ // Set category
32
+ accessory.category = this.hap.Categories.OTHER;
31
33
 
32
34
  // Initialize Switch Service
33
35
  accessory.context.Switch = accessory.context.Switch ?? {};
@@ -28,6 +28,8 @@ export class WaterHeater extends irdeviceBase {
28
28
  device: irdevice & irDevicesConfig,
29
29
  ) {
30
30
  super(platform, accessory, device);
31
+ // Set category
32
+ accessory.category = this.hap.Categories.FAUCET;
31
33
 
32
34
  // Initialize Switch Service
33
35
  accessory.context.Valve = accessory.context.Valve ?? {};
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
  /**