@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.
- package/dist/device/blindtilt.js +1 -1
- package/dist/device/blindtilt.js.map +1 -1
- package/dist/device/bot.js +1 -1
- package/dist/device/bot.js.map +1 -1
- package/dist/device/ceilinglight.js +1 -1
- package/dist/device/ceilinglight.js.map +1 -1
- package/dist/device/colorbulb.js +1 -1
- package/dist/device/colorbulb.js.map +1 -1
- package/dist/device/contact.js +1 -1
- package/dist/device/contact.js.map +1 -1
- package/dist/device/curtain.js +1 -1
- package/dist/device/curtain.js.map +1 -1
- package/dist/device/device.d.ts +0 -8
- package/dist/device/device.d.ts.map +1 -1
- package/dist/device/device.js +9 -195
- package/dist/device/device.js.map +1 -1
- package/dist/device/fan.js +1 -1
- package/dist/device/fan.js.map +1 -1
- package/dist/device/hub.js +1 -1
- package/dist/device/hub.js.map +1 -1
- package/dist/device/humidifier.js +1 -1
- package/dist/device/humidifier.js.map +1 -1
- package/dist/device/iosensor.js +1 -1
- package/dist/device/iosensor.js.map +1 -1
- package/dist/device/lightstrip.js +1 -1
- package/dist/device/lightstrip.js.map +1 -1
- package/dist/device/lock.js +1 -1
- package/dist/device/lock.js.map +1 -1
- package/dist/device/meter.js +1 -1
- package/dist/device/meter.js.map +1 -1
- package/dist/device/meterplus.js +1 -1
- package/dist/device/meterplus.js.map +1 -1
- package/dist/device/motion.js +1 -1
- package/dist/device/motion.js.map +1 -1
- package/dist/device/plug.js +1 -1
- package/dist/device/plug.js.map +1 -1
- package/dist/device/robotvacuumcleaner.js +1 -1
- package/dist/device/robotvacuumcleaner.js.map +1 -1
- package/dist/device/waterdetector.js +1 -1
- package/dist/device/waterdetector.js.map +1 -1
- package/dist/irdevice/irdevice.js +1 -1
- package/dist/irdevice/irdevice.js.map +1 -1
- package/dist/platform.d.ts +10 -1
- package/dist/platform.d.ts.map +1 -1
- package/dist/platform.js +175 -2
- package/dist/platform.js.map +1 -1
- package/package.json +1 -1
- package/src/device/blindtilt.ts +1 -1
- package/src/device/bot.ts +1 -1
- package/src/device/ceilinglight.ts +1 -1
- package/src/device/colorbulb.ts +1 -1
- package/src/device/contact.ts +1 -1
- package/src/device/curtain.ts +1 -1
- package/src/device/device.ts +9 -195
- package/src/device/fan.ts +1 -1
- package/src/device/hub.ts +1 -1
- package/src/device/humidifier.ts +1 -1
- package/src/device/iosensor.ts +1 -1
- package/src/device/lightstrip.ts +1 -1
- package/src/device/lock.ts +1 -1
- package/src/device/meter.ts +1 -1
- package/src/device/meterplus.ts +1 -1
- package/src/device/motion.ts +1 -1
- package/src/device/plug.ts +1 -1
- package/src/device/robotvacuumcleaner.ts +1 -1
- package/src/device/waterdetector.ts +1 -1
- package/src/irdevice/irdevice.ts +1 -1
- package/src/platform.ts +175 -2
package/src/device/device.ts
CHANGED
|
@@ -5,17 +5,14 @@
|
|
|
5
5
|
|
|
6
6
|
import { hostname } from 'os';
|
|
7
7
|
import { request } from 'undici';
|
|
8
|
-
import
|
|
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
|
-
|
|
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.
|
|
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;
|
package/src/device/humidifier.ts
CHANGED
|
@@ -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;
|
package/src/device/iosensor.ts
CHANGED
|
@@ -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;
|
package/src/device/lightstrip.ts
CHANGED
|
@@ -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;
|
package/src/device/lock.ts
CHANGED
|
@@ -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;
|
package/src/device/meter.ts
CHANGED
|
@@ -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;
|
package/src/device/meterplus.ts
CHANGED
|
@@ -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;
|
package/src/device/motion.ts
CHANGED
|
@@ -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;
|
package/src/device/plug.ts
CHANGED
|
@@ -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;
|
package/src/irdevice/irdevice.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
/**
|