@switchbot/homebridge-switchbot 5.0.0-beta.20 → 5.0.0-beta.22
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/CHANGELOG.md +6 -0
- package/config.schema.json +9 -0
- package/dist/platform-matter.cleanup.test.d.ts +2 -0
- package/dist/platform-matter.cleanup.test.d.ts.map +1 -0
- package/dist/platform-matter.cleanup.test.js +85 -0
- package/dist/platform-matter.cleanup.test.js.map +1 -0
- package/dist/platform-matter.d.ts.map +1 -1
- package/dist/platform-matter.js +179 -7
- package/dist/platform-matter.js.map +1 -1
- package/dist/platform-matter.mapping.test.d.ts +2 -0
- package/dist/platform-matter.mapping.test.d.ts.map +1 -0
- package/dist/platform-matter.mapping.test.js +50 -0
- package/dist/platform-matter.mapping.test.js.map +1 -0
- package/dist/platform-matter.unregister.test.d.ts +2 -0
- package/dist/platform-matter.unregister.test.d.ts.map +1 -0
- package/dist/platform-matter.unregister.test.js +37 -0
- package/dist/platform-matter.unregister.test.js.map +1 -0
- package/dist/settings.d.ts +6 -0
- package/dist/settings.d.ts.map +1 -1
- package/dist/settings.js.map +1 -1
- package/docs/variables/default.html +1 -1
- package/package.json +1 -1
- package/src/platform-matter.cleanup.test.ts +101 -0
- package/src/platform-matter.mapping.test.ts +60 -0
- package/src/platform-matter.ts +183 -7
- package/src/platform-matter.unregister.test.ts +48 -0
- package/src/settings.ts +6 -0
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,12 @@ All notable changes to this project will be documented in this file. This projec
|
|
|
8
8
|
- Matter platform: register only devices discovered via the SwitchBot OpenAPI by default. Per-device config overrides (by deviceId) are correctly merged into discovered devices.
|
|
9
9
|
- Add `options.allowConfigOnlyDevices` (boolean) to opt-in to registering devices that exist only in config (not discovered).
|
|
10
10
|
- Add unit tests for per-device merging and config-only behavior.
|
|
11
|
+
- Matter (Matter platform): default to discovery-first registration and automatically remove previously-registered "stale" accessories that are no longer discovered or configured.
|
|
12
|
+
- Add `options.keepStaleAccessories` (Advanced Settings) — opt-in to preserve previously-registered accessories when set to true. Default: false (stale accessories are removed automatically).
|
|
13
|
+
- Implement per-device OpenAPI polling with tracked timers and proper lifecycle cleanup (timers and BLE handlers cleared on unregister/shutdown).
|
|
14
|
+
- Centralize OpenAPI/BLE -> Matter mapping and expand BLE/OpenAPI parsing to include PM2.5/PM10/VOC/CO2, temperature/humidity, improved color parsing, and additional sensor/robot-vacuum fields.
|
|
15
|
+
- Prefer accessory-specific update helpers where available; fall back to generic Matter updates otherwise.
|
|
16
|
+
- Add unit tests covering BLE parsing, stale-accessory behavior, mapping helpers, and lifecycle cleanup.
|
|
11
17
|
|
|
12
18
|
## [4.3.1](https://github.com/OpenWonderLabs/homebridge-switchbot/releases/tag/v4.3.1) (2025-03-04)
|
|
13
19
|
|
package/config.schema.json
CHANGED
|
@@ -13082,6 +13082,11 @@
|
|
|
13082
13082
|
"type": "boolean",
|
|
13083
13083
|
"description": "If true, devices declared in options.devices that are not discovered by the SwitchBot OpenAPI will still be included (config-only devices). Default: false."
|
|
13084
13084
|
},
|
|
13085
|
+
"keepStaleAccessories": {
|
|
13086
|
+
"title": "Keep Stale Accessories",
|
|
13087
|
+
"type": "boolean",
|
|
13088
|
+
"description": "If true, accessories that are no longer found via the SwitchBot OpenAPI will be kept in HomeKit. Default: false."
|
|
13089
|
+
},
|
|
13085
13090
|
"allowInvalidCharacters": {
|
|
13086
13091
|
"title": "Allow Invalid Characters",
|
|
13087
13092
|
"type": "boolean",
|
|
@@ -14835,6 +14840,10 @@
|
|
|
14835
14840
|
"key": "options.allowConfigOnlyDevices",
|
|
14836
14841
|
"description": "<em class='primary-text'>If true, devices declared in options.devices that are not discovered by the SwitchBot OpenAPI will still be included (config-only devices). Default: false.</em>"
|
|
14837
14842
|
},
|
|
14843
|
+
{
|
|
14844
|
+
"key": "options.keepStaleAccessories",
|
|
14845
|
+
"description": "<em class='primary-text'>If true, previously-registered accessories for devices that are no longer discovered or configured will be kept on the bridge. Default: false (stale accessories are removed automatically).</em>"
|
|
14846
|
+
},
|
|
14838
14847
|
{
|
|
14839
14848
|
"key": "options.allowInvalidCharacters",
|
|
14840
14849
|
"description": "<em class='primary-text'>Allows device names with characters that are normally invalid in HomeKit. Use with caution, as this may lead to unexpected behavior.</em>"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"platform-matter.cleanup.test.d.ts","sourceRoot":"","sources":["../src/platform-matter.cleanup.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { SwitchBotMatterPlatform } from './platform-matter.js';
|
|
3
|
+
import { formatDeviceIdAsMac } from './utils.js';
|
|
4
|
+
describe('platform-matter lifecycle cleanup', () => {
|
|
5
|
+
it('clearDeviceResources removes timers, instances and BLE handler entries', async () => {
|
|
6
|
+
// Setup stubbed API and logs
|
|
7
|
+
const handlers = {};
|
|
8
|
+
const api = {
|
|
9
|
+
matter: {
|
|
10
|
+
uuid: { generate: (s) => `uuid-${s}` },
|
|
11
|
+
registerPlatformAccessories: vi.fn(),
|
|
12
|
+
unregisterPlatformAccessories: vi.fn(),
|
|
13
|
+
clusterNames: { OnOff: 'OnOff' },
|
|
14
|
+
},
|
|
15
|
+
isMatterAvailable: () => true,
|
|
16
|
+
isMatterEnabled: () => true,
|
|
17
|
+
on: (ev, fn) => { handlers[ev] = fn; },
|
|
18
|
+
_handlers: handlers,
|
|
19
|
+
};
|
|
20
|
+
const log = { info: vi.fn(), debug: vi.fn(), warn: vi.fn(), error: vi.fn(), success: vi.fn() };
|
|
21
|
+
const platform = new SwitchBotMatterPlatform(log, {}, api);
|
|
22
|
+
// Insert a fake timer, accessory instance, and BLE handler
|
|
23
|
+
const deviceId = 'AA:BB:CC:11:22:33';
|
|
24
|
+
const nid = platform.normalizeDeviceId(deviceId);
|
|
25
|
+
const timer = setInterval(() => { }, 100000);
|
|
26
|
+
platform.refreshTimers.set(nid, timer);
|
|
27
|
+
platform.accessoryInstances.set(nid, { dummy: true });
|
|
28
|
+
platform.bleEventHandler[deviceId.toLowerCase()] = () => { };
|
|
29
|
+
// Ensure they exist prior
|
|
30
|
+
expect(platform.refreshTimers.get(nid)).toBeDefined();
|
|
31
|
+
expect(platform.accessoryInstances.get(nid)).toBeDefined();
|
|
32
|
+
expect(platform.bleEventHandler[deviceId.toLowerCase()]).toBeDefined();
|
|
33
|
+
platform.clearDeviceResources(deviceId);
|
|
34
|
+
// Now they should be removed
|
|
35
|
+
expect(platform.refreshTimers.get(nid)).toBeUndefined();
|
|
36
|
+
expect(platform.accessoryInstances.get(nid)).toBeUndefined();
|
|
37
|
+
expect(platform.bleEventHandler[deviceId.toLowerCase()]).toBeUndefined();
|
|
38
|
+
});
|
|
39
|
+
it('shutdown handler clears all timers and handlers when invoked', async () => {
|
|
40
|
+
// Setup stubbed API and logs
|
|
41
|
+
const handlers = {};
|
|
42
|
+
const api = {
|
|
43
|
+
matter: {
|
|
44
|
+
uuid: { generate: (s) => `uuid-${s}` },
|
|
45
|
+
registerPlatformAccessories: vi.fn(),
|
|
46
|
+
unregisterPlatformAccessories: vi.fn(),
|
|
47
|
+
clusterNames: { OnOff: 'OnOff' },
|
|
48
|
+
},
|
|
49
|
+
isMatterAvailable: () => true,
|
|
50
|
+
isMatterEnabled: () => true,
|
|
51
|
+
on: (ev, fn) => { handlers[ev] = fn; },
|
|
52
|
+
_handlers: handlers,
|
|
53
|
+
};
|
|
54
|
+
const log = { info: vi.fn(), debug: vi.fn(), warn: vi.fn(), error: vi.fn(), success: vi.fn() };
|
|
55
|
+
const platform = new SwitchBotMatterPlatform(log, {}, api);
|
|
56
|
+
// Add two timers to the platform (using normalized ids)
|
|
57
|
+
const ids = ['devA', 'devB'];
|
|
58
|
+
for (const id of ids) {
|
|
59
|
+
const nid = platform.normalizeDeviceId(id);
|
|
60
|
+
const t = setInterval(() => { }, 100000);
|
|
61
|
+
platform.refreshTimers.set(nid, t);
|
|
62
|
+
platform.accessoryInstances.set(nid, { dummy: true });
|
|
63
|
+
try {
|
|
64
|
+
const mac = formatDeviceIdAsMac(id).toLowerCase();
|
|
65
|
+
platform.bleEventHandler[mac] = () => { };
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
// ignore formatting errors in this test
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// Invoke didFinishLaunching to ensure the platform registered its shutdown handler
|
|
72
|
+
await Promise.resolve(api._handlers.didFinishLaunching?.());
|
|
73
|
+
// Shutdown handler should now be registered on api._handlers.shutdown
|
|
74
|
+
expect(typeof api._handlers.shutdown).toBe('function');
|
|
75
|
+
// Call the shutdown handler
|
|
76
|
+
await Promise.resolve(api._handlers.shutdown());
|
|
77
|
+
// All refresh timers should be cleared
|
|
78
|
+
for (const id of ids) {
|
|
79
|
+
const nid = platform.normalizeDeviceId(id);
|
|
80
|
+
expect(platform.refreshTimers.get(nid)).toBeUndefined();
|
|
81
|
+
expect(platform.accessoryInstances.get(nid)).toBeUndefined();
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
//# sourceMappingURL=platform-matter.cleanup.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"platform-matter.cleanup.test.js","sourceRoot":"","sources":["../src/platform-matter.cleanup.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -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,EAAiB,uBAAuB,EAAE,MAAM,eAAe,CAAA;AA8B3E,qBAAa,uBAAwB,YAAW,qBAAqB;aAkCjD,GAAG,EAAE,OAAO;aACZ,MAAM,EAAE,uBAAuB;aAC/B,GAAG,EAAE,GAAG;IA9B1B,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,kBAAkB,CAA8B;IAExD,OAAO,CAAC,aAAa,CAAyC;IAE9D,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;IAoK1B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAIzB;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAoC5B;;;OAGG;IACH,OAAO,CAAC,eAAe;IAgBvB;;;OAGG;YACW,sBAAsB;IAuCpC;;;OAGG;IACH,OAAO,CAAC,oBAAoB;IAW5B;;;OAGG;YACW,yBAAyB;IA2gBvC;;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;
|
|
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,EAAiB,uBAAuB,EAAE,MAAM,eAAe,CAAA;AA8B3E,qBAAa,uBAAwB,YAAW,qBAAqB;aAkCjD,GAAG,EAAE,OAAO;aACZ,MAAM,EAAE,uBAAuB;aAC/B,GAAG,EAAE,GAAG;IA9B1B,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,kBAAkB,CAA8B;IAExD,OAAO,CAAC,aAAa,CAAyC;IAE9D,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;IAoK1B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAIzB;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAoC5B;;;OAGG;IACH,OAAO,CAAC,eAAe;IAgBvB;;;OAGG;YACW,sBAAsB;IAuCpC;;;OAGG;IACH,OAAO,CAAC,oBAAoB;IAW5B;;;OAGG;YACW,yBAAyB;IA2gBvC;;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;IA6InC;;;;OAIG;YACW,sBAAsB;IA0OpC;;;OAGG;IACH,kBAAkB;IAMlB;;;;;OAKG;IACH,wBAAwB,CAAC,SAAS,EAAE,yBAAyB;IAK7D;;OAEG;YACW,yBAAyB;IAsIvC;;OAEG;YACW,yBAAyB;IA8CvC;;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
|
@@ -909,6 +909,14 @@ export class SwitchBotMatterPlatform {
|
|
|
909
909
|
const parts = c.split(':').map(Number);
|
|
910
910
|
[r, g, b] = parts;
|
|
911
911
|
}
|
|
912
|
+
else if (c.includes(',')) {
|
|
913
|
+
const parts = c.split(',').map(s => Number(s.trim()));
|
|
914
|
+
[r, g, b] = parts;
|
|
915
|
+
}
|
|
916
|
+
else if (c.includes(' ')) {
|
|
917
|
+
const parts = c.split(' ').map(s => Number(s.trim()));
|
|
918
|
+
[r, g, b] = parts;
|
|
919
|
+
}
|
|
912
920
|
else if (c.startsWith('#')) {
|
|
913
921
|
const hex = c.replace('#', '');
|
|
914
922
|
r = Number.parseInt(hex.substring(0, 2), 16);
|
|
@@ -927,6 +935,72 @@ export class SwitchBotMatterPlatform {
|
|
|
927
935
|
if (battery !== undefined) {
|
|
928
936
|
result.battery = Number(battery);
|
|
929
937
|
}
|
|
938
|
+
// VOC / TVOC (some air quality devices report total volatile organic compounds)
|
|
939
|
+
const voc = sd.voc ?? sd.tvoc;
|
|
940
|
+
if (voc !== undefined) {
|
|
941
|
+
result.voc = Number(voc);
|
|
942
|
+
}
|
|
943
|
+
// PM10 (some devices report PM10 alongside PM2.5)
|
|
944
|
+
const pm10 = sd.pm10;
|
|
945
|
+
if (pm10 !== undefined) {
|
|
946
|
+
result.pm10 = Number(pm10);
|
|
947
|
+
}
|
|
948
|
+
// PM2.5 (some BLE adverts use pm25 / pm_2_5)
|
|
949
|
+
const pm25 = sd.pm2_5 ?? sd.pm25 ?? sd.pm_2_5;
|
|
950
|
+
if (pm25 !== undefined) {
|
|
951
|
+
result.pm25 = Number(pm25);
|
|
952
|
+
}
|
|
953
|
+
// CO2 (carbon dioxide ppm)
|
|
954
|
+
const co2 = sd.co2 ?? sd.co2ppm ?? sd.carbonDioxide;
|
|
955
|
+
if (co2 !== undefined) {
|
|
956
|
+
result.co2 = Number(co2);
|
|
957
|
+
}
|
|
958
|
+
// Temperature (C) and Humidity (%) — support common shorthand keys
|
|
959
|
+
const temperature = sd.temperature ?? sd.temp ?? sd.t;
|
|
960
|
+
if (temperature !== undefined) {
|
|
961
|
+
result.temperature = Number(temperature);
|
|
962
|
+
}
|
|
963
|
+
const humidity = sd.humidity ?? sd.h ?? sd.humid;
|
|
964
|
+
if (humidity !== undefined) {
|
|
965
|
+
result.humidity = Number(humidity);
|
|
966
|
+
}
|
|
967
|
+
// Motion, Contact, Leak
|
|
968
|
+
const motion = sd.motion ?? sd.m;
|
|
969
|
+
if (motion !== undefined) {
|
|
970
|
+
result.motion = Boolean(motion);
|
|
971
|
+
}
|
|
972
|
+
const contact = sd.contact ?? sd.open;
|
|
973
|
+
if (contact !== undefined) {
|
|
974
|
+
result.contact = contact;
|
|
975
|
+
}
|
|
976
|
+
const leak = sd.leak ?? sd.water;
|
|
977
|
+
if (leak !== undefined) {
|
|
978
|
+
result.leak = Boolean(leak);
|
|
979
|
+
}
|
|
980
|
+
// Position / Cover / Curtain synonyms
|
|
981
|
+
const position = sd.position ?? sd.percent ?? sd.slidePosition ?? sd.curtainPosition;
|
|
982
|
+
if (position !== undefined) {
|
|
983
|
+
result.position = Number(position);
|
|
984
|
+
}
|
|
985
|
+
// Fan speed/speed
|
|
986
|
+
const fanSpeed = sd.fanSpeed ?? sd.speed;
|
|
987
|
+
if (fanSpeed !== undefined) {
|
|
988
|
+
result.fanSpeed = Number(fanSpeed);
|
|
989
|
+
}
|
|
990
|
+
// Lock state
|
|
991
|
+
const lock = sd.lock;
|
|
992
|
+
if (lock !== undefined) {
|
|
993
|
+
result.lock = lock;
|
|
994
|
+
}
|
|
995
|
+
// Robot vacuum fields
|
|
996
|
+
const rvcRunMode = sd.rvcRunMode;
|
|
997
|
+
if (rvcRunMode !== undefined) {
|
|
998
|
+
result.rvcRunMode = rvcRunMode;
|
|
999
|
+
}
|
|
1000
|
+
const rvcOperationalState = sd.rvcOperationalState;
|
|
1001
|
+
if (rvcOperationalState !== undefined) {
|
|
1002
|
+
result.rvcOperationalState = rvcOperationalState;
|
|
1003
|
+
}
|
|
930
1004
|
return result;
|
|
931
1005
|
}
|
|
932
1006
|
catch (e) {
|
|
@@ -984,6 +1058,14 @@ export class SwitchBotMatterPlatform {
|
|
|
984
1058
|
const parts = color.split(':').map(Number);
|
|
985
1059
|
[r, g, b] = parts;
|
|
986
1060
|
}
|
|
1061
|
+
else if (color.includes(',')) {
|
|
1062
|
+
const parts = color.split(',').map(s => Number(s.trim()));
|
|
1063
|
+
[r, g, b] = parts;
|
|
1064
|
+
}
|
|
1065
|
+
else if (color.includes(' ')) {
|
|
1066
|
+
const parts = color.split(' ').map(s => Number(s.trim()));
|
|
1067
|
+
[r, g, b] = parts;
|
|
1068
|
+
}
|
|
987
1069
|
else if (color.startsWith('#')) {
|
|
988
1070
|
const hex = color.replace('#', '');
|
|
989
1071
|
r = Number.parseInt(hex.substring(0, 2), 16);
|
|
@@ -993,10 +1075,10 @@ export class SwitchBotMatterPlatform {
|
|
|
993
1075
|
const [h, s] = rgb2hs(r, g, b);
|
|
994
1076
|
await safeUpdate(this.api.matter.clusterNames.ColorControl, { currentHue: Math.round((h / 360) * 254), currentSaturation: Math.round((s / 100) * 254) }, 'updateHueSaturation');
|
|
995
1077
|
}
|
|
996
|
-
// Battery/powerSource
|
|
997
|
-
if (status?.battery !== undefined || status?.batt !== undefined) {
|
|
1078
|
+
// Battery/powerSource (support many possible field names)
|
|
1079
|
+
if (status?.battery !== undefined || status?.batt !== undefined || status?.batteryLevel !== undefined || status?.batteryPercentage !== undefined || status?.battery_level !== undefined) {
|
|
998
1080
|
try {
|
|
999
|
-
const percentage = Number(status?.battery ?? status?.batt);
|
|
1081
|
+
const percentage = Number(status?.battery ?? status?.batt ?? status?.batteryPercentage ?? status?.batteryLevel ?? status?.battery_level);
|
|
1000
1082
|
const batPercentRemaining = Math.max(0, Math.min(200, Math.round(percentage * 2)));
|
|
1001
1083
|
let batChargeLevel = 0;
|
|
1002
1084
|
if (percentage < 20) {
|
|
@@ -1005,7 +1087,8 @@ export class SwitchBotMatterPlatform {
|
|
|
1005
1087
|
else if (percentage < 40) {
|
|
1006
1088
|
batChargeLevel = 1;
|
|
1007
1089
|
}
|
|
1008
|
-
|
|
1090
|
+
const powerCluster = (this.api.matter?.clusterNames && this.api.matter.clusterNames.PowerSource) ? this.api.matter.clusterNames.PowerSource : 'powerSource';
|
|
1091
|
+
await safeUpdate(powerCluster, { batPercentRemaining, batChargeLevel }, 'updateBatteryPercentage');
|
|
1009
1092
|
}
|
|
1010
1093
|
catch (e) {
|
|
1011
1094
|
this.debugLog(`Failed to apply battery status for ${dev.deviceId}: ${e?.message ?? e}`);
|
|
@@ -1028,10 +1111,10 @@ export class SwitchBotMatterPlatform {
|
|
|
1028
1111
|
this.debugLog(`Failed to apply temperature for ${dev.deviceId}: ${e?.message ?? e}`);
|
|
1029
1112
|
}
|
|
1030
1113
|
}
|
|
1031
|
-
// Humidity
|
|
1032
|
-
if (status?.humidity !== undefined || status?.h !== undefined) {
|
|
1114
|
+
// Humidity (support different keys)
|
|
1115
|
+
if (status?.humidity !== undefined || status?.h !== undefined || status?.humid !== undefined) {
|
|
1033
1116
|
try {
|
|
1034
|
-
const percent = Number(status?.humidity ?? status?.h);
|
|
1117
|
+
const percent = Number(status?.humidity ?? status?.h ?? status?.humid);
|
|
1035
1118
|
const measured = Math.round(percent * 100);
|
|
1036
1119
|
await safeUpdate('relativeHumidityMeasurement', { measuredValue: measured }, 'updateHumidity');
|
|
1037
1120
|
}
|
|
@@ -1118,6 +1201,46 @@ export class SwitchBotMatterPlatform {
|
|
|
1118
1201
|
this.debugLog(`Failed to apply rvcRunMode for ${dev.deviceId}: ${e?.message ?? e}`);
|
|
1119
1202
|
}
|
|
1120
1203
|
}
|
|
1204
|
+
// CO2 (carbon dioxide) - support common synonyms
|
|
1205
|
+
if (status?.co2 !== undefined || status?.co2ppm !== undefined || status?.carbonDioxide !== undefined) {
|
|
1206
|
+
try {
|
|
1207
|
+
const val = Number(status?.co2 ?? status?.co2ppm ?? status?.carbonDioxide);
|
|
1208
|
+
await safeUpdate('carbonDioxide', { carbonDioxideLevel: val }, 'updateCO2');
|
|
1209
|
+
}
|
|
1210
|
+
catch (e) {
|
|
1211
|
+
this.debugLog(`Failed to apply CO2 for ${dev.deviceId}: ${e?.message ?? e}`);
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
// PM2.5 / particulate matter
|
|
1215
|
+
if (status?.pm2_5 !== undefined || status?.pm25 !== undefined || status?.pm_2_5 !== undefined) {
|
|
1216
|
+
try {
|
|
1217
|
+
const pm = Number(status?.pm2_5 ?? status?.pm25 ?? status?.pm_2_5);
|
|
1218
|
+
await safeUpdate('pm2_5', { pm25: pm }, 'updatePM25');
|
|
1219
|
+
}
|
|
1220
|
+
catch (e) {
|
|
1221
|
+
this.debugLog(`Failed to apply PM2.5 for ${dev.deviceId}: ${e?.message ?? e}`);
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
// PM10 (some devices report pm10)
|
|
1225
|
+
if (status?.pm10 !== undefined) {
|
|
1226
|
+
try {
|
|
1227
|
+
const pm10 = Number(status?.pm10);
|
|
1228
|
+
await safeUpdate('pm10', { pm10 }, 'updatePM10');
|
|
1229
|
+
}
|
|
1230
|
+
catch (e) {
|
|
1231
|
+
this.debugLog(`Failed to apply PM10 for ${dev.deviceId}: ${e?.message ?? e}`);
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
// VOC / TVOC - volatile organic compounds
|
|
1235
|
+
if (status?.voc !== undefined || status?.tvoc !== undefined) {
|
|
1236
|
+
try {
|
|
1237
|
+
const val = Number(status?.voc ?? status?.tvoc);
|
|
1238
|
+
await safeUpdate('voc', { voc: val }, 'updateVOC');
|
|
1239
|
+
}
|
|
1240
|
+
catch (e) {
|
|
1241
|
+
this.debugLog(`Failed to apply VOC for ${dev.deviceId}: ${e?.message ?? e}`);
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1121
1244
|
if (status?.rvcOperationalState !== undefined) {
|
|
1122
1245
|
try {
|
|
1123
1246
|
await safeUpdate('rvcOperationalState', { operationalState: Number(status.rvcOperationalState) }, 'updateOperationalState');
|
|
@@ -1164,6 +1287,55 @@ export class SwitchBotMatterPlatform {
|
|
|
1164
1287
|
this.infoLog(`Registering ${this.discoveredDevices.length} discovered SwitchBot device(s) as Matter accessories`);
|
|
1165
1288
|
// Merge device config (deviceConfig per deviceType and per-device overrides) to match HAP behavior
|
|
1166
1289
|
const devicesToProcess = await this.mergeDiscoveredDevices(this.discoveredDevices);
|
|
1290
|
+
// By default, automatically remove previously-registered Matter
|
|
1291
|
+
// accessories whose deviceId is not present in the merged discovered
|
|
1292
|
+
// list. If the user explicitly sets `options.keepStaleAccessories` to
|
|
1293
|
+
// true, then we will keep previously-registered accessories (legacy
|
|
1294
|
+
// behavior).
|
|
1295
|
+
if (this.config.options?.keepStaleAccessories) {
|
|
1296
|
+
this.debugLog('Keeping previously-registered stale accessories because options.keepStaleAccessories=true');
|
|
1297
|
+
}
|
|
1298
|
+
else {
|
|
1299
|
+
try {
|
|
1300
|
+
const desiredIds = new Set((devicesToProcess || []).map((d) => this.normalizeDeviceId(d.deviceId)));
|
|
1301
|
+
const toUnregister = [];
|
|
1302
|
+
for (const [uuid, acc] of Array.from(this.matterAccessories.entries())) {
|
|
1303
|
+
try {
|
|
1304
|
+
const deviceId = acc?.context?.deviceId;
|
|
1305
|
+
if (!deviceId) {
|
|
1306
|
+
continue;
|
|
1307
|
+
}
|
|
1308
|
+
const nid = this.normalizeDeviceId(deviceId);
|
|
1309
|
+
if (!desiredIds.has(nid)) {
|
|
1310
|
+
// Accessory exists but is no longer desired -> schedule for removal
|
|
1311
|
+
this.infoLog(`Removing previously-registered accessory for deviceId=${deviceId} (no longer discovered or configured)`);
|
|
1312
|
+
try {
|
|
1313
|
+
this.clearDeviceResources(deviceId);
|
|
1314
|
+
}
|
|
1315
|
+
catch (e) {
|
|
1316
|
+
this.debugLog(`Failed to clear resources for ${deviceId} before unregister: ${e?.message ?? e}`);
|
|
1317
|
+
}
|
|
1318
|
+
toUnregister.push(acc);
|
|
1319
|
+
this.matterAccessories.delete(uuid);
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
catch (e) {
|
|
1323
|
+
this.debugLog(`Error while checking existing accessory ${uuid}: ${e?.message ?? e}`);
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
if (toUnregister.length > 0) {
|
|
1327
|
+
try {
|
|
1328
|
+
await this.api.matter.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, toUnregister);
|
|
1329
|
+
}
|
|
1330
|
+
catch (e) {
|
|
1331
|
+
this.debugLog(`Failed to unregister accessories: ${e?.message ?? e}`);
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
catch (e) {
|
|
1336
|
+
this.debugLog(`Failed to remove stale accessories: ${e?.message ?? e}`);
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1167
1339
|
// We'll separate discovered devices into two buckets:
|
|
1168
1340
|
// - platformAccessories: accessories that will be hosted under the plugin's Matter bridge
|
|
1169
1341
|
// - roboticAccessories: robot vacuum devices which require standalone commissioning behaviour
|