@homebridge-plugins/homebridge-matter 0.1.3 → 0.1.4
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 +7 -0
- package/MATTER_API.md +1189 -182
- package/README.md +1 -1
- package/dist/devices/section-12-robotic/robotic-vacuum-cleaner.d.ts +9 -53
- package/dist/devices/section-12-robotic/robotic-vacuum-cleaner.js +12 -62
- package/dist/devices/section-12-robotic/robotic-vacuum-cleaner.js.map +1 -1
- package/dist/devices/section-4-lighting/color-temperature-light.d.ts +8 -1
- package/dist/devices/section-4-lighting/color-temperature-light.js +27 -23
- package/dist/devices/section-4-lighting/color-temperature-light.js.map +1 -1
- package/dist/devices/section-4-lighting/dimmable-light.d.ts +5 -22
- package/dist/devices/section-4-lighting/dimmable-light.js +40 -319
- package/dist/devices/section-4-lighting/dimmable-light.js.map +1 -1
- package/dist/devices/section-4-lighting/extended-color-light.d.ts +6 -25
- package/dist/devices/section-4-lighting/extended-color-light.js +33 -399
- package/dist/devices/section-4-lighting/extended-color-light.js.map +1 -1
- package/dist/devices/section-4-lighting/on-off-light.d.ts +8 -28
- package/dist/devices/section-4-lighting/on-off-light.js +64 -434
- package/dist/devices/section-4-lighting/on-off-light.js.map +1 -1
- package/dist/devices/section-5-smart-plugs/dimmable-plug-in-unit.d.ts +6 -0
- package/dist/devices/section-5-smart-plugs/dimmable-plug-in-unit.js +13 -3
- package/dist/devices/section-5-smart-plugs/dimmable-plug-in-unit.js.map +1 -1
- package/dist/devices/section-5-smart-plugs/on-off-plug-in-unit.d.ts +5 -0
- package/dist/devices/section-5-smart-plugs/on-off-plug-in-unit.js +9 -2
- package/dist/devices/section-5-smart-plugs/on-off-plug-in-unit.js.map +1 -1
- package/dist/devices/section-6-switches/on-off-light-switch.d.ts +5 -0
- package/dist/devices/section-6-switches/on-off-light-switch.js +19 -5
- package/dist/devices/section-6-switches/on-off-light-switch.js.map +1 -1
- package/dist/devices/section-7-sensors/contact-sensor.d.ts +5 -0
- package/dist/devices/section-7-sensors/contact-sensor.js +5 -0
- package/dist/devices/section-7-sensors/contact-sensor.js.map +1 -1
- package/dist/devices/section-7-sensors/humidity-sensor.d.ts +5 -0
- package/dist/devices/section-7-sensors/humidity-sensor.js +5 -0
- package/dist/devices/section-7-sensors/humidity-sensor.js.map +1 -1
- package/dist/devices/section-7-sensors/light-sensor.d.ts +5 -0
- package/dist/devices/section-7-sensors/light-sensor.js +5 -0
- package/dist/devices/section-7-sensors/light-sensor.js.map +1 -1
- package/dist/devices/section-7-sensors/occupancy-sensor.d.ts +6 -0
- package/dist/devices/section-7-sensors/occupancy-sensor.js +6 -0
- package/dist/devices/section-7-sensors/occupancy-sensor.js.map +1 -1
- package/dist/devices/section-7-sensors/smoke-co-alarm.d.ts +6 -0
- package/dist/devices/section-7-sensors/smoke-co-alarm.js +6 -0
- package/dist/devices/section-7-sensors/smoke-co-alarm.js.map +1 -1
- package/dist/devices/section-7-sensors/temperature-sensor.d.ts +5 -0
- package/dist/devices/section-7-sensors/temperature-sensor.js +5 -0
- package/dist/devices/section-7-sensors/temperature-sensor.js.map +1 -1
- package/dist/devices/section-7-sensors/water-leak-detector.d.ts +5 -0
- package/dist/devices/section-7-sensors/water-leak-detector.js +5 -0
- package/dist/devices/section-7-sensors/water-leak-detector.js.map +1 -1
- package/dist/devices/section-8-closure/door-lock.d.ts +6 -0
- package/dist/devices/section-8-closure/door-lock.js +12 -27
- package/dist/devices/section-8-closure/door-lock.js.map +1 -1
- package/dist/devices/section-8-closure/window-covering.d.ts +7 -0
- package/dist/devices/section-8-closure/window-covering.js +27 -43
- package/dist/devices/section-8-closure/window-covering.js.map +1 -1
- package/dist/devices/section-9-hvac/fan.d.ts +7 -0
- package/dist/devices/section-9-hvac/fan.js +17 -23
- package/dist/devices/section-9-hvac/fan.js.map +1 -1
- package/dist/devices/section-9-hvac/thermostat.d.ts +7 -0
- package/dist/devices/section-9-hvac/thermostat.js +21 -25
- package/dist/devices/section-9-hvac/thermostat.js.map +1 -1
- package/package.json +3 -3
|
@@ -1,36 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* On/Off Light Device (Matter Spec § 4.1)
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* A basic lighting device that can be switched on or off.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* For comprehensive documentation on Matter concepts, handlers, state management,
|
|
7
|
+
* and the two-way flow architecture, see:
|
|
8
|
+
* ../../../MATTER_API.md
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* 1. User taps in Home App
|
|
15
|
-
* 2. Matter command received by Homebridge
|
|
16
|
-
* 3. Your handler runs (on/off methods below)
|
|
17
|
-
* 4. You control your physical device (API call, MQTT, etc.)
|
|
18
|
-
* 5. Homebridge AUTOMATICALLY updates Matter state
|
|
19
|
-
* 6. All controllers (iPhone, iPad, etc.) are notified
|
|
20
|
-
* ✅ No manual state update needed!
|
|
21
|
-
*
|
|
22
|
-
* FLOW B: Physical Device → Home App (MANUAL - you must update state)
|
|
23
|
-
* ────────────────────────────────────────────────────────────────
|
|
24
|
-
* 1. User presses physical button on device (OR cloud app, automation, etc.)
|
|
25
|
-
* 2. Physical device changes state
|
|
26
|
-
* 3. ❌ Homebridge has NO IDEA this happened!
|
|
27
|
-
* 4. You MUST detect the change (via polling, MQTT, webhook, etc.)
|
|
28
|
-
* 5. You MUST call api.matter.updateAccessoryState() to update Matter
|
|
29
|
-
* 6. Then all controllers are notified
|
|
30
|
-
* ⚠️ If you don't do step 5, Home App shows wrong state!
|
|
31
|
-
*
|
|
32
|
-
* This example demonstrates BOTH flows in detail.
|
|
33
|
-
* ═══════════════════════════════════════════════════════════════════════════════
|
|
10
|
+
* This example demonstrates:
|
|
11
|
+
* - Basic OnOff cluster implementation
|
|
12
|
+
* - Handler setup for on/off commands (Flow A)
|
|
13
|
+
* - Event-based state monitoring (Flow B)
|
|
34
14
|
*/
|
|
35
15
|
export function registerOnOffLight(context) {
|
|
36
16
|
const { api, log, config } = context;
|
|
@@ -38,7 +18,6 @@ export function registerOnOffLight(context) {
|
|
|
38
18
|
if (!config.enableOnOffLight) {
|
|
39
19
|
return accessories;
|
|
40
20
|
}
|
|
41
|
-
// Create the accessory configuration
|
|
42
21
|
const accessory = {
|
|
43
22
|
uuid: api.matter.uuid.generate('matter-onoff-light'),
|
|
44
23
|
displayName: 'On/Off Light',
|
|
@@ -47,457 +26,108 @@ export function registerOnOffLight(context) {
|
|
|
47
26
|
manufacturer: 'Matter Examples',
|
|
48
27
|
model: 'OnOffLight v1',
|
|
49
28
|
// Optional: Store custom data that persists across restarts
|
|
50
|
-
// This works the same as PlatformAccessory.context for HAP accessories
|
|
51
|
-
// Use this to cache device state, API tokens, or any custom data
|
|
52
29
|
// context: {
|
|
53
30
|
// deviceId: 'my-light-123',
|
|
54
|
-
//
|
|
55
|
-
// apiToken: 'your-token-here'
|
|
31
|
+
// apiToken: 'your-token-here',
|
|
56
32
|
// },
|
|
57
|
-
//
|
|
33
|
+
// Initial state (persisted automatically after first creation)
|
|
58
34
|
clusters: {
|
|
59
35
|
onOff: {
|
|
60
|
-
onOff: true, // Initial state: light is on
|
|
36
|
+
onOff: true, // Initial state: light is on
|
|
61
37
|
},
|
|
62
38
|
},
|
|
63
|
-
//
|
|
64
|
-
//
|
|
65
|
-
//
|
|
66
|
-
// These handlers are called when users control the device via Home app
|
|
67
|
-
// After your handler runs, Homebridge AUTOMATICALLY updates the Matter state
|
|
39
|
+
// FLOW A: Home App → Physical Device
|
|
40
|
+
// Handlers are called when users control via Home app
|
|
41
|
+
// State updates automatically after handler completes
|
|
68
42
|
handlers: {
|
|
69
43
|
onOff: {
|
|
70
|
-
/**
|
|
71
|
-
* Called when user turns the light ON via Home app
|
|
72
|
-
*
|
|
73
|
-
* IMPORTANT: After this handler completes successfully, Homebridge
|
|
74
|
-
* AUTOMATICALLY calls super.on() which updates the Matter state to ON
|
|
75
|
-
* and notifies all connected devices. You don't need to manually update state!
|
|
76
|
-
*/
|
|
77
44
|
on: async () => {
|
|
78
|
-
log.info('[On/Off Light]
|
|
79
|
-
//
|
|
80
|
-
//
|
|
81
|
-
//
|
|
82
|
-
const currentState = accessory.clusters.onOff.onOff;
|
|
83
|
-
log.info(`[On/Off Light] Current Matter state: ${currentState ? 'ON' : 'OFF'}`);
|
|
84
|
-
// ─────────────────────────────────────────────────────────────────────
|
|
85
|
-
// YOUR JOB: Control your physical device
|
|
86
|
-
// ─────────────────────────────────────────────────────────────────────
|
|
87
|
-
// TODO: Replace this with your actual device control logic
|
|
88
|
-
//
|
|
89
|
-
// Example 1: Cloud API
|
|
90
|
-
// await fetch('https://api.mysmartlight.com/devices/light-001/on', {
|
|
91
|
-
// method: 'POST',
|
|
92
|
-
// headers: { 'Authorization': `Bearer ${accessory.context.apiToken}` }
|
|
93
|
-
// })
|
|
94
|
-
//
|
|
95
|
-
// Example 2: Local HTTP API
|
|
45
|
+
log.info('[On/Off Light] Turning ON');
|
|
46
|
+
// TODO: Control your physical device
|
|
47
|
+
// Examples:
|
|
48
|
+
// await fetch('https://api.mydevice.com/light/on', { method: 'POST' })
|
|
96
49
|
// await fetch('http://192.168.1.50/api/light/on')
|
|
97
|
-
//
|
|
98
|
-
// Example 3: MQTT
|
|
99
|
-
// mqttClient.publish('home/light-001/command', JSON.stringify({ state: 'ON' }))
|
|
100
|
-
//
|
|
101
|
-
// Example 4: Custom library
|
|
50
|
+
// mqttClient.publish('home/light/command', JSON.stringify({ state: 'ON' }))
|
|
102
51
|
// await myLightAPI.turnOn(accessory.context.deviceId)
|
|
103
52
|
log.info('[On/Off Light] Physical device turned ON');
|
|
104
|
-
//
|
|
105
|
-
// AUTOMATIC: Homebridge updates Matter state after this handler
|
|
106
|
-
// ─────────────────────────────────────────────────────────────────────
|
|
107
|
-
// You don't need to call api.matter.updateAccessoryState() here!
|
|
108
|
-
// Homebridge automatically:
|
|
109
|
-
// 1. Calls super.on() which sets this.state.onOff = true
|
|
110
|
-
// 2. Syncs to cache for persistence
|
|
111
|
-
// 3. Notifies all Matter controllers
|
|
53
|
+
// State automatically updated by Homebridge
|
|
112
54
|
},
|
|
113
|
-
/**
|
|
114
|
-
* Called when user turns the light OFF via Home app
|
|
115
|
-
*/
|
|
116
55
|
off: async () => {
|
|
117
|
-
log.info('[On/Off Light]
|
|
118
|
-
const currentState = accessory.clusters.onOff.onOff;
|
|
119
|
-
log.info(`[On/Off Light] Current Matter state: ${currentState ? 'ON' : 'OFF'}`);
|
|
56
|
+
log.info('[On/Off Light] Turning OFF');
|
|
120
57
|
// TODO: Control your physical device
|
|
121
|
-
//
|
|
58
|
+
// await myLightAPI.turnOff(accessory.context.deviceId)
|
|
122
59
|
log.info('[On/Off Light] Physical device turned OFF');
|
|
123
|
-
//
|
|
60
|
+
// State automatically updated by Homebridge
|
|
124
61
|
},
|
|
125
62
|
},
|
|
126
63
|
},
|
|
127
64
|
};
|
|
128
65
|
accessories.push(accessory);
|
|
129
|
-
//
|
|
130
|
-
// FLOW B:
|
|
131
|
-
//
|
|
132
|
-
//
|
|
133
|
-
//
|
|
134
|
-
//
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
// - Device state changes independently
|
|
139
|
-
//
|
|
140
|
-
// In these cases, Homebridge has NO IDEA the device changed!
|
|
141
|
-
// You MUST monitor your device and call api.matter.updateAccessoryState()
|
|
142
|
-
//
|
|
143
|
-
// WHY NO AUTOMATIC DETECTION?
|
|
144
|
-
// Your physical device is NOT a Matter device - it's a regular IoT device
|
|
145
|
-
// (cloud API, HTTP, MQTT, etc.). The virtual Matter device in Homebridge
|
|
146
|
-
// can't magically detect when the physical device changes. YOU must tell it!
|
|
147
|
-
//
|
|
148
|
-
// THE KEY API: api.matter.updateAccessoryState(uuid, cluster, attributes)
|
|
149
|
-
// This updates the Matter state AND notifies all connected controllers.
|
|
150
|
-
/**
|
|
151
|
-
* RECOMMENDED: Event-based updates (MQTT, WebSocket, webhooks, SSE)
|
|
152
|
-
*
|
|
153
|
-
* This is the BEST approach if your device supports push notifications.
|
|
154
|
-
* When your device changes, you get instant notification and update Matter.
|
|
155
|
-
*
|
|
156
|
-
* ⚡ Pros: Instant updates, efficient, the best user experience
|
|
157
|
-
* ⚠️ Requires: Device API that supports events/push notifications
|
|
158
|
-
*/
|
|
159
|
-
const startEventListenerExample = () => {
|
|
160
|
-
// ───────────────────────────────────────────────────────────────────────────
|
|
161
|
-
// Example A: MQTT listener
|
|
162
|
-
// ───────────────────────────────────────────────────────────────────────────
|
|
163
|
-
// import mqtt from 'mqtt'
|
|
164
|
-
// const mqttClient = mqtt.connect('mqtt://your-broker-url')
|
|
165
|
-
//
|
|
166
|
-
// mqttClient.subscribe('home/light-001/status') // Subscribe to status topic
|
|
66
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
67
|
+
// FLOW B: Physical Device → Home App
|
|
68
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
69
|
+
// Monitor your physical device and call updateAccessoryState() when it changes
|
|
70
|
+
// See MATTER_API.md "Monitoring External Changes" for detailed examples
|
|
71
|
+
// Example: Event-based monitoring (recommended)
|
|
72
|
+
const startEventListener = () => {
|
|
73
|
+
// MQTT Example:
|
|
74
|
+
// mqttClient.subscribe('home/light/status')
|
|
167
75
|
// mqttClient.on('message', (topic, message) => {
|
|
168
|
-
//
|
|
169
|
-
//
|
|
170
|
-
//
|
|
76
|
+
// const { state } = JSON.parse(message.toString())
|
|
77
|
+
// const deviceIsOn = state === 'ON'
|
|
78
|
+
// const currentState = accessory.clusters.onOff.onOff
|
|
171
79
|
//
|
|
172
|
-
//
|
|
173
|
-
//
|
|
174
|
-
//
|
|
175
|
-
//
|
|
176
|
-
//
|
|
177
|
-
//
|
|
178
|
-
//
|
|
179
|
-
// // ─────────────────────────────────────────────────────────────────
|
|
180
|
-
// api.matter.updateAccessoryState(
|
|
181
|
-
// accessory.uuid, // UUID of the accessory
|
|
182
|
-
// api.matter.clusterNames.OnOff, // Cluster name (use constants!)
|
|
183
|
-
// { onOff: deviceIsOn }, // New attribute values
|
|
184
|
-
// )
|
|
185
|
-
//
|
|
186
|
-
// // This updates the Matter state AND notifies all controllers
|
|
187
|
-
// log.info(`[On/Off Light] ✓ Matter state updated (Physical device → Home app)`)
|
|
188
|
-
// }
|
|
80
|
+
// if (deviceIsOn !== currentState) {
|
|
81
|
+
// api.matter.updateAccessoryState(
|
|
82
|
+
// accessory.uuid,
|
|
83
|
+
// api.matter.clusterNames.OnOff,
|
|
84
|
+
// { onOff: deviceIsOn }
|
|
85
|
+
// )
|
|
86
|
+
// log.info(`[On/Off Light] State synced: ${deviceIsOn ? 'ON' : 'OFF'}`)
|
|
189
87
|
// }
|
|
190
88
|
// })
|
|
191
|
-
//
|
|
192
|
-
//
|
|
193
|
-
// ───────────────────────────────────────────────────────────────────────────
|
|
194
|
-
// import WebSocket from 'ws'
|
|
195
|
-
// const ws = new WebSocket('wss://api.mysmartlight.com/devices/light-001/events')
|
|
196
|
-
//
|
|
89
|
+
// WebSocket Example:
|
|
90
|
+
// const ws = new WebSocket('wss://api.mydevice.com/events')
|
|
197
91
|
// ws.on('message', (data) => {
|
|
198
92
|
// const event = JSON.parse(data.toString())
|
|
199
93
|
// if (event.type === 'state_changed') {
|
|
200
94
|
// const deviceIsOn = event.state === 'ON'
|
|
201
|
-
// const
|
|
202
|
-
//
|
|
203
|
-
// if (deviceIsOn !== currentMatterState) {
|
|
204
|
-
// log.info(`[On/Off Light] Physical device changed (WebSocket): ${deviceIsOn ? 'ON' : 'OFF'}`)
|
|
95
|
+
// const currentState = accessory.clusters.onOff.onOff
|
|
205
96
|
//
|
|
97
|
+
// if (deviceIsOn !== currentState) {
|
|
206
98
|
// api.matter.updateAccessoryState(
|
|
207
99
|
// accessory.uuid,
|
|
208
100
|
// api.matter.clusterNames.OnOff,
|
|
209
|
-
// { onOff: deviceIsOn }
|
|
101
|
+
// { onOff: deviceIsOn }
|
|
210
102
|
// )
|
|
211
|
-
//
|
|
212
|
-
// log.info(`[On/Off Light] ✓ Matter state updated`)
|
|
213
103
|
// }
|
|
214
104
|
// }
|
|
215
105
|
// })
|
|
216
|
-
//
|
|
217
|
-
// // Handle reconnection on disconnect
|
|
218
|
-
// ws.on('close', () => {
|
|
219
|
-
// log.warn('[On/Off Light] WebSocket disconnected, reconnecting in 5s...')
|
|
220
|
-
// setTimeout(() => startEventListenerExample(), 5000)
|
|
221
|
-
// })
|
|
222
|
-
// ───────────────────────────────────────────────────────────────────────────
|
|
223
|
-
// Example C: HTTP Webhook listener (requires setting up an HTTP server)
|
|
224
|
-
// ───────────────────────────────────────────────────────────────────────────
|
|
225
|
-
// import express from 'express'
|
|
226
|
-
// const app = express()
|
|
227
|
-
// app.use(express.json())
|
|
228
|
-
//
|
|
229
|
-
// app.post('/webhook/light-001/state', (req, res) => {
|
|
230
|
-
// const deviceIsOn = req.body.state === 'ON'
|
|
231
|
-
// const currentMatterState = accessory.clusters.onOff.onOff
|
|
232
|
-
//
|
|
233
|
-
// if (deviceIsOn !== currentMatterState) {
|
|
234
|
-
// log.info(`[On/Off Light] Physical device changed (webhook): ${deviceIsOn ? 'ON' : 'OFF'}`)
|
|
235
|
-
//
|
|
236
|
-
// api.matter.updateAccessoryState(
|
|
237
|
-
// accessory.uuid,
|
|
238
|
-
// api.matter.clusterNames.OnOff,
|
|
239
|
-
// { onOff: deviceIsOn },
|
|
240
|
-
// )
|
|
241
|
-
//
|
|
242
|
-
// log.info(`[On/Off Light] ✓ Matter state updated`)
|
|
243
|
-
// }
|
|
244
|
-
// res.sendStatus(200)
|
|
245
|
-
// })
|
|
246
|
-
//
|
|
247
|
-
// app.listen(3000, () => log.info('[On/Off Light] Webhook server listening on port 3000'))
|
|
248
|
-
// ───────────────────────────────────────────────────────────────────────────
|
|
249
|
-
// Example D: Server-Sent Events (SSE)
|
|
250
|
-
// ───────────────────────────────────────────────────────────────────────────
|
|
251
|
-
// const EventSource = require('eventsource')
|
|
252
|
-
// const eventSource = new EventSource('https://api.mysmartlight.com/devices/light-001/events')
|
|
253
|
-
//
|
|
254
|
-
// eventSource.addEventListener('state', (event) => {
|
|
255
|
-
// const data = JSON.parse(event.data)
|
|
256
|
-
// const deviceIsOn = data.power === 'on'
|
|
257
|
-
// const currentMatterState = accessory.clusters.onOff.onOff
|
|
258
|
-
//
|
|
259
|
-
// if (deviceIsOn !== currentMatterState) {
|
|
260
|
-
// log.info(`[On/Off Light] Physical device changed (SSE): ${deviceIsOn ? 'ON' : 'OFF'}`)
|
|
261
|
-
//
|
|
262
|
-
// api.matter.updateAccessoryState(
|
|
263
|
-
// accessory.uuid,
|
|
264
|
-
// api.matter.clusterNames.OnOff,
|
|
265
|
-
// { onOff: deviceIsOn },
|
|
266
|
-
// )
|
|
267
|
-
//
|
|
268
|
-
// log.info(`[On/Off Light] ✓ Matter state updated`)
|
|
269
|
-
// }
|
|
270
|
-
// })
|
|
271
106
|
};
|
|
272
|
-
// Uncomment to enable event
|
|
273
|
-
//
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
*
|
|
277
|
-
* Use this approach ONLY if your device doesn't support push notifications.
|
|
278
|
-
* Polling is less efficient and has delays, but works with any REST API.
|
|
279
|
-
*
|
|
280
|
-
* 🐢 Pros: Works with any HTTP API
|
|
281
|
-
* ⚠️ Cons: Delayed updates, higher overhead, can strain APIs
|
|
282
|
-
*/
|
|
283
|
-
const startPollingExample = () => {
|
|
284
|
-
// Poll every 5-10 seconds (adjust based on your needs)
|
|
285
|
-
// WARNING: Frequent polling can strain your device's API and network
|
|
107
|
+
// Uncomment to enable event listener:
|
|
108
|
+
// startEventListener()
|
|
109
|
+
// Example: Polling-based monitoring (fallback)
|
|
110
|
+
const startPolling = () => {
|
|
286
111
|
setInterval(async () => {
|
|
287
112
|
try {
|
|
288
|
-
//
|
|
289
|
-
//
|
|
290
|
-
// ─────────────────────────────────────────────────────────────────────
|
|
291
|
-
// TODO: Replace with your actual device state fetching logic
|
|
292
|
-
//
|
|
293
|
-
// Example 1: REST API
|
|
294
|
-
// const response = await fetch('https://api.mysmartlight.com/devices/light-001/state')
|
|
113
|
+
// TODO: Fetch state from your physical device
|
|
114
|
+
// const response = await fetch('https://api.mydevice.com/light/state')
|
|
295
115
|
// const data = await response.json()
|
|
296
|
-
// const deviceIsOn = data.
|
|
297
|
-
//
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
// For this example, simulate fetching device state
|
|
304
|
-
// const deviceIsOn = true
|
|
305
|
-
// ─────────────────────────────────────────────────────────────────────
|
|
306
|
-
// STEP 2: Compare with current Matter state
|
|
307
|
-
// ─────────────────────────────────────────────────────────────────────
|
|
308
|
-
// const currentMatterState = accessory.clusters.onOff.onOff
|
|
309
|
-
//
|
|
310
|
-
// // Only update if the state actually changed (avoid unnecessary updates)
|
|
311
|
-
// if (deviceIsOn !== currentMatterState) {
|
|
312
|
-
// log.info(`[On/Off Light] Physical device changed (polling): ${deviceIsOn ? 'ON' : 'OFF'}`)
|
|
313
|
-
//
|
|
314
|
-
// // ───────────────────────────────────────────────────────────────────
|
|
315
|
-
// // STEP 3: Update Matter state
|
|
316
|
-
// // ───────────────────────────────────────────────────────────────────
|
|
317
|
-
// api.matter.updateAccessoryState(
|
|
318
|
-
// accessory.uuid,
|
|
319
|
-
// api.matter.clusterNames.OnOff,
|
|
320
|
-
// { onOff: deviceIsOn },
|
|
321
|
-
// )
|
|
322
|
-
//
|
|
323
|
-
// log.info(`[On/Off Light] ✓ Matter state updated (Physical device → Home app)`)
|
|
324
|
-
// }
|
|
116
|
+
// const deviceIsOn = data.power === 'on'
|
|
117
|
+
const deviceIsOn = true; // Replace with actual fetch
|
|
118
|
+
const currentState = accessory.clusters.onOff.onOff;
|
|
119
|
+
if (deviceIsOn !== currentState) {
|
|
120
|
+
api.matter.updateAccessoryState(accessory.uuid, api.matter.clusterNames.OnOff, { onOff: deviceIsOn });
|
|
121
|
+
log.info(`[On/Off Light] State synced: ${deviceIsOn ? 'ON' : 'OFF'}`);
|
|
122
|
+
}
|
|
325
123
|
}
|
|
326
124
|
catch (error) {
|
|
327
|
-
log.error(`[On/Off Light]
|
|
125
|
+
log.error(`[On/Off Light] Polling error: ${error}`);
|
|
328
126
|
}
|
|
329
|
-
}, 5000); // Poll every 5 seconds
|
|
127
|
+
}, 5000); // Poll every 5 seconds
|
|
330
128
|
};
|
|
331
|
-
// Uncomment to enable polling (
|
|
332
|
-
//
|
|
333
|
-
// ═════════════════════════════════════════════════════════════════════════════
|
|
334
|
-
// KEY TAKEAWAYS - THE TWO-WAY FLOW:
|
|
335
|
-
// ═════════════════════════════════════════════════════════════════════════════
|
|
336
|
-
//
|
|
337
|
-
// 1. TWO SEPARATE FLOWS:
|
|
338
|
-
// FLOW A (Home App → Physical Device):
|
|
339
|
-
// - Your handler runs when user controls via Home app
|
|
340
|
-
// - You control the physical device (API call, MQTT, etc.)
|
|
341
|
-
// - Homebridge AUTOMATICALLY updates Matter state after your handler
|
|
342
|
-
// - DO NOT call api.matter.updateAccessoryState() in handlers!
|
|
343
|
-
//
|
|
344
|
-
// FLOW B (Physical Device → Home App):
|
|
345
|
-
// - Physical device changes (button press, cloud app, etc.)
|
|
346
|
-
// - Homebridge has NO IDEA this happened!
|
|
347
|
-
// - You MUST monitor device (events/polling) and detect the change
|
|
348
|
-
// - You MUST call api.matter.updateAccessoryState() to update Matter
|
|
349
|
-
// - Then all controllers are notified
|
|
350
|
-
//
|
|
351
|
-
// 2. READING STATE:
|
|
352
|
-
// - Access via: accessory.clusters.onOff.onOff
|
|
353
|
-
// - This gives you the current Matter state
|
|
354
|
-
// - Use this to compare with physical device state before updating
|
|
355
|
-
//
|
|
356
|
-
// 3. UPDATING STATE (Physical Device → Home App):
|
|
357
|
-
// - Use the Homebridge API:
|
|
358
|
-
// api.matter.updateAccessoryState(
|
|
359
|
-
// accessory.uuid,
|
|
360
|
-
// api.matter.clusterNames.OnOff,
|
|
361
|
-
// { onOff: newValue }
|
|
362
|
-
// )
|
|
363
|
-
// - This updates Matter state AND notifies all controllers
|
|
364
|
-
// - ONLY use this for external changes (FLOW B), NOT in handlers (FLOW A)!
|
|
365
|
-
//
|
|
366
|
-
// 4. WHY NO AUTOMATIC DETECTION?
|
|
367
|
-
// - Your physical device is NOT a Matter device (it's HTTP, MQTT, cloud, etc.)
|
|
368
|
-
// - The virtual Matter device in Homebridge can't detect physical changes
|
|
369
|
-
// - You must explicitly monitor your device and call updateAccessoryState()
|
|
370
|
-
//
|
|
371
|
-
// 5. CHOOSING MONITORING METHOD:
|
|
372
|
-
// Event-based (RECOMMENDED): MQTT, WebSocket, webhooks, SSE
|
|
373
|
-
// ✅ Instant updates
|
|
374
|
-
// ✅ More efficient
|
|
375
|
-
// ✅ Better user experience
|
|
376
|
-
// ✅ Lower overhead
|
|
377
|
-
//
|
|
378
|
-
// Polling (FALLBACK): Only if device has no event support
|
|
379
|
-
// ⚠️ Delayed updates (depends on interval)
|
|
380
|
-
// ⚠️ Higher network overhead
|
|
381
|
-
// ⚠️ Can strain device APIs
|
|
382
|
-
// ⚠️ Use 5-10 second intervals minimum
|
|
383
|
-
//
|
|
384
|
-
// 6. BEST PRACTICES:
|
|
385
|
-
// - Always compare states before calling updateAccessoryState() (avoid unnecessary updates)
|
|
386
|
-
// - Use events whenever possible for better performance
|
|
387
|
-
// - Handle errors gracefully in monitoring logic
|
|
388
|
-
// - Store connection objects (MQTT, WebSocket) for cleanup on plugin shutdown
|
|
389
|
-
// - Reconnect automatically if event connection drops
|
|
390
|
-
// - Log clearly: "Physical device → Home app" vs "Home app → Physical device"
|
|
391
|
-
//
|
|
392
|
-
// 7. DISCOVERING CLUSTER ATTRIBUTES & TYPES:
|
|
393
|
-
// Q: "How do I know what attributes are available for my device type?"
|
|
394
|
-
// A: Three ways to discover this:
|
|
395
|
-
//
|
|
396
|
-
// METHOD 1: TypeScript Autocomplete (EASIEST for simple types)
|
|
397
|
-
// When you type `accessory.clusters.`, TypeScript will show available clusters.
|
|
398
|
-
// When you type `onOff: `, TypeScript knows the type (boolean).
|
|
399
|
-
// This works well for simple types like booleans and numbers.
|
|
400
|
-
//
|
|
401
|
-
// METHOD 2: Programmatic Discovery (for attribute names)
|
|
402
|
-
// All clusters are available via api.matter.clusters:
|
|
403
|
-
// ```typescript
|
|
404
|
-
// // See all OnOff attributes
|
|
405
|
-
// const onOffAttrs = api.matter.clusters.OnOffCluster.attributes
|
|
406
|
-
// console.log(Object.keys(onOffAttrs))
|
|
407
|
-
// // Shows: ['onOff', 'clusterRevision', 'featureMap', ...]
|
|
408
|
-
//
|
|
409
|
-
// // See all FanControl attributes
|
|
410
|
-
// const fanAttrs = api.matter.clusters.FanControlCluster.attributes
|
|
411
|
-
// console.log(Object.keys(fanAttrs))
|
|
412
|
-
// // Shows: ['fanMode', 'fanModeSequence', 'percentSetting', 'percentCurrent', ...]
|
|
413
|
-
// ```
|
|
414
|
-
//
|
|
415
|
-
// METHOD 3: api.matter.types Namespace (BEST - for enum values with type safety!)
|
|
416
|
-
// Homebridge exports ALL Matter.js cluster types via the api.matter.types namespace.
|
|
417
|
-
// This gives you full access to enums, types, and constants with TypeScript autocomplete!
|
|
418
|
-
//
|
|
419
|
-
// EXAMPLE: Fan Speed Values (using api.matter.types)
|
|
420
|
-
// For a fan, you might wonder: "What values can fanMode accept?"
|
|
421
|
-
// ```typescript
|
|
422
|
-
// // Access FanMode enum with full type safety and autocomplete (no import needed!)
|
|
423
|
-
// api.matter.updateAccessoryState(
|
|
424
|
-
// fanUuid,
|
|
425
|
-
// api.matter.clusterNames.FanControl,
|
|
426
|
-
// { fanMode: api.matter.types.FanControl.FanMode.High }
|
|
427
|
-
// )
|
|
428
|
-
//
|
|
429
|
-
// // All FanMode values available:
|
|
430
|
-
// // - api.matter.types.FanControl.FanMode.Off (0)
|
|
431
|
-
// // - api.matter.types.FanControl.FanMode.Low (1)
|
|
432
|
-
// // - api.matter.types.FanControl.FanMode.Medium (2)
|
|
433
|
-
// // - api.matter.types.FanControl.FanMode.High (3)
|
|
434
|
-
// // - api.matter.types.FanControl.FanMode.On (4)
|
|
435
|
-
// // - api.matter.types.FanControl.FanMode.Auto (5)
|
|
436
|
-
// // - api.matter.types.FanControl.FanMode.Smart (6)
|
|
437
|
-
// ```
|
|
438
|
-
//
|
|
439
|
-
// EXAMPLE: Thermostat System Mode (using api.matter.types)
|
|
440
|
-
// ```typescript
|
|
441
|
-
// api.matter.updateAccessoryState(
|
|
442
|
-
// thermostatUuid,
|
|
443
|
-
// api.matter.clusterNames.Thermostat,
|
|
444
|
-
// {
|
|
445
|
-
// localTemperature: 2200, // 22.00°C (hundredths)
|
|
446
|
-
// occupiedHeatingSetpoint: 2000, // 20.00°C
|
|
447
|
-
// occupiedCoolingSetpoint: 2400, // 24.00°C
|
|
448
|
-
// systemMode: api.matter.types.Thermostat.SystemMode.Heat,
|
|
449
|
-
// }
|
|
450
|
-
// )
|
|
451
|
-
//
|
|
452
|
-
// // All SystemMode values available:
|
|
453
|
-
// // - api.matter.types.Thermostat.SystemMode.Off (0)
|
|
454
|
-
// // - api.matter.types.Thermostat.SystemMode.Auto (1)
|
|
455
|
-
// // - api.matter.types.Thermostat.SystemMode.Cool (3)
|
|
456
|
-
// // - api.matter.types.Thermostat.SystemMode.Heat (4)
|
|
457
|
-
// // - api.matter.types.Thermostat.SystemMode.EmergencyHeat (5)
|
|
458
|
-
// // - api.matter.types.Thermostat.SystemMode.Precooling (6)
|
|
459
|
-
// // - api.matter.types.Thermostat.SystemMode.FanOnly (7)
|
|
460
|
-
// ```
|
|
461
|
-
//
|
|
462
|
-
// EXAMPLE: Door Lock State (using api.matter.types)
|
|
463
|
-
// ```typescript
|
|
464
|
-
// api.matter.updateAccessoryState(
|
|
465
|
-
// lockUuid,
|
|
466
|
-
// api.matter.clusterNames.DoorLock,
|
|
467
|
-
// { lockState: api.matter.types.DoorLock.LockState.Locked }
|
|
468
|
-
// )
|
|
469
|
-
//
|
|
470
|
-
// // All LockState values available:
|
|
471
|
-
// // - api.matter.types.DoorLock.LockState.NotFullyLocked (0)
|
|
472
|
-
// // - api.matter.types.DoorLock.LockState.Locked (1)
|
|
473
|
-
// // - api.matter.types.DoorLock.LockState.Unlocked (2)
|
|
474
|
-
// ```
|
|
475
|
-
//
|
|
476
|
-
// DISCOVERING ENUM VALUES:
|
|
477
|
-
// Three ways to find available enum values:
|
|
478
|
-
// 1. TypeScript Autocomplete: Type `api.matter.types.FanControl.` and see suggestions
|
|
479
|
-
// 2. Matter.js Reference: https://github.com/project-chip/matter.js
|
|
480
|
-
// 3. Matter Specification: https://csa-iot.org/developer-resource/specifications-download-request/
|
|
481
|
-
//
|
|
482
|
-
// ALL AVAILABLE CLUSTERS IN api.matter.types:
|
|
483
|
-
// api.matter.types exports ALL 130+ Matter clusters using original matter.js names:
|
|
484
|
-
// - api.matter.types.FanControl
|
|
485
|
-
// - api.matter.types.Thermostat
|
|
486
|
-
// - api.matter.types.DoorLock
|
|
487
|
-
// - api.matter.types.ColorControl
|
|
488
|
-
// - api.matter.types.WindowCovering
|
|
489
|
-
// - api.matter.types.SmokeCoAlarm
|
|
490
|
-
// - api.matter.types.OccupancySensing
|
|
491
|
-
// - api.matter.types.TemperatureMeasurement
|
|
492
|
-
// - ... and 120+ more!
|
|
493
|
-
//
|
|
494
|
-
// COMMON ATTRIBUTE VALUE TYPES:
|
|
495
|
-
// - Boolean: true/false (e.g., onOff, occupied)
|
|
496
|
-
// - Uint8/Uint16: 0-254, 0-100, 0-65535 (e.g., currentLevel, hue, saturation)
|
|
497
|
-
// - Enum: Use api.matter.types for type-safe enum values (see examples above)
|
|
498
|
-
// - Temperature: Usually in hundredths of degrees C (2500 = 25.00°C)
|
|
499
|
-
// - Percentage: Usually 0-100 for sensors, 0-254 for controls (Matter range)
|
|
500
|
-
// ═════════════════════════════════════════════════════════════════════════════
|
|
129
|
+
// Uncomment to enable polling (use events if possible!):
|
|
130
|
+
// startPolling()
|
|
501
131
|
return accessories;
|
|
502
132
|
}
|
|
503
133
|
//# sourceMappingURL=on-off-light.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"on-off-light.js","sourceRoot":"","sources":["../../../src/devices/section-4-lighting/on-off-light.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"on-off-light.js","sourceRoot":"","sources":["../../../src/devices/section-4-lighting/on-off-light.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
* Dimmable Plug-in Unit Device (Matter Spec § 5.2)
|
|
3
3
|
*
|
|
4
4
|
* A plug-in unit with on/off and level control.
|
|
5
|
+
*
|
|
6
|
+
* For comprehensive documentation, see: ../../../MATTER_API.md
|
|
7
|
+
*
|
|
8
|
+
* This example demonstrates:
|
|
9
|
+
* - Multiple clusters (OnOff + LevelControl) for outlets
|
|
10
|
+
* - Type-safe handlers with MatterRequests
|
|
5
11
|
*/
|
|
6
12
|
import type { DeviceContext } from '../types.js';
|
|
7
13
|
export declare function registerDimmablePlugInUnit(context: DeviceContext): any[];
|