@switchbot/homebridge-switchbot 5.0.0-beta.13 → 5.0.0-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.
- package/dist/devices-hap/device.d.ts.map +1 -1
- package/dist/devices-hap/device.js +104 -22
- package/dist/devices-hap/device.js.map +1 -1
- package/dist/devices-matter/BaseMatterAccessory.d.ts.map +1 -1
- package/dist/devices-matter/BaseMatterAccessory.js +25 -1
- package/dist/devices-matter/BaseMatterAccessory.js.map +1 -1
- package/dist/irdevice/irdevice.d.ts.map +1 -1
- package/dist/irdevice/irdevice.js +122 -22
- package/dist/irdevice/irdevice.js.map +1 -1
- package/dist/platform-hap.d.ts +10 -14
- package/dist/platform-hap.d.ts.map +1 -1
- package/dist/platform-hap.js +28 -66
- package/dist/platform-hap.js.map +1 -1
- package/dist/platform-matter.d.ts +11 -0
- package/dist/platform-matter.d.ts.map +1 -1
- package/dist/platform-matter.js +129 -126
- package/dist/platform-matter.js.map +1 -1
- package/dist/utils.d.ts +53 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +155 -0
- package/dist/utils.js.map +1 -1
- package/dist/verifyconfig.test.js +2 -2
- package/dist/verifyconfig.test.js.map +1 -1
- package/docs/variables/default.html +1 -1
- package/package.json +1 -1
- package/src/devices-hap/device.ts +98 -25
- package/src/devices-matter/BaseMatterAccessory.ts +25 -1
- package/src/irdevice/irdevice.ts +113 -22
- package/src/platform-hap.ts +30 -75
- package/src/platform-matter.ts +131 -126
- package/src/utils.ts +158 -1
- package/src/verifyconfig.test.ts +11 -10
|
@@ -16,6 +16,17 @@ export declare class SwitchBotMatterPlatform implements DynamicPlatformPlugin {
|
|
|
16
16
|
private switchBotBLE?;
|
|
17
17
|
private discoveredDevices;
|
|
18
18
|
private bleEventHandler;
|
|
19
|
+
private platformLogging?;
|
|
20
|
+
infoLog: (...args: any[]) => void;
|
|
21
|
+
successLog: (...args: any[]) => void;
|
|
22
|
+
debugSuccessLog: (...args: any[]) => void;
|
|
23
|
+
warnLog: (...args: any[]) => void;
|
|
24
|
+
debugWarnLog: (...args: any[]) => void;
|
|
25
|
+
errorLog: (...args: any[]) => void;
|
|
26
|
+
debugErrorLog: (...args: any[]) => void;
|
|
27
|
+
debugLog: (...args: any[]) => void;
|
|
28
|
+
loggingIsDebug: () => Promise<boolean>;
|
|
29
|
+
enablingPlatformLogging: () => Promise<boolean>;
|
|
19
30
|
constructor(log: Logging, config: SwitchBotPlatformConfig, api: API);
|
|
20
31
|
/**
|
|
21
32
|
* Normalize a deviceId for matching (uppercase alphanumerics only)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"platform-matter.d.ts","sourceRoot":"","sources":["../src/platform-matter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,GAAG,EACH,qBAAqB,EACrB,OAAO,EAEP,yBAAyB,EAC1B,MAAM,YAAY,CAAA;AACnB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AAExD,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAA;AA8B5D;;;;;GAKG;AACH,qBAAa,uBAAwB,YAAW,qBAAqB;
|
|
1
|
+
{"version":3,"file":"platform-matter.d.ts","sourceRoot":"","sources":["../src/platform-matter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,GAAG,EACH,qBAAqB,EACrB,OAAO,EAEP,yBAAyB,EAC1B,MAAM,YAAY,CAAA;AACnB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AAExD,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAA;AA8B5D;;;;;GAKG;AACH,qBAAa,uBAAwB,YAAW,qBAAqB;aA8BjD,GAAG,EAAE,OAAO;aACZ,MAAM,EAAE,uBAAuB;aAC/B,GAAG,EAAE,GAAG;IA1B1B,SAAgB,iBAAiB,EAAE,GAAG,CAAC,MAAM,EAAE,yBAAyB,CAAC,CAAY;IAErF,OAAO,CAAC,YAAY,CAAC,CAAkB;IACvC,OAAO,CAAC,YAAY,CAAC,CAAc;IAEnC,OAAO,CAAC,iBAAiB,CAAe;IAExC,OAAO,CAAC,eAAe,CAA8C;IAErE,OAAO,CAAC,eAAe,CAAC,CAAS;IAGjC,OAAO,EAAG,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAA;IAClC,UAAU,EAAG,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAA;IACrC,eAAe,EAAG,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAA;IAC1C,OAAO,EAAG,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAA;IAClC,YAAY,EAAG,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAA;IACvC,QAAQ,EAAG,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAA;IACnC,aAAa,EAAG,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAA;IACxC,QAAQ,EAAG,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAA;IACnC,cAAc,EAAG,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;IACvC,uBAAuB,EAAG,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;gBAG9B,GAAG,EAAE,OAAO,EACZ,MAAM,EAAE,uBAAuB,EAC/B,GAAG,EAAE,GAAG;IA0G1B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAIzB;;;OAGG;IACH,OAAO,CAAC,eAAe;IAOvB;;;OAGG;YACW,sBAAsB;IAsCpC;;;OAGG;IACH,OAAO,CAAC,oBAAoB;IAW5B;;;OAGG;YACW,yBAAyB;IA8VvC;;OAEG;YACW,eAAe;IAwB7B;;OAEG;IACG,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,SAAI,EAAE,mBAAmB,SAAO,GAAG,OAAO,CAAC;QAAE,QAAQ,EAAE,GAAG,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC;IA0BzJ;;;OAGG;IACH,OAAO,CAAC,2BAA2B;IAwDnC;;;OAGG;IACH,kBAAkB;IAMlB;;;;;OAKG;IACH,wBAAwB,CAAC,SAAS,EAAE,yBAAyB;IAK7D;;OAEG;YACW,yBAAyB;IAoDvC;;OAEG;YACW,yBAAyB;IAqCvC;;OAEG;YACW,wBAAwB;IA8CtC;;OAEG;YACW,0BAA0B;IAsBxC;;OAEG;YACW,wBAAwB;IAsBtC;;OAEG;YACW,uBAAuB;IA0DrC;;OAEG;YACW,uBAAuB;IAkCrC;;OAEG;YACW,oBAAoB;IA4BlC;;;;;OAKG;YACW,wBAAwB;IAsBtC;;;;;;OAMG;YACW,qBAAqB;CAqBpC"}
|
package/dist/platform-matter.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { SwitchBotBLE, SwitchBotOpenAPI } from 'node-switchbot';
|
|
2
2
|
import { ColorLightAccessory, ColorTemperatureLightAccessory, ContactSensorAccessory, DimmableLightAccessory, DoorLockAccessory, ExtendedColorLightAccessory, FanAccessory, HumiditySensorAccessory, LeakSensorAccessory, LightSensorAccessory, OccupancySensorAccessory, OnOffLightAccessory, OnOffOutletAccessory, OnOffSwitchAccessory, PowerStripAccessory, RoboticVacuumAccessory, SmokeCOAlarmAccessory, TemperatureSensorAccessory, ThermostatAccessory, VenetianBlindAccessory, WindowBlindAccessory, } from './devices-matter/index.js';
|
|
3
3
|
import { PLATFORM_NAME, PLUGIN_NAME } from './settings.js';
|
|
4
|
-
import { cleanDeviceConfig, formatDeviceIdAsMac, hs2rgb, rgb2hs, sleep } from './utils.js';
|
|
4
|
+
import { cleanDeviceConfig, createPlatformLogger, formatDeviceIdAsMac, hs2rgb, makeBLESender, makeOpenAPISender, rgb2hs, sleep } from './utils.js';
|
|
5
5
|
/**
|
|
6
6
|
* MatterPlatform
|
|
7
7
|
* Demonstrates all available Matter device types in Homebridge
|
|
@@ -24,11 +24,37 @@ export class SwitchBotMatterPlatform {
|
|
|
24
24
|
discoveredDevices = [];
|
|
25
25
|
// BLE event handlers keyed by device MAC (formatted)
|
|
26
26
|
bleEventHandler = {};
|
|
27
|
+
// Platform logging toggle (can be controlled via UI or config)
|
|
28
|
+
platformLogging;
|
|
29
|
+
// Platform-provided logging helpers (attached in constructor)
|
|
30
|
+
infoLog;
|
|
31
|
+
successLog;
|
|
32
|
+
debugSuccessLog;
|
|
33
|
+
warnLog;
|
|
34
|
+
debugWarnLog;
|
|
35
|
+
errorLog;
|
|
36
|
+
debugErrorLog;
|
|
37
|
+
debugLog;
|
|
38
|
+
loggingIsDebug;
|
|
39
|
+
enablingPlatformLogging;
|
|
27
40
|
constructor(log, config, api) {
|
|
28
41
|
this.log = log;
|
|
29
42
|
this.config = config;
|
|
30
43
|
this.api = api;
|
|
31
|
-
|
|
44
|
+
// Attach platform-wide logging helpers from utils so Matter and device
|
|
45
|
+
// classes can use consistent logging methods (infoLog/debugLog/etc.)
|
|
46
|
+
const _pl = createPlatformLogger(async () => this.platformLogging, this.log);
|
|
47
|
+
this.infoLog = _pl.infoLog;
|
|
48
|
+
this.successLog = _pl.successLog;
|
|
49
|
+
this.debugSuccessLog = _pl.debugSuccessLog;
|
|
50
|
+
this.warnLog = _pl.warnLog;
|
|
51
|
+
this.debugWarnLog = _pl.debugWarnLog;
|
|
52
|
+
this.errorLog = _pl.errorLog;
|
|
53
|
+
this.debugErrorLog = _pl.debugErrorLog;
|
|
54
|
+
this.debugLog = _pl.debugLog;
|
|
55
|
+
this.loggingIsDebug = _pl.loggingIsDebug;
|
|
56
|
+
this.enablingPlatformLogging = _pl.enablingPlatformLogging;
|
|
57
|
+
this.debugLog('Finished initializing platform:', this.config.name);
|
|
32
58
|
// Normalize deviceConfig to remove UI-inserted defaults
|
|
33
59
|
try {
|
|
34
60
|
if (this.config.options) {
|
|
@@ -43,11 +69,11 @@ export class SwitchBotMatterPlatform {
|
|
|
43
69
|
}
|
|
44
70
|
}
|
|
45
71
|
catch (e) {
|
|
46
|
-
this.
|
|
72
|
+
this.debugLog('Failed to clean deviceConfig: %s', e);
|
|
47
73
|
}
|
|
48
74
|
// Does the user have a version of Homebridge that is compatible with matter?
|
|
49
75
|
if (!this.api.isMatterAvailable?.()) {
|
|
50
|
-
this.
|
|
76
|
+
this.warnLog('Matter is not available in this version of Homebridge. Please update Homebridge to use this plugin.');
|
|
51
77
|
}
|
|
52
78
|
// Check if the user has matter enabled, this means:
|
|
53
79
|
// - If the plugin is running on the main bridge, then the user must have enabled matter in the Homebridge settings page in the UI
|
|
@@ -55,36 +81,36 @@ export class SwitchBotMatterPlatform {
|
|
|
55
81
|
// In reality, only the below check is needed, but they are both included here for completeness
|
|
56
82
|
// Remember to use a '?.' optional chaining operator in case the user is running an older version of Homebridge that does not have these APIs
|
|
57
83
|
if (!this.api.isMatterEnabled?.()) {
|
|
58
|
-
this.
|
|
84
|
+
this.warnLog('Matter is not enabled in Homebridge. Please enable Matter in the Homebridge settings to use this plugin.');
|
|
59
85
|
return;
|
|
60
86
|
}
|
|
61
87
|
// Register Matter accessories when Homebridge has finished launching
|
|
62
88
|
this.api.on('didFinishLaunching', () => {
|
|
63
|
-
this.
|
|
89
|
+
this.debugLog('Executed didFinishLaunching callback');
|
|
64
90
|
// Initialize SwitchBot API clients
|
|
65
91
|
try {
|
|
66
92
|
if (this.config.credentials?.token && this.config.credentials?.secret) {
|
|
67
93
|
this.switchBotAPI = new SwitchBotOpenAPI(this.config.credentials.token, this.config.credentials.secret, this.config.options?.hostname);
|
|
68
94
|
// forward basic logs
|
|
69
95
|
if (!this.config.options?.disableLogsforOpenAPI && this.switchBotAPI?.on) {
|
|
70
|
-
this.switchBotAPI.on('log', (l) => this.
|
|
96
|
+
this.switchBotAPI.on('log', (l) => this.debugLog('[SwitchBot OpenAPI]', l.message));
|
|
71
97
|
}
|
|
72
98
|
}
|
|
73
99
|
else {
|
|
74
|
-
this.
|
|
100
|
+
this.debugLog('SwitchBot OpenAPI credentials not provided; cloud devices will be skipped');
|
|
75
101
|
}
|
|
76
102
|
}
|
|
77
103
|
catch (e) {
|
|
78
|
-
this.
|
|
104
|
+
this.errorLog('Failed to initialize SwitchBot OpenAPI:', e?.message ?? e);
|
|
79
105
|
}
|
|
80
106
|
try {
|
|
81
107
|
this.switchBotBLE = new SwitchBotBLE();
|
|
82
108
|
if (!this.config.options?.disableLogsforBLE && this.switchBotBLE?.on) {
|
|
83
|
-
this.switchBotBLE.on('log', (l) => this.
|
|
109
|
+
this.switchBotBLE.on('log', (l) => this.debugLog('[SwitchBot BLE]', l.message));
|
|
84
110
|
}
|
|
85
111
|
}
|
|
86
112
|
catch (e) {
|
|
87
|
-
this.
|
|
113
|
+
this.errorLog('Failed to initialize SwitchBot BLE client:', e?.message ?? e);
|
|
88
114
|
}
|
|
89
115
|
// If BLE scanning is enabled, start scanning and route advertisements to registered handlers
|
|
90
116
|
if (this.config.options?.BLE && this.switchBotBLE) {
|
|
@@ -94,7 +120,7 @@ export class SwitchBotMatterPlatform {
|
|
|
94
120
|
await ble.startScan();
|
|
95
121
|
}
|
|
96
122
|
catch (e) {
|
|
97
|
-
this.
|
|
123
|
+
this.errorLog(`Failed to start BLE scanning: ${e?.message ?? e}`);
|
|
98
124
|
}
|
|
99
125
|
// route advertisements to our handlers
|
|
100
126
|
ble.onadvertisement = async (ad) => {
|
|
@@ -106,7 +132,7 @@ export class SwitchBotMatterPlatform {
|
|
|
106
132
|
}
|
|
107
133
|
}
|
|
108
134
|
catch (e) {
|
|
109
|
-
this.
|
|
135
|
+
this.errorLog(`Failed to handle BLE advertisement: ${e?.message ?? e}`);
|
|
110
136
|
}
|
|
111
137
|
};
|
|
112
138
|
})();
|
|
@@ -145,7 +171,7 @@ export class SwitchBotMatterPlatform {
|
|
|
145
171
|
const devicesWithTypeConfig = await Promise.all(discovered.map(async (deviceObj) => {
|
|
146
172
|
if (!deviceObj.deviceType) {
|
|
147
173
|
deviceObj.deviceType = deviceObj.configDeviceType !== undefined ? deviceObj.configDeviceType : 'Unknown';
|
|
148
|
-
this.
|
|
174
|
+
this.debugLog(`API missing deviceType for ${deviceObj.deviceId}, using configDeviceType: ${deviceObj.configDeviceType}`);
|
|
149
175
|
}
|
|
150
176
|
const deviceTypeConfig = this.config.options?.deviceConfig?.[deviceObj.deviceType] || {};
|
|
151
177
|
return Object.assign({}, deviceObj, deviceTypeConfig);
|
|
@@ -210,45 +236,9 @@ export class SwitchBotMatterPlatform {
|
|
|
210
236
|
deviceId: dev.deviceId,
|
|
211
237
|
},
|
|
212
238
|
};
|
|
213
|
-
//
|
|
214
|
-
|
|
215
|
-
const
|
|
216
|
-
const bodyChange = { command, parameter, commandType: 'command' };
|
|
217
|
-
return this.retryCommand(dev, bodyChange, this.config.options?.maxRetries ?? 1, this.config.options?.delayBetweenRetries ?? 1000);
|
|
218
|
-
};
|
|
219
|
-
// Helper to send a BLE action if Platform BLE is enabled and switchBotBLE exists
|
|
220
|
-
const sendBLE = async (methodName, ...args) => {
|
|
221
|
-
// Provide a small retry loop for flaky BLE operations
|
|
222
|
-
if (!this.switchBotBLE) {
|
|
223
|
-
throw new Error('Platform BLE not available');
|
|
224
|
-
}
|
|
225
|
-
const id = formatDeviceIdAsMac(dev.deviceId);
|
|
226
|
-
const maxRetries = this.config.options?.bleRetries ?? 2;
|
|
227
|
-
const retryDelay = this.config.options?.bleRetryDelay ?? 500;
|
|
228
|
-
let attempt = 0;
|
|
229
|
-
while (attempt < maxRetries) {
|
|
230
|
-
try {
|
|
231
|
-
const list = await this.switchBotBLE.discover({ model: dev.bleModel, id });
|
|
232
|
-
if (!Array.isArray(list) || list.length === 0) {
|
|
233
|
-
throw new Error('BLE device not found');
|
|
234
|
-
}
|
|
235
|
-
const deviceInst = list[0];
|
|
236
|
-
if (typeof deviceInst[methodName] !== 'function') {
|
|
237
|
-
throw new TypeError(`BLE method ${methodName} not available on device`);
|
|
238
|
-
}
|
|
239
|
-
return await deviceInst[methodName](...args);
|
|
240
|
-
}
|
|
241
|
-
catch (e) {
|
|
242
|
-
attempt++;
|
|
243
|
-
if (attempt >= maxRetries) {
|
|
244
|
-
throw e;
|
|
245
|
-
}
|
|
246
|
-
this.log.debug(`BLE ${methodName} attempt ${attempt} failed for ${dev.deviceId}: ${e?.message ?? e}, retrying in ${retryDelay}ms`);
|
|
247
|
-
await sleep(retryDelay);
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
throw new Error('BLE operation failed');
|
|
251
|
-
};
|
|
239
|
+
// Build platform-side helpers using shared factories so they can be reused/tested
|
|
240
|
+
const sendOpenAPI = makeOpenAPISender(this.retryCommand.bind(this), dev, { maxRetries: this.config.options?.maxRetries ?? 1, delayBetweenRetries: this.config.options?.delayBetweenRetries ?? 1000 });
|
|
241
|
+
const sendBLE = makeBLESender(this.switchBotBLE, dev, { bleRetries: this.config.options?.bleRetries ?? 2, bleRetryDelay: this.config.options?.bleRetryDelay ?? 500 });
|
|
252
242
|
const makeOnOffHandlers = (uuid, connectionType) => ({
|
|
253
243
|
onOff: {
|
|
254
244
|
on: async () => {
|
|
@@ -262,7 +252,7 @@ export class SwitchBotMatterPlatform {
|
|
|
262
252
|
await this.api.matter.updateAccessoryState(uuid, this.api.matter.clusterNames.OnOff, { onOff: true });
|
|
263
253
|
}
|
|
264
254
|
catch (e) {
|
|
265
|
-
this.
|
|
255
|
+
this.errorLog(`Failed to turn on device ${dev.deviceId}: ${e?.message ?? e}`);
|
|
266
256
|
}
|
|
267
257
|
},
|
|
268
258
|
off: async () => {
|
|
@@ -276,7 +266,7 @@ export class SwitchBotMatterPlatform {
|
|
|
276
266
|
await this.api.matter.updateAccessoryState(uuid, this.api.matter.clusterNames.OnOff, { onOff: false });
|
|
277
267
|
}
|
|
278
268
|
catch (e) {
|
|
279
|
-
this.
|
|
269
|
+
this.errorLog(`Failed to turn off device ${dev.deviceId}: ${e?.message ?? e}`);
|
|
280
270
|
}
|
|
281
271
|
},
|
|
282
272
|
},
|
|
@@ -354,7 +344,7 @@ export class SwitchBotMatterPlatform {
|
|
|
354
344
|
};
|
|
355
345
|
const Ctor = mapping[dev.deviceType ?? ''];
|
|
356
346
|
if (!Ctor) {
|
|
357
|
-
this.
|
|
347
|
+
this.debugLog(`No Matter mapping for deviceType='${dev.deviceType}', deviceId=${dev.deviceId}`);
|
|
358
348
|
return undefined;
|
|
359
349
|
}
|
|
360
350
|
// Build opts and handlers tailored for basic capabilities
|
|
@@ -381,7 +371,7 @@ export class SwitchBotMatterPlatform {
|
|
|
381
371
|
await this.api.matter.updateAccessoryState(uuid, this.api.matter.clusterNames.LevelControl, { currentLevel: level });
|
|
382
372
|
}
|
|
383
373
|
catch (e) {
|
|
384
|
-
this.
|
|
374
|
+
this.errorLog(`Failed to set brightness for ${dev.deviceId}: ${e?.message ?? e}`);
|
|
385
375
|
}
|
|
386
376
|
},
|
|
387
377
|
};
|
|
@@ -401,7 +391,7 @@ export class SwitchBotMatterPlatform {
|
|
|
401
391
|
await this.api.matter.updateAccessoryState(uuid, this.api.matter.clusterNames.ColorControl, { currentHue: hue, currentSaturation: saturation });
|
|
402
392
|
}
|
|
403
393
|
catch (e) {
|
|
404
|
-
this.
|
|
394
|
+
this.errorLog(`Failed to set hue/sat for ${dev.deviceId}: ${e?.message ?? e}`);
|
|
405
395
|
}
|
|
406
396
|
},
|
|
407
397
|
moveToColorLogic: async (request) => {
|
|
@@ -422,7 +412,7 @@ export class SwitchBotMatterPlatform {
|
|
|
422
412
|
await this.api.matter.updateAccessoryState(uuid, this.api.matter.clusterNames.ColorControl, { currentX: colorX, currentY: colorY });
|
|
423
413
|
}
|
|
424
414
|
catch (e) {
|
|
425
|
-
this.
|
|
415
|
+
this.errorLog(`Failed to set XY color for ${dev.deviceId}: ${e?.message ?? e}`);
|
|
426
416
|
}
|
|
427
417
|
},
|
|
428
418
|
};
|
|
@@ -440,7 +430,7 @@ export class SwitchBotMatterPlatform {
|
|
|
440
430
|
await this.api.matter.updateAccessoryState(uuid, this.api.matter.clusterNames.ColorControl, { currentX: request.colorX ?? 0, currentY: request.colorY ?? 0 });
|
|
441
431
|
}
|
|
442
432
|
catch (e) {
|
|
443
|
-
this.
|
|
433
|
+
this.errorLog(`Failed to set color temperature for ${dev.deviceId}: ${e?.message ?? e}`);
|
|
444
434
|
}
|
|
445
435
|
},
|
|
446
436
|
};
|
|
@@ -449,11 +439,24 @@ export class SwitchBotMatterPlatform {
|
|
|
449
439
|
// classes can call OpenAPI/BLE actions (sendOpenAPI/sendBLE) and know
|
|
450
440
|
// the effective connection type.
|
|
451
441
|
try {
|
|
442
|
+
/* Inject platform helpers (OpenAPI/BLE senders + logging helpers + connection type)
|
|
443
|
+
into the accessory context so Matter accessory classes can use them without
|
|
444
|
+
reaching into the platform implementation directly. */
|
|
452
445
|
;
|
|
453
|
-
baseOpts.context = Object.assign({}, baseOpts.context, {
|
|
446
|
+
baseOpts.context = Object.assign({}, baseOpts.context, {
|
|
447
|
+
sendOpenAPI,
|
|
448
|
+
sendBLE,
|
|
449
|
+
connectionType,
|
|
450
|
+
// Expose platform logging helpers so accessories can use consistent logging
|
|
451
|
+
infoLog: this.infoLog,
|
|
452
|
+
debugLog: this.debugLog,
|
|
453
|
+
warnLog: this.warnLog,
|
|
454
|
+
errorLog: this.errorLog,
|
|
455
|
+
successLog: this.successLog,
|
|
456
|
+
});
|
|
454
457
|
}
|
|
455
458
|
catch (e) {
|
|
456
|
-
this.
|
|
459
|
+
this.debugLog('Failed to attach platform helpers to baseOpts.context: %s', e?.message ?? e);
|
|
457
460
|
}
|
|
458
461
|
const opts = Object.assign({}, baseOpts, { handlers });
|
|
459
462
|
// Instantiate the device class and return its serialized accessory
|
|
@@ -490,7 +493,7 @@ export class SwitchBotMatterPlatform {
|
|
|
490
493
|
}
|
|
491
494
|
}
|
|
492
495
|
catch (e) {
|
|
493
|
-
this.
|
|
496
|
+
this.debugLog(`BLE advertisement parsing failed for ${dev.deviceId}: ${e?.message ?? e}`);
|
|
494
497
|
}
|
|
495
498
|
// Fallback to OpenAPI getDeviceStatus when serviceData is not present or parsing failed
|
|
496
499
|
if (!this.switchBotAPI) {
|
|
@@ -535,12 +538,12 @@ export class SwitchBotMatterPlatform {
|
|
|
535
538
|
}
|
|
536
539
|
}
|
|
537
540
|
catch (e) {
|
|
538
|
-
this.
|
|
541
|
+
this.debugLog(`BLE push handler failed for ${dev.deviceId}: ${e?.message ?? e}`);
|
|
539
542
|
}
|
|
540
543
|
};
|
|
541
544
|
}
|
|
542
545
|
catch (e) {
|
|
543
|
-
this.
|
|
546
|
+
this.debugLog(`Failed to register BLE handler for ${dev.deviceId}: ${e?.message ?? e}`);
|
|
544
547
|
}
|
|
545
548
|
return instance.toAccessory();
|
|
546
549
|
}
|
|
@@ -549,26 +552,26 @@ export class SwitchBotMatterPlatform {
|
|
|
549
552
|
*/
|
|
550
553
|
async discoverDevices() {
|
|
551
554
|
if (!this.switchBotAPI) {
|
|
552
|
-
this.
|
|
555
|
+
this.debugLog('SwitchBot OpenAPI not configured; skipping discovery');
|
|
553
556
|
return;
|
|
554
557
|
}
|
|
555
558
|
try {
|
|
556
559
|
const { response, statusCode } = await this.switchBotAPI.getDevices();
|
|
557
|
-
this.
|
|
560
|
+
this.debugLog(`SwitchBot getDevices response status: ${statusCode}`);
|
|
558
561
|
if (statusCode === 100 || statusCode === 200) {
|
|
559
562
|
const deviceList = Array.isArray(response?.body?.deviceList) ? response.body.deviceList : [];
|
|
560
563
|
this.discoveredDevices = deviceList;
|
|
561
|
-
this.
|
|
564
|
+
this.infoLog(`Discovered ${deviceList.length} SwitchBot device(s) from OpenAPI`);
|
|
562
565
|
for (const d of deviceList) {
|
|
563
|
-
this.
|
|
566
|
+
this.debugLog(` - ${d.deviceName} (${d.deviceType}) id=${d.deviceId}`);
|
|
564
567
|
}
|
|
565
568
|
}
|
|
566
569
|
else {
|
|
567
|
-
this.
|
|
570
|
+
this.warnLog(`SwitchBot getDevices returned status ${statusCode}`);
|
|
568
571
|
}
|
|
569
572
|
}
|
|
570
573
|
catch (e) {
|
|
571
|
-
this.
|
|
574
|
+
this.errorLog('Failed to discover SwitchBot devices:', e?.message ?? e);
|
|
572
575
|
}
|
|
573
576
|
}
|
|
574
577
|
/**
|
|
@@ -585,7 +588,7 @@ export class SwitchBotMatterPlatform {
|
|
|
585
588
|
return { response, statusCode };
|
|
586
589
|
}
|
|
587
590
|
catch (e) {
|
|
588
|
-
this.
|
|
591
|
+
this.debugLog(`retryCommand error: ${e?.message ?? e}`);
|
|
589
592
|
}
|
|
590
593
|
retryCount++;
|
|
591
594
|
await sleep(delayBetweenRetries);
|
|
@@ -645,7 +648,7 @@ export class SwitchBotMatterPlatform {
|
|
|
645
648
|
return result;
|
|
646
649
|
}
|
|
647
650
|
catch (e) {
|
|
648
|
-
this.
|
|
651
|
+
this.debugLog(`parseAdvertisementForDevice failed for ${dev.deviceId}: ${e?.message ?? e}`);
|
|
649
652
|
return null;
|
|
650
653
|
}
|
|
651
654
|
}
|
|
@@ -665,21 +668,21 @@ export class SwitchBotMatterPlatform {
|
|
|
665
668
|
* any custom data you stored when the accessory was originally registered.
|
|
666
669
|
*/
|
|
667
670
|
configureMatterAccessory(accessory) {
|
|
668
|
-
this.
|
|
671
|
+
this.debugLog('Loading cached Matter accessory:', accessory.displayName);
|
|
669
672
|
this.matterAccessories.set(accessory.uuid, accessory);
|
|
670
673
|
}
|
|
671
674
|
/**
|
|
672
675
|
* Register all Matter accessories
|
|
673
676
|
*/
|
|
674
677
|
async registerMatterAccessories() {
|
|
675
|
-
this.
|
|
676
|
-
this.
|
|
677
|
-
this.
|
|
678
|
+
this.debugLog('═'.repeat(80));
|
|
679
|
+
this.infoLog('Homebridge Matter Plugin');
|
|
680
|
+
this.debugLog('═'.repeat(80));
|
|
678
681
|
// Remove accessories that are disabled in config
|
|
679
682
|
await this.removeDisabledAccessories();
|
|
680
683
|
// If we discovered real SwitchBot devices via OpenAPI, map and register them
|
|
681
684
|
if (this.discoveredDevices && this.discoveredDevices.length > 0) {
|
|
682
|
-
this.
|
|
685
|
+
this.infoLog(`Registering ${this.discoveredDevices.length} discovered SwitchBot device(s) as Matter accessories`);
|
|
683
686
|
const accessories = [];
|
|
684
687
|
// Merge device config (deviceConfig per deviceType and per-device overrides) to match HAP behavior
|
|
685
688
|
const devicesToProcess = await this.mergeDiscoveredDevices(this.discoveredDevices);
|
|
@@ -691,18 +694,18 @@ export class SwitchBotMatterPlatform {
|
|
|
691
694
|
}
|
|
692
695
|
}
|
|
693
696
|
catch (e) {
|
|
694
|
-
this.
|
|
697
|
+
this.errorLog(`Failed to create Matter accessory for ${dev.deviceId}: ${e?.message ?? e}`);
|
|
695
698
|
}
|
|
696
699
|
}
|
|
697
700
|
if (accessories.length > 0) {
|
|
698
|
-
this.
|
|
701
|
+
this.infoLog(`✓ Registered ${accessories.length} discovered device(s)`);
|
|
699
702
|
for (const acc of accessories) {
|
|
700
|
-
this.
|
|
703
|
+
this.infoLog(` - ${acc.displayName}`);
|
|
701
704
|
}
|
|
702
705
|
await this.api.matter.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, accessories);
|
|
703
706
|
return;
|
|
704
707
|
}
|
|
705
|
-
this.
|
|
708
|
+
this.infoLog('No discovered devices were mapped to Matter accessories; falling back to example sections');
|
|
706
709
|
}
|
|
707
710
|
// Register example/demo devices by Matter specification sections
|
|
708
711
|
await this.registerSection4Lighting();
|
|
@@ -713,9 +716,9 @@ export class SwitchBotMatterPlatform {
|
|
|
713
716
|
await this.registerSection9HVAC();
|
|
714
717
|
await this.registerSection12Robotic();
|
|
715
718
|
await this.registerCustomDevices();
|
|
716
|
-
this.
|
|
717
|
-
this.
|
|
718
|
-
this.
|
|
719
|
+
this.debugLog('═'.repeat(80));
|
|
720
|
+
this.debugLog('Finished registering Matter accessories');
|
|
721
|
+
this.debugLog('═'.repeat(80));
|
|
719
722
|
}
|
|
720
723
|
/**
|
|
721
724
|
* Remove accessories that are disabled in config
|
|
@@ -748,7 +751,7 @@ export class SwitchBotMatterPlatform {
|
|
|
748
751
|
if (enabled === false) {
|
|
749
752
|
const existingAccessory = this.matterAccessories.get(uuid);
|
|
750
753
|
if (existingAccessory) {
|
|
751
|
-
this.
|
|
754
|
+
this.infoLog(`Removing accessory '${name}' (disabled in config)`);
|
|
752
755
|
await this.api.matter.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [existingAccessory]);
|
|
753
756
|
this.matterAccessories.delete(uuid);
|
|
754
757
|
}
|
|
@@ -759,9 +762,9 @@ export class SwitchBotMatterPlatform {
|
|
|
759
762
|
* Section 4: Lighting Devices (Matter Spec § 4)
|
|
760
763
|
*/
|
|
761
764
|
async registerSection4Lighting() {
|
|
762
|
-
this.
|
|
763
|
-
this.
|
|
764
|
-
this.
|
|
765
|
+
this.debugLog('═'.repeat(80));
|
|
766
|
+
this.infoLog('Section 4: Lighting Devices (Matter Spec § 4)');
|
|
767
|
+
this.debugLog('═'.repeat(80));
|
|
765
768
|
const accessories = [];
|
|
766
769
|
// On/Off Light
|
|
767
770
|
if (this.config.enableOnOffLight !== false) {
|
|
@@ -789,9 +792,9 @@ export class SwitchBotMatterPlatform {
|
|
|
789
792
|
accessories.push(device.toAccessory());
|
|
790
793
|
}
|
|
791
794
|
if (accessories.length > 0) {
|
|
792
|
-
this.
|
|
795
|
+
this.infoLog(`✓ Registered ${accessories.length} lighting device(s)`);
|
|
793
796
|
for (const acc of accessories) {
|
|
794
|
-
this.
|
|
797
|
+
this.infoLog(` - ${acc.displayName}`);
|
|
795
798
|
}
|
|
796
799
|
await this.api.matter.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, accessories);
|
|
797
800
|
}
|
|
@@ -800,9 +803,9 @@ export class SwitchBotMatterPlatform {
|
|
|
800
803
|
* Section 5: Smart Plugs/Actuators (Matter Spec § 5)
|
|
801
804
|
*/
|
|
802
805
|
async registerSection5SmartPlugs() {
|
|
803
|
-
this.
|
|
804
|
-
this.
|
|
805
|
-
this.
|
|
806
|
+
this.debugLog('═'.repeat(80));
|
|
807
|
+
this.infoLog('Section 5: Smart Plugs/Actuators (Matter Spec § 5)');
|
|
808
|
+
this.debugLog('═'.repeat(80));
|
|
806
809
|
const accessories = [];
|
|
807
810
|
// On/Off Outlet
|
|
808
811
|
if (this.config.enableOnOffOutlet !== false) {
|
|
@@ -810,9 +813,9 @@ export class SwitchBotMatterPlatform {
|
|
|
810
813
|
accessories.push(device.toAccessory());
|
|
811
814
|
}
|
|
812
815
|
if (accessories.length > 0) {
|
|
813
|
-
this.
|
|
816
|
+
this.infoLog(`✓ Registered ${accessories.length} smart plug/actuator device(s)`);
|
|
814
817
|
for (const acc of accessories) {
|
|
815
|
-
this.
|
|
818
|
+
this.infoLog(` - ${acc.displayName}`);
|
|
816
819
|
}
|
|
817
820
|
await this.api.matter.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, accessories);
|
|
818
821
|
}
|
|
@@ -821,9 +824,9 @@ export class SwitchBotMatterPlatform {
|
|
|
821
824
|
* Section 6: Switches & Controllers (Matter Spec § 6)
|
|
822
825
|
*/
|
|
823
826
|
async registerSection6Switches() {
|
|
824
|
-
this.
|
|
825
|
-
this.
|
|
826
|
-
this.
|
|
827
|
+
this.debugLog('═'.repeat(80));
|
|
828
|
+
this.infoLog('Section 6: Switches & Controllers (Matter Spec § 6)');
|
|
829
|
+
this.debugLog('═'.repeat(80));
|
|
827
830
|
const accessories = [];
|
|
828
831
|
// On/Off Switch
|
|
829
832
|
if (this.config.enableOnOffSwitch !== false) {
|
|
@@ -831,9 +834,9 @@ export class SwitchBotMatterPlatform {
|
|
|
831
834
|
accessories.push(device.toAccessory());
|
|
832
835
|
}
|
|
833
836
|
if (accessories.length > 0) {
|
|
834
|
-
this.
|
|
837
|
+
this.infoLog(`✓ Registered ${accessories.length} switch/controller device(s)`);
|
|
835
838
|
for (const acc of accessories) {
|
|
836
|
-
this.
|
|
839
|
+
this.infoLog(` - ${acc.displayName}`);
|
|
837
840
|
}
|
|
838
841
|
await this.api.matter.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, accessories);
|
|
839
842
|
}
|
|
@@ -842,9 +845,9 @@ export class SwitchBotMatterPlatform {
|
|
|
842
845
|
* Section 7: Sensors (Matter Spec § 7)
|
|
843
846
|
*/
|
|
844
847
|
async registerSection7Sensors() {
|
|
845
|
-
this.
|
|
846
|
-
this.
|
|
847
|
-
this.
|
|
848
|
+
this.debugLog('═'.repeat(80));
|
|
849
|
+
this.infoLog('Section 7: Sensors (Matter Spec § 7)');
|
|
850
|
+
this.debugLog('═'.repeat(80));
|
|
848
851
|
const accessories = [];
|
|
849
852
|
// Contact Sensor
|
|
850
853
|
if (this.config.enableContactSensor !== false) {
|
|
@@ -882,9 +885,9 @@ export class SwitchBotMatterPlatform {
|
|
|
882
885
|
accessories.push(device.toAccessory());
|
|
883
886
|
}
|
|
884
887
|
if (accessories.length > 0) {
|
|
885
|
-
this.
|
|
888
|
+
this.infoLog(`✓ Registered ${accessories.length} sensor device(s)`);
|
|
886
889
|
for (const acc of accessories) {
|
|
887
|
-
this.
|
|
890
|
+
this.infoLog(` - ${acc.displayName}`);
|
|
888
891
|
}
|
|
889
892
|
await this.api.matter.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, accessories);
|
|
890
893
|
}
|
|
@@ -893,9 +896,9 @@ export class SwitchBotMatterPlatform {
|
|
|
893
896
|
* Section 8: Closure Devices (Matter Spec § 8)
|
|
894
897
|
*/
|
|
895
898
|
async registerSection8Closure() {
|
|
896
|
-
this.
|
|
897
|
-
this.
|
|
898
|
-
this.
|
|
899
|
+
this.debugLog('═'.repeat(80));
|
|
900
|
+
this.infoLog('Section 8: Closure Devices (Matter Spec § 8)');
|
|
901
|
+
this.debugLog('═'.repeat(80));
|
|
899
902
|
const accessories = [];
|
|
900
903
|
// Door Lock
|
|
901
904
|
if (this.config.enableDoorLock !== false) {
|
|
@@ -913,9 +916,9 @@ export class SwitchBotMatterPlatform {
|
|
|
913
916
|
accessories.push(device.toAccessory());
|
|
914
917
|
}
|
|
915
918
|
if (accessories.length > 0) {
|
|
916
|
-
this.
|
|
919
|
+
this.infoLog(`✓ Registered ${accessories.length} closure device(s)`);
|
|
917
920
|
for (const acc of accessories) {
|
|
918
|
-
this.
|
|
921
|
+
this.infoLog(` - ${acc.displayName}`);
|
|
919
922
|
}
|
|
920
923
|
await this.api.matter.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, accessories);
|
|
921
924
|
}
|
|
@@ -924,9 +927,9 @@ export class SwitchBotMatterPlatform {
|
|
|
924
927
|
* Section 9: HVAC (Matter Spec § 9)
|
|
925
928
|
*/
|
|
926
929
|
async registerSection9HVAC() {
|
|
927
|
-
this.
|
|
928
|
-
this.
|
|
929
|
-
this.
|
|
930
|
+
this.debugLog('═'.repeat(80));
|
|
931
|
+
this.infoLog('Section 9: HVAC (Matter Spec § 9)');
|
|
932
|
+
this.debugLog('═'.repeat(80));
|
|
930
933
|
const accessories = [];
|
|
931
934
|
// Thermostat
|
|
932
935
|
if (this.config.enableThermostat !== false) {
|
|
@@ -939,9 +942,9 @@ export class SwitchBotMatterPlatform {
|
|
|
939
942
|
accessories.push(device.toAccessory());
|
|
940
943
|
}
|
|
941
944
|
if (accessories.length > 0) {
|
|
942
|
-
this.
|
|
945
|
+
this.infoLog(`✓ Registered ${accessories.length} HVAC device(s)`);
|
|
943
946
|
for (const acc of accessories) {
|
|
944
|
-
this.
|
|
947
|
+
this.infoLog(` - ${acc.displayName}`);
|
|
945
948
|
}
|
|
946
949
|
await this.api.matter.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, accessories);
|
|
947
950
|
}
|
|
@@ -953,9 +956,9 @@ export class SwitchBotMatterPlatform {
|
|
|
953
956
|
* Use those codes to pair the vacuum as a separate bridge in your Home app.
|
|
954
957
|
*/
|
|
955
958
|
async registerSection12Robotic() {
|
|
956
|
-
this.
|
|
957
|
-
this.
|
|
958
|
-
this.
|
|
959
|
+
this.debugLog('═'.repeat(80));
|
|
960
|
+
this.infoLog('Section 12: Robotic Devices (Matter Spec § 12)');
|
|
961
|
+
this.debugLog('═'.repeat(80));
|
|
959
962
|
const accessories = [];
|
|
960
963
|
// Robot Vacuum
|
|
961
964
|
if (this.config.enableRobotVacuum !== false) {
|
|
@@ -963,9 +966,9 @@ export class SwitchBotMatterPlatform {
|
|
|
963
966
|
accessories.push(device.toAccessory());
|
|
964
967
|
}
|
|
965
968
|
if (accessories.length > 0) {
|
|
966
|
-
this.
|
|
969
|
+
this.infoLog(`✓ Registered ${accessories.length} robot vacuum device(s)`);
|
|
967
970
|
for (const acc of accessories) {
|
|
968
|
-
this.
|
|
971
|
+
this.infoLog(` - ${acc.displayName} (standalone for Apple Home compatibility)`);
|
|
969
972
|
}
|
|
970
973
|
await this.api.matter.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, accessories);
|
|
971
974
|
}
|
|
@@ -978,9 +981,9 @@ export class SwitchBotMatterPlatform {
|
|
|
978
981
|
* like managing multiple logical components within a single device.
|
|
979
982
|
*/
|
|
980
983
|
async registerCustomDevices() {
|
|
981
|
-
this.
|
|
982
|
-
this.
|
|
983
|
-
this.
|
|
984
|
+
this.debugLog('═'.repeat(80));
|
|
985
|
+
this.infoLog('Custom Devices');
|
|
986
|
+
this.debugLog('═'.repeat(80));
|
|
984
987
|
const accessories = [];
|
|
985
988
|
// Power Strip (4 Outlets)
|
|
986
989
|
if (this.config.enablePowerStrip !== false) {
|
|
@@ -988,9 +991,9 @@ export class SwitchBotMatterPlatform {
|
|
|
988
991
|
accessories.push(device.toAccessory());
|
|
989
992
|
}
|
|
990
993
|
if (accessories.length > 0) {
|
|
991
|
-
this.
|
|
994
|
+
this.infoLog(`✓ Registered ${accessories.length} custom device(s)`);
|
|
992
995
|
for (const acc of accessories) {
|
|
993
|
-
this.
|
|
996
|
+
this.infoLog(` - ${acc.displayName}`);
|
|
994
997
|
}
|
|
995
998
|
await this.api.matter.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, accessories);
|
|
996
999
|
}
|