@switchbot/homebridge-switchbot 5.0.0-beta.26 → 5.0.0-beta.28
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/homebridge-ui/public/index.html +48 -1
- package/dist/homebridge-ui/server.js +35 -0
- package/dist/homebridge-ui/server.js.map +1 -1
- package/dist/platform-hap.d.ts.map +1 -1
- package/dist/platform-hap.js +13 -0
- package/dist/platform-hap.js.map +1 -1
- package/dist/platform-matter.d.ts.map +1 -1
- package/dist/platform-matter.js +50 -4
- package/dist/platform-matter.js.map +1 -1
- package/docs/variables/default.html +1 -1
- package/package.json +1 -1
- package/src/homebridge-ui/public/index.html +48 -1
- package/src/homebridge-ui/server.ts +37 -0
- package/src/platform-hap.ts +14 -0
- package/src/platform-matter.ts +48 -5
|
@@ -37,6 +37,43 @@ class PluginUiServer extends HomebridgePluginUiServer {
|
|
|
37
37
|
return []
|
|
38
38
|
}
|
|
39
39
|
})
|
|
40
|
+
// Provide Matter cached accessories if Homebridge stores them separately.
|
|
41
|
+
this.onRequest('getCachedMatterAccessories', () => {
|
|
42
|
+
try {
|
|
43
|
+
const plugin = 'homebridge-switchbot'
|
|
44
|
+
const devicesToReturn: any[] = []
|
|
45
|
+
|
|
46
|
+
const accFile = `${this.homebridgeStoragePath}/accessories/cachedAccessories`
|
|
47
|
+
const matterFile = `${this.homebridgeStoragePath}/accessories/cachedMatterAccessories`
|
|
48
|
+
|
|
49
|
+
const readAndCollect = (filePath: string) => {
|
|
50
|
+
if (!fs.existsSync(filePath)) {
|
|
51
|
+
return
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
const parsed: any[] = JSON.parse(fs.readFileSync(filePath, 'utf8'))
|
|
55
|
+
parsed.forEach((entry: any) => {
|
|
56
|
+
// Entry shape varies between Homebridge versions; try common locations
|
|
57
|
+
const pluginName = entry.plugin || entry?.accessory?.plugin || entry?.accessory?.pluginName
|
|
58
|
+
const acc = entry.accessory ?? entry
|
|
59
|
+
if (pluginName === plugin) {
|
|
60
|
+
devicesToReturn.push(acc as never)
|
|
61
|
+
}
|
|
62
|
+
})
|
|
63
|
+
} catch {
|
|
64
|
+
// ignore parse errors for a single file
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Read both canonical files (some Homebridge versions use one or the other)
|
|
69
|
+
readAndCollect(accFile)
|
|
70
|
+
readAndCollect(matterFile)
|
|
71
|
+
|
|
72
|
+
return devicesToReturn
|
|
73
|
+
} catch {
|
|
74
|
+
return []
|
|
75
|
+
}
|
|
76
|
+
})
|
|
40
77
|
this.ready()
|
|
41
78
|
}
|
|
42
79
|
}
|
package/src/platform-hap.ts
CHANGED
|
@@ -144,6 +144,20 @@ export class SwitchBotHAPPlatform implements DynamicPlatformPlugin {
|
|
|
144
144
|
devices: config.devices as { deviceId: string }[],
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
+
// Determine platform logging preference (match HAP behaviour as closely as
|
|
148
|
+
// possible using config values. We default to 'standard' when unspecified.)
|
|
149
|
+
this.platformLogging = (this.config.options?.logging === 'debug' || this.config.options?.logging === 'standard' || this.config.options?.logging === 'none')
|
|
150
|
+
? this.config.options.logging
|
|
151
|
+
: 'standard'
|
|
152
|
+
|
|
153
|
+
// Unconditional diagnostic using the raw Homebridge `log` so it always
|
|
154
|
+
// appears regardless of the platform logging helpers' gating logic.
|
|
155
|
+
try {
|
|
156
|
+
this.log.debug?.(`[SwitchBot HAP] effective platformLogging=${String(this.platformLogging)}`)
|
|
157
|
+
} catch (e: any) {
|
|
158
|
+
// swallow any logging errors — diagnostics are best-effort
|
|
159
|
+
}
|
|
160
|
+
|
|
147
161
|
// Normalize deviceConfig to remove UI-inserted defaults (lots of false/empty values)
|
|
148
162
|
try {
|
|
149
163
|
if ((this.config as any).options) {
|
package/src/platform-matter.ts
CHANGED
|
@@ -65,7 +65,8 @@ export class SwitchBotMatterPlatform implements DynamicPlatformPlugin {
|
|
|
65
65
|
private webhookEventListener: Server | null = null
|
|
66
66
|
private webhookEventHandler: { [x: string]: (context: any) => void } = {}
|
|
67
67
|
// Platform logging toggle (can be controlled via UI or config)
|
|
68
|
-
|
|
68
|
+
// Use same shape as HAP platform: string values like 'debug', 'debugMode', 'standard', or 'none'
|
|
69
|
+
private platformLogging?: string
|
|
69
70
|
|
|
70
71
|
// Platform-provided logging helpers (attached in constructor)
|
|
71
72
|
infoLog!: (...args: any[]) => void
|
|
@@ -84,6 +85,20 @@ export class SwitchBotMatterPlatform implements DynamicPlatformPlugin {
|
|
|
84
85
|
public readonly config: SwitchBotPlatformConfig,
|
|
85
86
|
public readonly api: API,
|
|
86
87
|
) {
|
|
88
|
+
// Determine platform logging preference (match HAP behaviour as closely as
|
|
89
|
+
// possible using config values. We default to 'standard' when unspecified.)
|
|
90
|
+
this.platformLogging = (this.config.options?.logging === 'debug' || this.config.options?.logging === 'standard' || this.config.options?.logging === 'none')
|
|
91
|
+
? this.config.options.logging
|
|
92
|
+
: 'standard'
|
|
93
|
+
|
|
94
|
+
// Unconditional diagnostic using the raw Homebridge `log` so it always
|
|
95
|
+
// appears regardless of the platform logging helpers' gating logic.
|
|
96
|
+
try {
|
|
97
|
+
this.log.debug?.(`[SwitchBot Matter] effective platformLogging=${String(this.platformLogging)}`)
|
|
98
|
+
} catch (e: any) {
|
|
99
|
+
// swallow any logging errors — diagnostics are best-effort
|
|
100
|
+
}
|
|
101
|
+
|
|
87
102
|
// Attach platform-wide logging helpers from utils so Matter and device
|
|
88
103
|
// classes can use consistent logging methods (infoLog/debugLog/etc.)
|
|
89
104
|
const _pl = createPlatformLogger(async () => (this as any).platformLogging, this.log)
|
|
@@ -727,7 +742,15 @@ export class SwitchBotMatterPlatform implements DynamicPlatformPlugin {
|
|
|
727
742
|
} else if (percentage < 40) {
|
|
728
743
|
batChargeLevel = 1
|
|
729
744
|
}
|
|
730
|
-
|
|
745
|
+
try {
|
|
746
|
+
await this.api.matter.updateAccessoryState(uuidLocal, 'powerSource', { batPercentRemaining, batChargeLevel })
|
|
747
|
+
} catch (updateError: any) {
|
|
748
|
+
// Silently skip if powerSource cluster doesn't exist on this device
|
|
749
|
+
const msg = String(updateError?.message ?? updateError)
|
|
750
|
+
if (!msg.includes('does not exist') && !msg.includes('not found')) {
|
|
751
|
+
throw updateError
|
|
752
|
+
}
|
|
753
|
+
}
|
|
731
754
|
} catch (e: any) {
|
|
732
755
|
this.debugLog(`Failed to update battery state for ${dev.deviceId}: ${e?.message ?? e}`)
|
|
733
756
|
}
|
|
@@ -896,6 +919,7 @@ export class SwitchBotMatterPlatform implements DynamicPlatformPlugin {
|
|
|
896
919
|
// Immediate one-shot to populate initial state
|
|
897
920
|
;(async () => {
|
|
898
921
|
try {
|
|
922
|
+
this.infoLog(`Performing initial OpenAPI refresh for ${dev.deviceId}`)
|
|
899
923
|
const { response, statusCode } = await this.switchBotAPI!.getDeviceStatus(dev.deviceId, this.config.credentials?.token, this.config.credentials?.secret)
|
|
900
924
|
const respAny: any = response
|
|
901
925
|
const body = respAny?.body ?? respAny
|
|
@@ -908,14 +932,18 @@ export class SwitchBotMatterPlatform implements DynamicPlatformPlugin {
|
|
|
908
932
|
if (statusCode === 100 || statusCode === 200) {
|
|
909
933
|
const status = body?.status ?? body
|
|
910
934
|
await this.applyStatusToAccessory(uuid, dev, status)
|
|
935
|
+
this.infoLog(`Initial OpenAPI refresh succeeded for ${dev.deviceId}`)
|
|
936
|
+
} else {
|
|
937
|
+
this.warnLog(`Initial OpenAPI refresh returned unexpected statusCode=${statusCode} for ${dev.deviceId}`)
|
|
911
938
|
}
|
|
912
939
|
} catch (e: any) {
|
|
913
|
-
this.
|
|
940
|
+
this.errorLog(`Initial OpenAPI refresh failed for ${dev.deviceId}: ${e?.message ?? e}`)
|
|
914
941
|
}
|
|
915
942
|
})()
|
|
916
943
|
|
|
917
944
|
const timer = setInterval(async () => {
|
|
918
945
|
try {
|
|
946
|
+
this.debugLog(`Performing periodic OpenAPI refresh for ${dev.deviceId}`)
|
|
919
947
|
const { response, statusCode } = await this.switchBotAPI!.getDeviceStatus(dev.deviceId, this.config.credentials?.token, this.config.credentials?.secret)
|
|
920
948
|
const respAny: any = response
|
|
921
949
|
const body = respAny?.body ?? respAny
|
|
@@ -928,9 +956,11 @@ export class SwitchBotMatterPlatform implements DynamicPlatformPlugin {
|
|
|
928
956
|
if (statusCode === 100 || statusCode === 200) {
|
|
929
957
|
const status = body?.status ?? body
|
|
930
958
|
await this.applyStatusToAccessory(uuid, dev, status)
|
|
959
|
+
} else {
|
|
960
|
+
this.debugLog(`Periodic OpenAPI refresh returned unexpected statusCode=${statusCode} for ${dev.deviceId}`)
|
|
931
961
|
}
|
|
932
962
|
} catch (e: any) {
|
|
933
|
-
this.
|
|
963
|
+
this.errorLog(`Periodic OpenAPI refresh failed for ${dev.deviceId}: ${e?.message ?? e}`)
|
|
934
964
|
}
|
|
935
965
|
}, Number(refreshRateSec) * 1000)
|
|
936
966
|
|
|
@@ -1013,9 +1043,11 @@ export class SwitchBotMatterPlatform implements DynamicPlatformPlugin {
|
|
|
1013
1043
|
const url = this.config.options?.webhookURL
|
|
1014
1044
|
try {
|
|
1015
1045
|
this.switchBotAPI?.setupWebhook(url)
|
|
1046
|
+
this.infoLog(`Webhook configured for URL: ${url}`)
|
|
1016
1047
|
// Listen for webhook events
|
|
1017
1048
|
this.switchBotAPI?.on('webhookEvent', (body: any) => {
|
|
1018
1049
|
try {
|
|
1050
|
+
this.infoLog(`Received webhook event for device: ${body.context.deviceMac}`)
|
|
1019
1051
|
if (this.config.options?.mqttURL) {
|
|
1020
1052
|
const mac = body.context.deviceMac?.toLowerCase().match(/[\s\S]{1,2}/g)?.join(':')
|
|
1021
1053
|
const options = this.config.options?.mqttPubOptions || {}
|
|
@@ -1236,7 +1268,18 @@ export class SwitchBotMatterPlatform implements DynamicPlatformPlugin {
|
|
|
1236
1268
|
// some accessories expose updateState that accepts cluster and attributes
|
|
1237
1269
|
await instance.updateState(cluster, attributes)
|
|
1238
1270
|
} else {
|
|
1239
|
-
|
|
1271
|
+
try {
|
|
1272
|
+
await this.api.matter.updateAccessoryState(uuidLocal, cluster, attributes)
|
|
1273
|
+
} catch (updateError: any) {
|
|
1274
|
+
// Silently ignore "does not exist" errors for clusters that aren't
|
|
1275
|
+
// supported by this device type (e.g., powerSource on WindowBlind).
|
|
1276
|
+
const msg = String(updateError?.message ?? updateError)
|
|
1277
|
+
if (msg.includes('does not exist') || msg.includes('not found')) {
|
|
1278
|
+
this.debugLog(`Cluster ${cluster} not available on ${dev.deviceId}, skipping update`)
|
|
1279
|
+
return
|
|
1280
|
+
}
|
|
1281
|
+
throw updateError
|
|
1282
|
+
}
|
|
1240
1283
|
}
|
|
1241
1284
|
} catch (e: any) {
|
|
1242
1285
|
this.debugLog(`safeUpdate failed for ${dev.deviceId} cluster=${cluster}: ${e?.message ?? e}`)
|