@switchbot/homebridge-switchbot 5.0.0-beta.11 → 5.0.0-beta.13
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/baseMatterAccessory.test.d.ts +2 -0
- package/dist/baseMatterAccessory.test.d.ts.map +1 -0
- package/dist/baseMatterAccessory.test.js +71 -0
- package/dist/baseMatterAccessory.test.js.map +1 -0
- package/dist/devices-matter/BaseMatterAccessory.d.ts +15 -0
- package/dist/devices-matter/BaseMatterAccessory.d.ts.map +1 -1
- package/dist/devices-matter/BaseMatterAccessory.js +120 -0
- package/dist/devices-matter/BaseMatterAccessory.js.map +1 -1
- package/dist/devices-matter/ColorLightAccessory.d.ts.map +1 -1
- package/dist/devices-matter/ColorLightAccessory.js +12 -12
- package/dist/devices-matter/ColorLightAccessory.js.map +1 -1
- package/dist/devices-matter/ColorTemperatureLightAccessory.d.ts.map +1 -1
- package/dist/devices-matter/ColorTemperatureLightAccessory.js +5 -7
- package/dist/devices-matter/ColorTemperatureLightAccessory.js.map +1 -1
- package/dist/devices-matter/DimmableLightAccessory.js +9 -9
- package/dist/devices-matter/DimmableLightAccessory.js.map +1 -1
- package/dist/devices-matter/ExtendedColorLightAccessory.d.ts.map +1 -1
- package/dist/devices-matter/ExtendedColorLightAccessory.js +14 -15
- package/dist/devices-matter/ExtendedColorLightAccessory.js.map +1 -1
- package/dist/devices-matter/OnOffLightAccessory.d.ts.map +1 -1
- package/dist/devices-matter/OnOffLightAccessory.js +8 -16
- package/dist/devices-matter/OnOffLightAccessory.js.map +1 -1
- package/dist/devices-matter/OnOffOutletAccessory.d.ts +2 -0
- package/dist/devices-matter/OnOffOutletAccessory.d.ts.map +1 -1
- package/dist/devices-matter/OnOffOutletAccessory.js +10 -7
- package/dist/devices-matter/OnOffOutletAccessory.js.map +1 -1
- package/dist/devices-matter/OnOffSwitchAccessory.js +2 -2
- package/dist/devices-matter/OnOffSwitchAccessory.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -63
- package/dist/index.js.map +1 -1
- package/dist/platform-matter.d.ts.map +1 -1
- package/dist/platform-matter.js +37 -22
- package/dist/platform-matter.js.map +1 -1
- package/dist/utils.d.ts +35 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +98 -0
- package/dist/utils.js.map +1 -1
- package/dist/utils.test.d.ts +2 -0
- package/dist/utils.test.d.ts.map +1 -0
- package/dist/utils.test.js +95 -0
- package/dist/utils.test.js.map +1 -0
- package/docs/variables/default.html +1 -1
- package/package.json +1 -1
- package/src/baseMatterAccessory.test.ts +88 -0
- package/src/devices-matter/BaseMatterAccessory.ts +118 -0
- package/src/devices-matter/ColorLightAccessory.ts +12 -12
- package/src/devices-matter/ColorTemperatureLightAccessory.ts +5 -7
- package/src/devices-matter/DimmableLightAccessory.ts +9 -9
- package/src/devices-matter/ExtendedColorLightAccessory.ts +14 -15
- package/src/devices-matter/OnOffLightAccessory.ts +8 -16
- package/src/devices-matter/OnOffOutletAccessory.ts +12 -7
- package/src/devices-matter/OnOffSwitchAccessory.ts +2 -2
- package/src/index.ts +4 -64
- package/src/platform-matter.ts +36 -22
- package/src/utils.test.ts +96 -0
- package/src/utils.ts +97 -0
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
|
|
11
11
|
import type { API, EndpointType, Logger, MatterAccessory } from 'homebridge'
|
|
12
12
|
|
|
13
|
+
import { rgb2hs } from '../utils.js'
|
|
14
|
+
|
|
13
15
|
export interface BaseMatterAccessoryConfig {
|
|
14
16
|
uuid: string
|
|
15
17
|
displayName: string
|
|
@@ -108,6 +110,122 @@ export abstract class BaseMatterAccessory implements MatterAccessory {
|
|
|
108
110
|
this.log.warn(`[${this.displayName}] ${message}`, ...args)
|
|
109
111
|
}
|
|
110
112
|
|
|
113
|
+
/**
|
|
114
|
+
* Convenience helpers that delegate OpenAPI/BLE commands to platform-provided
|
|
115
|
+
* functions that are injected into the accessory `context` by
|
|
116
|
+
* `platform-matter` when the accessory is created from a discovered device.
|
|
117
|
+
*
|
|
118
|
+
* These methods are intentionally thin wrappers — the platform controls
|
|
119
|
+
* retries, discovery and client lifecycle via the helper functions.
|
|
120
|
+
*/
|
|
121
|
+
protected async sendOpenAPICommand(command: string, parameter = 'default'): Promise<any> {
|
|
122
|
+
const ctx: any = this.context as any
|
|
123
|
+
const fn = ctx?.sendOpenAPI
|
|
124
|
+
if (typeof fn === 'function') {
|
|
125
|
+
return fn(command, parameter)
|
|
126
|
+
}
|
|
127
|
+
throw new Error('OpenAPI helper not available')
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
protected async sendBLECommand(methodName: string, ...args: any[]): Promise<any> {
|
|
131
|
+
const ctx: any = this.context as any
|
|
132
|
+
const fn = ctx?.sendBLE
|
|
133
|
+
if (typeof fn === 'function') {
|
|
134
|
+
return fn(methodName, ...args)
|
|
135
|
+
}
|
|
136
|
+
throw new Error('BLE helper not available')
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
public async sendOnCommand(deviceId?: string): Promise<void> {
|
|
140
|
+
const ctx: any = this.context as any
|
|
141
|
+
const id = deviceId ?? ctx?.deviceId
|
|
142
|
+
try {
|
|
143
|
+
if (ctx?.connectionType === 'BLE') {
|
|
144
|
+
await this.sendBLECommand('turnOn')
|
|
145
|
+
} else {
|
|
146
|
+
await this.sendOpenAPICommand('turnOn')
|
|
147
|
+
}
|
|
148
|
+
// update our matter state
|
|
149
|
+
await this.updateState(this.api.matter.clusterNames.OnOff, { onOff: true })
|
|
150
|
+
this.logInfo(`sendOnCommand successful for ${id}`)
|
|
151
|
+
} catch (e: any) {
|
|
152
|
+
this.logError(`sendOnCommand failed for ${id}: ${String(e?.message ?? e)}`)
|
|
153
|
+
throw e
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
public async sendOffCommand(deviceId?: string): Promise<void> {
|
|
158
|
+
const ctx: any = this.context as any
|
|
159
|
+
const id = deviceId ?? ctx?.deviceId
|
|
160
|
+
try {
|
|
161
|
+
if (ctx?.connectionType === 'BLE') {
|
|
162
|
+
await this.sendBLECommand('turnOff')
|
|
163
|
+
} else {
|
|
164
|
+
await this.sendOpenAPICommand('turnOff')
|
|
165
|
+
}
|
|
166
|
+
await this.updateState(this.api.matter.clusterNames.OnOff, { onOff: false })
|
|
167
|
+
this.logInfo(`sendOffCommand successful for ${id}`)
|
|
168
|
+
} catch (e: any) {
|
|
169
|
+
this.logError(`sendOffCommand failed for ${id}: ${String(e?.message ?? e)}`)
|
|
170
|
+
throw e
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
public async sendSetBrightness(percent: number, deviceId?: string): Promise<void> {
|
|
175
|
+
const ctx: any = this.context as any
|
|
176
|
+
const id = deviceId ?? ctx?.deviceId
|
|
177
|
+
try {
|
|
178
|
+
if (ctx?.connectionType === 'BLE') {
|
|
179
|
+
await this.sendBLECommand('setBrightness', percent)
|
|
180
|
+
} else {
|
|
181
|
+
await this.sendOpenAPICommand('setBrightness', String(percent))
|
|
182
|
+
}
|
|
183
|
+
const level = Math.round((percent / 100) * 254)
|
|
184
|
+
await this.updateState(this.api.matter.clusterNames.LevelControl, { currentLevel: level })
|
|
185
|
+
this.logInfo(`sendSetBrightness successful for ${id}: ${percent}%`)
|
|
186
|
+
} catch (e: any) {
|
|
187
|
+
this.logError(`sendSetBrightness failed for ${id}: ${String(e?.message ?? e)}`)
|
|
188
|
+
throw e
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
public async sendSetColor(r: number, g: number, b: number, deviceId?: string): Promise<void> {
|
|
193
|
+
const ctx: any = this.context as any
|
|
194
|
+
const id = deviceId ?? ctx?.deviceId
|
|
195
|
+
try {
|
|
196
|
+
if (ctx?.connectionType === 'BLE') {
|
|
197
|
+
await this.sendBLECommand('setRGB', r, g, b)
|
|
198
|
+
} else {
|
|
199
|
+
await this.sendOpenAPICommand('setColor', `${r}:${g}:${b}`)
|
|
200
|
+
}
|
|
201
|
+
// Convert to hue/sat for matter state update
|
|
202
|
+
const [h, s] = rgb2hs(r, g, b)
|
|
203
|
+
await this.updateState(this.api.matter.clusterNames.ColorControl, { currentHue: Math.round((h / 360) * 254), currentSaturation: Math.round((s / 100) * 254) })
|
|
204
|
+
this.logInfo(`sendSetColor successful for ${id}: ${r},${g},${b}`)
|
|
205
|
+
} catch (e: any) {
|
|
206
|
+
this.logError(`sendSetColor failed for ${id}: ${String(e?.message ?? e)}`)
|
|
207
|
+
throw e
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
public async sendSetColorTemperature(kelvin: number, deviceId?: string): Promise<void> {
|
|
212
|
+
const ctx: any = this.context as any
|
|
213
|
+
const id = deviceId ?? ctx?.deviceId
|
|
214
|
+
try {
|
|
215
|
+
if (ctx?.connectionType === 'BLE') {
|
|
216
|
+
await this.sendBLECommand('setColorTemperature', kelvin)
|
|
217
|
+
} else {
|
|
218
|
+
await this.sendOpenAPICommand('setColorTemperature', `${kelvin}`)
|
|
219
|
+
}
|
|
220
|
+
const mireds = Math.round(1000000 / kelvin)
|
|
221
|
+
await this.updateState(this.api.matter.clusterNames.ColorControl, { colorTemperatureMireds: mireds })
|
|
222
|
+
this.logInfo(`sendSetColorTemperature successful for ${id}: ${kelvin}K`)
|
|
223
|
+
} catch (e: any) {
|
|
224
|
+
this.logError(`sendSetColorTemperature failed for ${id}: ${String(e?.message ?? e)}`)
|
|
225
|
+
throw e
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
111
229
|
/**
|
|
112
230
|
* Convert this class instance to a plain MatterAccessory object
|
|
113
231
|
* This is what gets registered with Homebridge
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import type { API, Logger, MatterRequests } from 'homebridge'
|
|
7
7
|
|
|
8
|
+
import { hs2rgb } from '../utils.js'
|
|
8
9
|
import { BaseMatterAccessory } from './BaseMatterAccessory.js'
|
|
9
10
|
|
|
10
11
|
export class ColorLightAccessory extends BaseMatterAccessory {
|
|
@@ -56,38 +57,37 @@ export class ColorLightAccessory extends BaseMatterAccessory {
|
|
|
56
57
|
|
|
57
58
|
private async handleOn(): Promise<void> {
|
|
58
59
|
this.logInfo('turning on.')
|
|
59
|
-
|
|
60
|
+
await this.sendOnCommand()
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
private async handleOff(): Promise<void> {
|
|
63
64
|
this.logInfo('turning off.')
|
|
64
|
-
|
|
65
|
+
await this.sendOffCommand()
|
|
65
66
|
}
|
|
66
67
|
|
|
67
68
|
private async handleSetLevel(request: MatterRequests.MoveToLevel): Promise<void> {
|
|
68
69
|
this.logInfo(`MoveToLevel request: ${JSON.stringify(request)}`)
|
|
69
70
|
const { level } = request
|
|
70
71
|
const brightnessPercent = Math.round((level / 254) * 100)
|
|
71
|
-
this.
|
|
72
|
-
// TODO: await myLightAPI.setBrightness(brightnessPercent)
|
|
72
|
+
await this.sendSetBrightness(brightnessPercent)
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
private async handleSetColor(request: MatterRequests.MoveToColor): Promise<void> {
|
|
76
76
|
this.logInfo(`MoveToColor request: ${JSON.stringify(request)}`)
|
|
77
|
-
const { colorX, colorY
|
|
78
|
-
const
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
77
|
+
const { colorX, colorY } = request
|
|
78
|
+
const hueApprox = Math.round((colorX / 65535) * 360)
|
|
79
|
+
const satApprox = Math.round((colorY / 65535) * 100)
|
|
80
|
+
const [r, g, b] = hs2rgb(hueApprox, satApprox)
|
|
81
|
+
await this.sendSetColor(r, g, b)
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
private async handleSetHueSaturation(request: MatterRequests.MoveToHueAndSaturation): Promise<void> {
|
|
85
85
|
this.logInfo(`MoveToHueAndSaturation request: ${JSON.stringify(request)}`)
|
|
86
|
-
const { hue, saturation
|
|
86
|
+
const { hue, saturation } = request
|
|
87
87
|
const hueDegrees = Math.round((hue / 254) * 360)
|
|
88
88
|
const saturationPercent = Math.round((saturation / 254) * 100)
|
|
89
|
-
|
|
90
|
-
|
|
89
|
+
const [r, g, b] = hs2rgb(hueDegrees, saturationPercent)
|
|
90
|
+
await this.sendSetColor(r, g, b)
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
public updateOnOffState(isOn: boolean): void {
|
|
@@ -52,28 +52,26 @@ export class ColorTemperatureLightAccessory extends BaseMatterAccessory {
|
|
|
52
52
|
|
|
53
53
|
private async handleOn(): Promise<void> {
|
|
54
54
|
this.logInfo('turning on.')
|
|
55
|
-
|
|
55
|
+
await this.sendOnCommand()
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
private async handleOff(): Promise<void> {
|
|
59
59
|
this.logInfo('turning off.')
|
|
60
|
-
|
|
60
|
+
await this.sendOffCommand()
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
private async handleSetLevel(request: MatterRequests.MoveToLevel): Promise<void> {
|
|
64
64
|
this.logInfo(`MoveToLevel request: ${JSON.stringify(request)}`)
|
|
65
65
|
const { level } = request
|
|
66
66
|
const brightnessPercent = Math.round((level / 254) * 100)
|
|
67
|
-
this.
|
|
68
|
-
// TODO: await myLightAPI.setBrightness(brightnessPercent)
|
|
67
|
+
await this.sendSetBrightness(brightnessPercent)
|
|
69
68
|
}
|
|
70
69
|
|
|
71
70
|
private async handleSetColorTemperature(request: MatterRequests.MoveToColorTemperature): Promise<void> {
|
|
72
71
|
this.logInfo(`MoveToColorTemperature request: ${JSON.stringify(request)}`)
|
|
73
|
-
const { colorTemperatureMireds
|
|
72
|
+
const { colorTemperatureMireds } = request
|
|
74
73
|
const kelvin = Math.round(1000000 / colorTemperatureMireds)
|
|
75
|
-
this.
|
|
76
|
-
// TODO: await myLightAPI.setColorTemperature(kelvin, transitionTime)
|
|
74
|
+
await this.sendSetColorTemperature(kelvin)
|
|
77
75
|
}
|
|
78
76
|
|
|
79
77
|
public updateOnOffState(isOn: boolean): void {
|
|
@@ -69,10 +69,10 @@ export class DimmableLightAccessory extends BaseMatterAccessory {
|
|
|
69
69
|
this.logInfo('turning on.')
|
|
70
70
|
|
|
71
71
|
try {
|
|
72
|
-
//
|
|
73
|
-
|
|
72
|
+
// Use platform helper (OpenAPI/BLE) when available
|
|
73
|
+
await this.sendOnCommand()
|
|
74
74
|
|
|
75
|
-
this.logInfo('physical device turned on.')
|
|
75
|
+
this.logInfo('physical device turned on (via platform helper).')
|
|
76
76
|
} catch (error) {
|
|
77
77
|
this.logError('failed to turn on:', error)
|
|
78
78
|
throw error
|
|
@@ -86,10 +86,10 @@ export class DimmableLightAccessory extends BaseMatterAccessory {
|
|
|
86
86
|
this.logInfo('turning off.')
|
|
87
87
|
|
|
88
88
|
try {
|
|
89
|
-
//
|
|
90
|
-
|
|
89
|
+
// Use platform helper (OpenAPI/BLE) when available
|
|
90
|
+
await this.sendOffCommand()
|
|
91
91
|
|
|
92
|
-
this.logInfo('physical device turned off.')
|
|
92
|
+
this.logInfo('physical device turned off (via platform helper).')
|
|
93
93
|
} catch (error) {
|
|
94
94
|
this.logError('failed to turn off:', error)
|
|
95
95
|
throw error
|
|
@@ -110,10 +110,10 @@ export class DimmableLightAccessory extends BaseMatterAccessory {
|
|
|
110
110
|
this.logInfo(`setting brightness to ${brightnessPercent}% (level: ${level}), transitionTime: ${transitionTime}.`)
|
|
111
111
|
|
|
112
112
|
try {
|
|
113
|
-
//
|
|
114
|
-
|
|
113
|
+
// Use platform helper (OpenAPI/BLE) when available
|
|
114
|
+
await this.sendSetBrightness(brightnessPercent)
|
|
115
115
|
|
|
116
|
-
this.logInfo(`physical device brightness set to ${brightnessPercent}
|
|
116
|
+
this.logInfo(`physical device brightness set to ${brightnessPercent}% (via platform helper).`)
|
|
117
117
|
} catch (error) {
|
|
118
118
|
this.logError('Failed to set brightness:', error)
|
|
119
119
|
throw error
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import type { API, Logger, MatterRequests } from 'homebridge'
|
|
7
7
|
|
|
8
|
+
import { hs2rgb } from '../utils.js'
|
|
8
9
|
import { BaseMatterAccessory } from './BaseMatterAccessory.js'
|
|
9
10
|
|
|
10
11
|
export class ExtendedColorLightAccessory extends BaseMatterAccessory {
|
|
@@ -61,46 +62,44 @@ export class ExtendedColorLightAccessory extends BaseMatterAccessory {
|
|
|
61
62
|
|
|
62
63
|
private async handleOn(): Promise<void> {
|
|
63
64
|
this.logInfo('turning on.')
|
|
64
|
-
|
|
65
|
+
await this.sendOnCommand()
|
|
65
66
|
}
|
|
66
67
|
|
|
67
68
|
private async handleOff(): Promise<void> {
|
|
68
69
|
this.logInfo('turning off.')
|
|
69
|
-
|
|
70
|
+
await this.sendOffCommand()
|
|
70
71
|
}
|
|
71
72
|
|
|
72
73
|
private async handleSetLevel(request: MatterRequests.MoveToLevel): Promise<void> {
|
|
73
74
|
this.logInfo(`MoveToLevel request: ${JSON.stringify(request)}`)
|
|
74
75
|
const { level } = request
|
|
75
76
|
const brightnessPercent = Math.round((level / 254) * 100)
|
|
76
|
-
this.
|
|
77
|
-
// TODO: await myLightAPI.setBrightness(brightnessPercent)
|
|
77
|
+
await this.sendSetBrightness(brightnessPercent)
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
private async handleSetColor(request: MatterRequests.MoveToColor): Promise<void> {
|
|
81
81
|
this.logInfo(`MoveToColor request: ${JSON.stringify(request)}`)
|
|
82
|
-
const { colorX, colorY
|
|
83
|
-
const
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
82
|
+
const { colorX, colorY } = request
|
|
83
|
+
const hueApprox = Math.round((colorX / 65535) * 360)
|
|
84
|
+
const satApprox = Math.round((colorY / 65535) * 100)
|
|
85
|
+
const [r, g, b] = hs2rgb(hueApprox, satApprox)
|
|
86
|
+
await this.sendSetColor(r, g, b)
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
private async handleSetHueSaturation(request: MatterRequests.MoveToHueAndSaturation): Promise<void> {
|
|
90
90
|
this.logInfo(`MoveToHueAndSaturation request: ${JSON.stringify(request)}`)
|
|
91
|
-
const { hue, saturation
|
|
91
|
+
const { hue, saturation } = request
|
|
92
92
|
const hueDegrees = Math.round((hue / 254) * 360)
|
|
93
93
|
const saturationPercent = Math.round((saturation / 254) * 100)
|
|
94
|
-
|
|
95
|
-
|
|
94
|
+
const [r, g, b] = hs2rgb(hueDegrees, saturationPercent)
|
|
95
|
+
await this.sendSetColor(r, g, b)
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
private async handleSetColorTemperature(request: MatterRequests.MoveToColorTemperature): Promise<void> {
|
|
99
99
|
this.logInfo(`MoveToColorTemperature request: ${JSON.stringify(request)}`)
|
|
100
|
-
const { colorTemperatureMireds
|
|
100
|
+
const { colorTemperatureMireds } = request
|
|
101
101
|
const kelvin = Math.round(1000000 / colorTemperatureMireds)
|
|
102
|
-
this.
|
|
103
|
-
// TODO: await myLightAPI.setColorTemperature(kelvin, transitionTime)
|
|
102
|
+
await this.sendSetColorTemperature(kelvin)
|
|
104
103
|
}
|
|
105
104
|
|
|
106
105
|
public updateOnOffState(isOn: boolean): void {
|
|
@@ -27,12 +27,8 @@ export class OnOffLightAccessory extends BaseMatterAccessory {
|
|
|
27
27
|
const clusters = opts?.clusters ?? { onOff: { onOff: true } }
|
|
28
28
|
const handlers = opts?.handlers ?? {
|
|
29
29
|
onOff: {
|
|
30
|
-
on: async () =>
|
|
31
|
-
|
|
32
|
-
},
|
|
33
|
-
off: async () => {
|
|
34
|
-
log.debug(`${displayName} off handler invoked (default no-op).`)
|
|
35
|
-
},
|
|
30
|
+
on: async () => this.handleOnCommand(),
|
|
31
|
+
off: async () => this.handleOffCommand(),
|
|
36
32
|
},
|
|
37
33
|
}
|
|
38
34
|
|
|
@@ -61,14 +57,10 @@ export class OnOffLightAccessory extends BaseMatterAccessory {
|
|
|
61
57
|
this.logInfo('turning on.')
|
|
62
58
|
|
|
63
59
|
try {
|
|
64
|
-
//
|
|
65
|
-
|
|
66
|
-
// await fetch('https://api.mydevice.com/light/on', { method: 'POST' })
|
|
67
|
-
// await fetch('http://192.168.1.50/api/light/on')
|
|
68
|
-
// mqttClient.publish('home/light/command', JSON.stringify({ state: 'ON' }))
|
|
69
|
-
// await myLightAPI.turnOn(this.context.deviceId)
|
|
60
|
+
// Delegate to the platform-provided helper (OpenAPI/BLE) when available
|
|
61
|
+
await this.sendOnCommand()
|
|
70
62
|
|
|
71
|
-
this.logInfo('physical device turned on.')
|
|
63
|
+
this.logInfo('physical device turned on (via platform helper).')
|
|
72
64
|
|
|
73
65
|
// State automatically updated by Homebridge after handler completes
|
|
74
66
|
} catch (error) {
|
|
@@ -84,10 +76,10 @@ export class OnOffLightAccessory extends BaseMatterAccessory {
|
|
|
84
76
|
this.logInfo('turning off.')
|
|
85
77
|
|
|
86
78
|
try {
|
|
87
|
-
//
|
|
88
|
-
|
|
79
|
+
// Delegate to the platform-provided helper (OpenAPI/BLE) when available
|
|
80
|
+
await this.sendOffCommand()
|
|
89
81
|
|
|
90
|
-
this.logInfo('physical device turned off.')
|
|
82
|
+
this.logInfo('physical device turned off (via platform helper).')
|
|
91
83
|
|
|
92
84
|
// State automatically updated by Homebridge after handler completes
|
|
93
85
|
} catch (error) {
|
|
@@ -13,13 +13,8 @@ export class OnOffOutletAccessory extends BaseMatterAccessory {
|
|
|
13
13
|
const clusters = opts?.clusters ?? { onOff: { onOff: false } }
|
|
14
14
|
const handlers = opts?.handlers ?? {
|
|
15
15
|
onOff: {
|
|
16
|
-
on: async () =>
|
|
17
|
-
|
|
18
|
-
log.debug(`${displayName} on handler invoked (default no-op).`)
|
|
19
|
-
},
|
|
20
|
-
off: async () => {
|
|
21
|
-
log.debug(`${displayName} off handler invoked (default no-op).`)
|
|
22
|
-
},
|
|
16
|
+
on: async () => this.handleOn(),
|
|
17
|
+
off: async () => this.handleOff(),
|
|
23
18
|
},
|
|
24
19
|
}
|
|
25
20
|
|
|
@@ -43,4 +38,14 @@ export class OnOffOutletAccessory extends BaseMatterAccessory {
|
|
|
43
38
|
public updateOnOffState(isOn: boolean): void {
|
|
44
39
|
this.updateState(this.api.matter.clusterNames.OnOff, { onOff: isOn })
|
|
45
40
|
}
|
|
41
|
+
|
|
42
|
+
private async handleOn(): Promise<void> {
|
|
43
|
+
this.logInfo('turning on.')
|
|
44
|
+
await this.sendOnCommand()
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
private async handleOff(): Promise<void> {
|
|
48
|
+
this.logInfo('turning off.')
|
|
49
|
+
await this.sendOffCommand()
|
|
50
|
+
}
|
|
46
51
|
}
|
|
@@ -37,12 +37,12 @@ export class OnOffSwitchAccessory extends BaseMatterAccessory {
|
|
|
37
37
|
|
|
38
38
|
private async handleOn(): Promise<void> {
|
|
39
39
|
this.logInfo('turning on.')
|
|
40
|
-
|
|
40
|
+
await this.sendOnCommand()
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
private async handleOff(): Promise<void> {
|
|
44
44
|
this.logInfo('turning off.')
|
|
45
|
-
|
|
45
|
+
await this.sendOffCommand()
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
public updateOnOffState(isOn: boolean): void {
|
package/src/index.ts
CHANGED
|
@@ -7,71 +7,11 @@ import type { API } from 'homebridge'
|
|
|
7
7
|
import { SwitchBotHAPPlatform } from './platform-hap.js'
|
|
8
8
|
import { SwitchBotMatterPlatform } from './platform-matter.js'
|
|
9
9
|
import { PLATFORM_NAME, PLUGIN_NAME } from './settings.js'
|
|
10
|
+
import { createPlatformProxy } from './utils.js'
|
|
10
11
|
|
|
11
12
|
// Register our platform with homebridge.
|
|
12
13
|
export default (api: API): void => {
|
|
13
|
-
//
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
// registering two platforms and possible conflicts.
|
|
17
|
-
// Compact helper to determine if Matter should be used.
|
|
18
|
-
const detectMatter = (apiObj: API): boolean => {
|
|
19
|
-
try {
|
|
20
|
-
const maybe = (apiObj as any).isMatterEnabled
|
|
21
|
-
if (typeof maybe === 'function') {
|
|
22
|
-
return Boolean(maybe.call(apiObj))
|
|
23
|
-
}
|
|
24
|
-
if (typeof maybe !== 'undefined') {
|
|
25
|
-
return Boolean(maybe)
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const server = (apiObj as any).server ?? (apiObj as any).homebridgeServer ?? (apiObj as any).homebridge_server
|
|
29
|
-
const serverMaybe = server?.isMatterEnabled
|
|
30
|
-
if (typeof serverMaybe === 'function') {
|
|
31
|
-
return Boolean(serverMaybe.call(server))
|
|
32
|
-
}
|
|
33
|
-
if (typeof server?.isMatterEnabled !== 'undefined') {
|
|
34
|
-
return Boolean(server.isMatterEnabled)
|
|
35
|
-
}
|
|
36
|
-
} catch (e) {
|
|
37
|
-
// ignore
|
|
38
|
-
}
|
|
39
|
-
return false
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
class SwitchBotPlatformProxy {
|
|
43
|
-
private delegate: any
|
|
44
|
-
|
|
45
|
-
constructor(public readonly log: any, public readonly config: any, public readonly api: API) {
|
|
46
|
-
const isMatter = detectMatter(this.api)
|
|
47
|
-
this.log.info?.(`Homebridge SwitchBot Plugin initializing in ${isMatter ? 'Matter' : 'HAP'} mode.`)
|
|
48
|
-
const PlatformCtor = isMatter ? SwitchBotMatterPlatform : SwitchBotHAPPlatform
|
|
49
|
-
this.delegate = new PlatformCtor(this.log, this.config, this.api)
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Homebridge lifecycle hooks we expect to be called — delegate if available.
|
|
53
|
-
configureAccessory(accessory: any): void {
|
|
54
|
-
if (typeof this.delegate.configureAccessory === 'function') {
|
|
55
|
-
return this.delegate.configureAccessory(accessory)
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
configureMatterAccessory?(accessory: any): void {
|
|
60
|
-
if (typeof this.delegate.configureMatterAccessory === 'function') {
|
|
61
|
-
return this.delegate.configureMatterAccessory(accessory)
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// Expose common properties via getters for external access
|
|
66
|
-
get accessories(): any {
|
|
67
|
-
return this.delegate?.accessories
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
get matterAccessories(): any {
|
|
71
|
-
return this.delegate?.matterAccessories
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Register the proxy class with Homebridge. Homebridge will call `new SwitchBotPlatformProxy(log, config, api)`
|
|
76
|
-
api.registerPlatform(PLUGIN_NAME, PLATFORM_NAME, SwitchBotPlatformProxy as any)
|
|
14
|
+
// Create and register a small proxy that selects the correct platform (HAP or Matter) at runtime.
|
|
15
|
+
const ProxyCtor = createPlatformProxy(SwitchBotHAPPlatform, SwitchBotMatterPlatform)
|
|
16
|
+
api.registerPlatform(PLUGIN_NAME, PLATFORM_NAME, ProxyCtor as any)
|
|
77
17
|
}
|
package/src/platform-matter.ts
CHANGED
|
@@ -250,7 +250,12 @@ export class SwitchBotMatterPlatform implements DynamicPlatformPlugin {
|
|
|
250
250
|
firmwareRevision: String(firmware),
|
|
251
251
|
hardwareRevision: '1.0.0',
|
|
252
252
|
deviceId: dev.deviceId,
|
|
253
|
-
|
|
253
|
+
// Inject handy platform-side helpers into the accessory `context` so Matter
|
|
254
|
+
// accessory classes can perform OpenAPI/BLE actions without reaching into
|
|
255
|
+
// the platform implementation directly.
|
|
256
|
+
context: {
|
|
257
|
+
deviceId: dev.deviceId,
|
|
258
|
+
},
|
|
254
259
|
}
|
|
255
260
|
|
|
256
261
|
// Small helper to wrap common OpenAPI on/off commands
|
|
@@ -494,6 +499,15 @@ export class SwitchBotMatterPlatform implements DynamicPlatformPlugin {
|
|
|
494
499
|
}
|
|
495
500
|
}
|
|
496
501
|
|
|
502
|
+
// Expose platform helpers to the accessory via context so accessory
|
|
503
|
+
// classes can call OpenAPI/BLE actions (sendOpenAPI/sendBLE) and know
|
|
504
|
+
// the effective connection type.
|
|
505
|
+
try {
|
|
506
|
+
;(baseOpts as any).context = Object.assign({}, (baseOpts as any).context, { sendOpenAPI, sendBLE, connectionType })
|
|
507
|
+
} catch (e: any) {
|
|
508
|
+
this.log.debug('Failed to attach platform helpers to baseOpts.context: %s', e?.message ?? e)
|
|
509
|
+
}
|
|
510
|
+
|
|
497
511
|
const opts = Object.assign({}, baseOpts, { handlers })
|
|
498
512
|
|
|
499
513
|
// Instantiate the device class and return its serialized accessory
|
|
@@ -732,9 +746,9 @@ export class SwitchBotMatterPlatform implements DynamicPlatformPlugin {
|
|
|
732
746
|
* Register all Matter accessories
|
|
733
747
|
*/
|
|
734
748
|
private async registerMatterAccessories() {
|
|
735
|
-
this.log.
|
|
749
|
+
this.log.debug('═'.repeat(80))
|
|
736
750
|
this.log.info('Homebridge Matter Plugin')
|
|
737
|
-
this.log.
|
|
751
|
+
this.log.debug('═'.repeat(80))
|
|
738
752
|
|
|
739
753
|
// Remove accessories that are disabled in config
|
|
740
754
|
await this.removeDisabledAccessories()
|
|
@@ -778,9 +792,9 @@ export class SwitchBotMatterPlatform implements DynamicPlatformPlugin {
|
|
|
778
792
|
await this.registerSection12Robotic()
|
|
779
793
|
await this.registerCustomDevices()
|
|
780
794
|
|
|
781
|
-
this.log.
|
|
782
|
-
this.log.
|
|
783
|
-
this.log.
|
|
795
|
+
this.log.debug('═'.repeat(80))
|
|
796
|
+
this.log.debug('Finished registering Matter accessories')
|
|
797
|
+
this.log.debug('═'.repeat(80))
|
|
784
798
|
}
|
|
785
799
|
|
|
786
800
|
/**
|
|
@@ -827,9 +841,9 @@ export class SwitchBotMatterPlatform implements DynamicPlatformPlugin {
|
|
|
827
841
|
* Section 4: Lighting Devices (Matter Spec § 4)
|
|
828
842
|
*/
|
|
829
843
|
private async registerSection4Lighting() {
|
|
830
|
-
this.log.
|
|
844
|
+
this.log.debug('═'.repeat(80))
|
|
831
845
|
this.log.info('Section 4: Lighting Devices (Matter Spec § 4)')
|
|
832
|
-
this.log.
|
|
846
|
+
this.log.debug('═'.repeat(80))
|
|
833
847
|
|
|
834
848
|
const accessories: Array<MatterAccessory<Record<string, unknown>>> = []
|
|
835
849
|
|
|
@@ -876,9 +890,9 @@ export class SwitchBotMatterPlatform implements DynamicPlatformPlugin {
|
|
|
876
890
|
* Section 5: Smart Plugs/Actuators (Matter Spec § 5)
|
|
877
891
|
*/
|
|
878
892
|
private async registerSection5SmartPlugs() {
|
|
879
|
-
this.log.
|
|
893
|
+
this.log.debug('═'.repeat(80))
|
|
880
894
|
this.log.info('Section 5: Smart Plugs/Actuators (Matter Spec § 5)')
|
|
881
|
-
this.log.
|
|
895
|
+
this.log.debug('═'.repeat(80))
|
|
882
896
|
|
|
883
897
|
const accessories: Array<MatterAccessory<Record<string, unknown>>> = []
|
|
884
898
|
|
|
@@ -901,9 +915,9 @@ export class SwitchBotMatterPlatform implements DynamicPlatformPlugin {
|
|
|
901
915
|
* Section 6: Switches & Controllers (Matter Spec § 6)
|
|
902
916
|
*/
|
|
903
917
|
private async registerSection6Switches() {
|
|
904
|
-
this.log.
|
|
918
|
+
this.log.debug('═'.repeat(80))
|
|
905
919
|
this.log.info('Section 6: Switches & Controllers (Matter Spec § 6)')
|
|
906
|
-
this.log.
|
|
920
|
+
this.log.debug('═'.repeat(80))
|
|
907
921
|
|
|
908
922
|
const accessories: Array<MatterAccessory<Record<string, unknown>>> = []
|
|
909
923
|
|
|
@@ -926,9 +940,9 @@ export class SwitchBotMatterPlatform implements DynamicPlatformPlugin {
|
|
|
926
940
|
* Section 7: Sensors (Matter Spec § 7)
|
|
927
941
|
*/
|
|
928
942
|
private async registerSection7Sensors() {
|
|
929
|
-
this.log.
|
|
943
|
+
this.log.debug('═'.repeat(80))
|
|
930
944
|
this.log.info('Section 7: Sensors (Matter Spec § 7)')
|
|
931
|
-
this.log.
|
|
945
|
+
this.log.debug('═'.repeat(80))
|
|
932
946
|
|
|
933
947
|
const accessories: Array<MatterAccessory<Record<string, unknown>>> = []
|
|
934
948
|
|
|
@@ -987,9 +1001,9 @@ export class SwitchBotMatterPlatform implements DynamicPlatformPlugin {
|
|
|
987
1001
|
* Section 8: Closure Devices (Matter Spec § 8)
|
|
988
1002
|
*/
|
|
989
1003
|
private async registerSection8Closure() {
|
|
990
|
-
this.log.
|
|
1004
|
+
this.log.debug('═'.repeat(80))
|
|
991
1005
|
this.log.info('Section 8: Closure Devices (Matter Spec § 8)')
|
|
992
|
-
this.log.
|
|
1006
|
+
this.log.debug('═'.repeat(80))
|
|
993
1007
|
|
|
994
1008
|
const accessories: Array<MatterAccessory<Record<string, unknown>>> = []
|
|
995
1009
|
|
|
@@ -1024,9 +1038,9 @@ export class SwitchBotMatterPlatform implements DynamicPlatformPlugin {
|
|
|
1024
1038
|
* Section 9: HVAC (Matter Spec § 9)
|
|
1025
1039
|
*/
|
|
1026
1040
|
private async registerSection9HVAC() {
|
|
1027
|
-
this.log.
|
|
1041
|
+
this.log.debug('═'.repeat(80))
|
|
1028
1042
|
this.log.info('Section 9: HVAC (Matter Spec § 9)')
|
|
1029
|
-
this.log.
|
|
1043
|
+
this.log.debug('═'.repeat(80))
|
|
1030
1044
|
|
|
1031
1045
|
const accessories: Array<MatterAccessory<Record<string, unknown>>> = []
|
|
1032
1046
|
|
|
@@ -1058,9 +1072,9 @@ export class SwitchBotMatterPlatform implements DynamicPlatformPlugin {
|
|
|
1058
1072
|
* Use those codes to pair the vacuum as a separate bridge in your Home app.
|
|
1059
1073
|
*/
|
|
1060
1074
|
private async registerSection12Robotic() {
|
|
1061
|
-
this.log.
|
|
1075
|
+
this.log.debug('═'.repeat(80))
|
|
1062
1076
|
this.log.info('Section 12: Robotic Devices (Matter Spec § 12)')
|
|
1063
|
-
this.log.
|
|
1077
|
+
this.log.debug('═'.repeat(80))
|
|
1064
1078
|
|
|
1065
1079
|
const accessories: Array<MatterAccessory<Record<string, unknown>>> = []
|
|
1066
1080
|
|
|
@@ -1087,9 +1101,9 @@ export class SwitchBotMatterPlatform implements DynamicPlatformPlugin {
|
|
|
1087
1101
|
* like managing multiple logical components within a single device.
|
|
1088
1102
|
*/
|
|
1089
1103
|
private async registerCustomDevices() {
|
|
1090
|
-
this.log.
|
|
1104
|
+
this.log.debug('═'.repeat(80))
|
|
1091
1105
|
this.log.info('Custom Devices')
|
|
1092
|
-
this.log.
|
|
1106
|
+
this.log.debug('═'.repeat(80))
|
|
1093
1107
|
|
|
1094
1108
|
const accessories: Array<MatterAccessory<Record<string, unknown>>> = []
|
|
1095
1109
|
|