@homebridge-plugins/homebridge-matter 0.2.0 → 0.2.1-beta.1
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/MATTER_API.md
DELETED
|
@@ -1,3869 +0,0 @@
|
|
|
1
|
-
# Homebridge Matter Integration API
|
|
2
|
-
|
|
3
|
-
This document serves as a comprehensive guide for integrating Matter devices into Homebridge plugins. It covers the core concepts, patterns, and device-specific implementations.
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## Table of Contents
|
|
8
|
-
|
|
9
|
-
1. [Core Concepts](#core-concepts)
|
|
10
|
-
2. [The Two-Way Flow Architecture](#the-two-way-flow-architecture)
|
|
11
|
-
3. [Implementing Matter Devices](#implementing-matter-devices)
|
|
12
|
-
4. [Reading and Updating State](#reading-and-updating-state)
|
|
13
|
-
5. [Monitoring External Changes](#monitoring-external-changes)
|
|
14
|
-
6. [Using Matter Types](#using-matter-types)
|
|
15
|
-
7. [Best Practices](#best-practices)
|
|
16
|
-
8. [API Reference](#api-reference)
|
|
17
|
-
9. [Device Reference](#device-reference)
|
|
18
|
-
|
|
19
|
-
---
|
|
20
|
-
|
|
21
|
-
## Core Concepts
|
|
22
|
-
|
|
23
|
-
### Matter Device Architecture
|
|
24
|
-
|
|
25
|
-
Matter devices in Homebridge are **virtual devices** that bridge your physical IoT devices (cloud API, HTTP, MQTT, etc.) to the Matter protocol. Understanding this separation is crucial:
|
|
26
|
-
|
|
27
|
-
- **Physical Device**: Your actual IoT device (controlled via API, MQTT, HTTP, etc.)
|
|
28
|
-
- **Virtual Matter Device**: A Matter representation in Homebridge that Home app communicates with
|
|
29
|
-
- **Your Plugin**: Acts as the bridge between the two, translating commands and synchronizing state
|
|
30
|
-
|
|
31
|
-
### Coming from HAP? Quick Reference
|
|
32
|
-
|
|
33
|
-
**If you're familiar with HAP (HomeKit Accessory Protocol), here's the essential translation:**
|
|
34
|
-
|
|
35
|
-
| HAP Concept | Matter Equivalent | Quick Description |
|
|
36
|
-
|--------------------|-------------------|------------------------------------------------|
|
|
37
|
-
| **Accessory** | **Endpoint** | The device (one tile in Home app) |
|
|
38
|
-
| **Service** | **Cluster** | A capability (Lightbulb, Switch, etc.) |
|
|
39
|
-
| **Characteristic** | **Attribute** | A property (On, Brightness, Temperature, etc.) |
|
|
40
|
-
|
|
41
|
-
**Key difference**: Matter uses a **declarative configuration** approach (define everything upfront) instead of HAP's object-oriented approach (add services, set characteristics). Both protocols automatically update state after handlers complete successfully.
|
|
42
|
-
|
|
43
|
-
### Clusters (HAP: Services)
|
|
44
|
-
|
|
45
|
-
**Clusters** are the building blocks of Matter devices, equivalent to **Services** in HAP. They define functionality and state:
|
|
46
|
-
|
|
47
|
-
- **OnOff Cluster** *(HAP: Switch/Lightbulb Service)*: Controls power state (on/off)
|
|
48
|
-
- **LevelControl Cluster** *(HAP: Brightness Characteristic)*: Controls brightness/level (1-254)
|
|
49
|
-
- **ColorControl Cluster** *(HAP: Hue/Saturation Characteristics)*: Controls color (hue/saturation/temperature)
|
|
50
|
-
- And many more...
|
|
51
|
-
|
|
52
|
-
Each cluster has:
|
|
53
|
-
- **Attributes** *(HAP: Characteristics)*: State values (e.g., `onOff: true`, `currentLevel: 127`)
|
|
54
|
-
- **Commands** *(HAP: Characteristic setters)*: Actions that can be invoked (e.g., `on()`, `off()`, `moveToLevel()`)
|
|
55
|
-
|
|
56
|
-
#### Understanding Commands (vs HAP Characteristic Setters)
|
|
57
|
-
|
|
58
|
-
**Commands** are a new concept if you're coming from HAP. Here's how they compare:
|
|
59
|
-
|
|
60
|
-
**In HAP:**
|
|
61
|
-
```typescript
|
|
62
|
-
// You set a characteristic value, which triggers onSet
|
|
63
|
-
service.getCharacteristic(Characteristic.On)
|
|
64
|
-
.onSet(async (value: boolean) => {
|
|
65
|
-
// value is true or false
|
|
66
|
-
await device.setPower(value)
|
|
67
|
-
})
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
**In Matter:**
|
|
71
|
-
```typescript
|
|
72
|
-
// Separate commands are invoked for different actions
|
|
73
|
-
handlers: {
|
|
74
|
-
onOff: {
|
|
75
|
-
on: async () => {
|
|
76
|
-
// Explicit "on" command was invoked
|
|
77
|
-
await device.turnOn()
|
|
78
|
-
},
|
|
79
|
-
off: async () => {
|
|
80
|
-
// Explicit "off" command was invoked
|
|
81
|
-
await device.turnOff()
|
|
82
|
-
},
|
|
83
|
-
},
|
|
84
|
-
}
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
**Key differences:**
|
|
88
|
-
|
|
89
|
-
1. **Action-oriented vs Value-oriented**
|
|
90
|
-
- HAP: You set a value (`true`/`false`) and interpret what to do
|
|
91
|
-
- Matter: Specific commands (`on()`, `off()`) with clear intent
|
|
92
|
-
|
|
93
|
-
2. **Command Parameters**
|
|
94
|
-
- Simple commands like `on()` and `off()` take no parameters
|
|
95
|
-
- Complex commands like `moveToLevelWithOnOff()` receive parameters:
|
|
96
|
-
```typescript
|
|
97
|
-
handlers: {
|
|
98
|
-
levelControl: {
|
|
99
|
-
moveToLevelWithOnOff: async (request) => {
|
|
100
|
-
const level = request.level // 1-254
|
|
101
|
-
const transitionTime = request.transitionTime // Optional
|
|
102
|
-
await device.setBrightness(level)
|
|
103
|
-
},
|
|
104
|
-
},
|
|
105
|
-
}
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
3. **Handler Mapping**
|
|
109
|
-
- Each command maps to a handler function in your `handlers` object
|
|
110
|
-
- Command names are predefined by the Matter spec (not arbitrary)
|
|
111
|
-
- Common commands:
|
|
112
|
-
- **OnOff**: `on()`, `off()`, `toggle()`
|
|
113
|
-
- **LevelControl**: `moveToLevel()`, `moveToLevelWithOnOff()`, `step()`, `stop()`
|
|
114
|
-
- **ColorControl**: `moveToHue()`, `moveToSaturation()`, `moveToHueAndSaturation()`
|
|
115
|
-
- **DoorLock**: `lockDoor()`, `unlockDoor()`
|
|
116
|
-
|
|
117
|
-
**Practical example - Brightness:**
|
|
118
|
-
|
|
119
|
-
```typescript
|
|
120
|
-
// HAP approach
|
|
121
|
-
service.getCharacteristic(Characteristic.Brightness)
|
|
122
|
-
.onSet(async (value: number) => {
|
|
123
|
-
// You receive the target brightness value
|
|
124
|
-
await device.setBrightness(value)
|
|
125
|
-
})
|
|
126
|
-
|
|
127
|
-
// Matter approach
|
|
128
|
-
handlers: {
|
|
129
|
-
levelControl: {
|
|
130
|
-
moveToLevelWithOnOff: async (request) => {
|
|
131
|
-
// Command with parameters
|
|
132
|
-
const targetLevel = request.level // 1-254
|
|
133
|
-
await device.setBrightness(targetLevel)
|
|
134
|
-
},
|
|
135
|
-
},
|
|
136
|
-
}
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
The command-based approach is more explicit about the user's intent and can provide additional context (like transition time for smooth dimming).
|
|
140
|
-
|
|
141
|
-
#### Type-Safe Handler Arguments with MatterRequests
|
|
142
|
-
|
|
143
|
-
For commands with parameters, import `MatterRequests` for TypeScript autocomplete and type checking:
|
|
144
|
-
|
|
145
|
-
```typescript
|
|
146
|
-
import type { MatterRequests } from 'homebridge'
|
|
147
|
-
|
|
148
|
-
handlers: {
|
|
149
|
-
levelControl: {
|
|
150
|
-
moveToLevelWithOnOff: async (request: MatterRequests.MoveToLevel) => {
|
|
151
|
-
const { level, transitionTime } = request // Fully typed!
|
|
152
|
-
await device.setBrightness(level)
|
|
153
|
-
},
|
|
154
|
-
},
|
|
155
|
-
}
|
|
156
|
-
```
|
|
157
|
-
|
|
158
|
-
**Quick Examples:**
|
|
159
|
-
- `MatterRequests.MoveToLevel` - Brightness control
|
|
160
|
-
- `MatterRequests.MoveToHueAndSaturation` - Color control
|
|
161
|
-
- `MatterRequests.LockDoor` - Door lock with optional PIN
|
|
162
|
-
- `MatterRequests.SetpointRaiseLower` - Thermostat temperature
|
|
163
|
-
|
|
164
|
-
**Why use this?**
|
|
165
|
-
✅ Autocomplete shows available properties
|
|
166
|
-
✅ Compile-time errors for typos
|
|
167
|
-
✅ Hover to see parameter types
|
|
168
|
-
|
|
169
|
-
**Note:** TypeScript types only - not available on `api.matter` at runtime.
|
|
170
|
-
|
|
171
|
-
<details>
|
|
172
|
-
<summary><strong>Click to see all MatterRequests types</strong></summary>
|
|
173
|
-
|
|
174
|
-
#### Level Control
|
|
175
|
-
```typescript
|
|
176
|
-
MatterRequests.MoveToLevel // { level, transitionTime?, optionsMask?, optionsOverride? }
|
|
177
|
-
MatterRequests.Move // { moveMode, rate?, optionsMask?, optionsOverride? }
|
|
178
|
-
MatterRequests.Step // { stepMode, stepSize, transitionTime?, ... }
|
|
179
|
-
MatterRequests.Stop // { optionsMask?, optionsOverride? }
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
#### Color Control
|
|
183
|
-
```typescript
|
|
184
|
-
MatterRequests.MoveToHue // { hue, direction, transitionTime?, ... }
|
|
185
|
-
MatterRequests.MoveToSaturation // { saturation, transitionTime?, ... }
|
|
186
|
-
MatterRequests.MoveToHueAndSaturation // { hue, saturation, transitionTime?, ... }
|
|
187
|
-
MatterRequests.MoveToColorTemperature // { colorTemperatureMireds, transitionTime?, ... }
|
|
188
|
-
MatterRequests.MoveHue // { moveMode, rate?, ... }
|
|
189
|
-
MatterRequests.MoveSaturation // { moveMode, rate?, ... }
|
|
190
|
-
MatterRequests.MoveColorTemperature // { moveMode, rate?, ... }
|
|
191
|
-
MatterRequests.StepHue // { stepMode, stepSize, transitionTime?, ... }
|
|
192
|
-
MatterRequests.StepSaturation // { stepMode, stepSize, transitionTime?, ... }
|
|
193
|
-
MatterRequests.StepColorTemperature // { stepMode, stepSize, transitionTime?, ... }
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
#### Door Lock
|
|
197
|
-
```typescript
|
|
198
|
-
MatterRequests.LockDoor // { pinCode? }
|
|
199
|
-
MatterRequests.UnlockDoor // { pinCode? }
|
|
200
|
-
```
|
|
201
|
-
|
|
202
|
-
#### Window Covering
|
|
203
|
-
```typescript
|
|
204
|
-
MatterRequests.GoToLiftPercentage // { liftPercent100thsValue }
|
|
205
|
-
MatterRequests.GoToTiltPercentage // { tiltPercent100thsValue }
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
#### Thermostat
|
|
209
|
-
```typescript
|
|
210
|
-
MatterRequests.SetpointRaiseLower // { mode, amount }
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
#### Fan Control
|
|
214
|
-
```typescript
|
|
215
|
-
MatterRequests.FanStep // { direction, wrap?, lowestOff? }
|
|
216
|
-
```
|
|
217
|
-
|
|
218
|
-
</details>
|
|
219
|
-
|
|
220
|
-
### Endpoints vs Clusters (HAP: Accessories vs Services)
|
|
221
|
-
|
|
222
|
-
Understanding the relationship between endpoints and clusters is fundamental to Matter architecture.
|
|
223
|
-
|
|
224
|
-
#### The Hierarchy
|
|
225
|
-
|
|
226
|
-
```
|
|
227
|
-
Matter Node (Homebridge instance)
|
|
228
|
-
└── Endpoint 1 (e.g., "Living Room Light")
|
|
229
|
-
├── Cluster: OnOff
|
|
230
|
-
├── Cluster: LevelControl
|
|
231
|
-
└── Cluster: ColorControl
|
|
232
|
-
└── Endpoint 2 (e.g., "Bedroom Light")
|
|
233
|
-
├── Cluster: OnOff
|
|
234
|
-
└── Cluster: LevelControl
|
|
235
|
-
└── Endpoint 3 (e.g., "Temperature Sensor")
|
|
236
|
-
└── Cluster: TemperatureMeasurement
|
|
237
|
-
```
|
|
238
|
-
|
|
239
|
-
#### Endpoints
|
|
240
|
-
|
|
241
|
-
**Endpoints** are individual addressable "devices" or "sub-devices" within a Matter node.
|
|
242
|
-
|
|
243
|
-
- Each endpoint represents **one accessory** that appears in Home app
|
|
244
|
-
- Each endpoint has a **device type** (OnOffLight, DimmableLight, TemperatureSensor, etc.)
|
|
245
|
-
- Endpoints are numbered (Endpoint 0 is reserved for root/node-level, Endpoint 1+ are your devices)
|
|
246
|
-
- Each endpoint appears as a **separate tile** in Home app
|
|
247
|
-
|
|
248
|
-
**Examples**:
|
|
249
|
-
- A single smart bulb = 1 endpoint
|
|
250
|
-
- A 3-gang light switch = 3 endpoints (one per switch)
|
|
251
|
-
- A combo device (light + motion sensor) = 2 endpoints
|
|
252
|
-
|
|
253
|
-
#### Clusters
|
|
254
|
-
|
|
255
|
-
**Clusters** are the functional building blocks **within** an endpoint.
|
|
256
|
-
|
|
257
|
-
- Each cluster provides a **specific capability** (power control, brightness, temperature sensing, etc.)
|
|
258
|
-
- Clusters contain **attributes** (state values) and **commands** (actions)
|
|
259
|
-
- Multiple clusters combine to create the full functionality of an endpoint
|
|
260
|
-
- Clusters are reusable across different device types
|
|
261
|
-
|
|
262
|
-
**Examples**:
|
|
263
|
-
- **OnOff cluster**: Provides on/off capability
|
|
264
|
-
- **LevelControl cluster**: Provides brightness/level control
|
|
265
|
-
- **TemperatureMeasurement cluster**: Provides temperature reading
|
|
266
|
-
|
|
267
|
-
#### In Practice
|
|
268
|
-
|
|
269
|
-
When you create a Matter accessory in Homebridge:
|
|
270
|
-
|
|
271
|
-
```typescript
|
|
272
|
-
const accessory = {
|
|
273
|
-
// This creates an ENDPOINT
|
|
274
|
-
uuid: api.matter.uuid.generate('my-light'),
|
|
275
|
-
displayName: 'Living Room Light',
|
|
276
|
-
deviceType: api.matter.deviceTypes.DimmableLight,
|
|
277
|
-
|
|
278
|
-
// These are CLUSTERS within the endpoint
|
|
279
|
-
clusters: {
|
|
280
|
-
onOff: { onOff: false }, // OnOff cluster
|
|
281
|
-
levelControl: { currentLevel: 127 }, // LevelControl cluster
|
|
282
|
-
},
|
|
283
|
-
}
|
|
284
|
-
```
|
|
285
|
-
|
|
286
|
-
**Key Points**:
|
|
287
|
-
- **Each accessory you create = 1 endpoint**
|
|
288
|
-
- The `clusters` object defines which clusters (capabilities) that endpoint has
|
|
289
|
-
- The device type determines which clusters are required/optional for that endpoint
|
|
290
|
-
|
|
291
|
-
#### HAP vs Matter: Detailed Comparison
|
|
292
|
-
|
|
293
|
-
For HAP developers, here's how common patterns translate:
|
|
294
|
-
|
|
295
|
-
| Aspect | HAP | Matter |
|
|
296
|
-
|---------------------------|-------------------------------------------------------|----------------------------------------------------------|
|
|
297
|
-
| **Structure** | Object-oriented (classes, methods) | Configuration-based (objects) |
|
|
298
|
-
| **Services/Clusters** | Added dynamically with `addService()` | Defined upfront in `clusters` object |
|
|
299
|
-
| **Handlers** | Registered per-characteristic (`onSet`, `onGet`) | Grouped by cluster in `handlers` object |
|
|
300
|
-
| **State Updates** | `updateCharacteristic()` per characteristic | `updateAccessoryState()` per cluster |
|
|
301
|
-
| **Reading State** | Via getter methods or cached values | Direct property access: `accessory.clusters.onOff.onOff` |
|
|
302
|
-
| **Multiple Capabilities** | Multiple services on one accessory | Multiple clusters in one endpoint |
|
|
303
|
-
| **Automatic Updates** | Automatic after handlers, manual for external changes | Automatic after handlers, manual for external changes |
|
|
304
|
-
|
|
305
|
-
---
|
|
306
|
-
|
|
307
|
-
## The Two-Way Flow Architecture
|
|
308
|
-
|
|
309
|
-
There are **TWO separate flows** for every Matter device, which is similar to HAP.
|
|
310
|
-
|
|
311
|
-
### Flow A: Home App → Physical Device (AUTOMATIC)
|
|
312
|
-
|
|
313
|
-
When a user controls the device via Home app:
|
|
314
|
-
|
|
315
|
-
```
|
|
316
|
-
1. User taps in Home App
|
|
317
|
-
2. Matter command received by Homebridge
|
|
318
|
-
3. Your handler runs (e.g., on(), off(), moveToLevel())
|
|
319
|
-
4. You control your physical device (API call, MQTT, etc.)
|
|
320
|
-
5. Homebridge AUTOMATICALLY updates Matter state
|
|
321
|
-
6. All controllers (iPhone, iPad, etc.) are notified
|
|
322
|
-
```
|
|
323
|
-
|
|
324
|
-
**✅ Key Point**: After your handler completes, Homebridge **automatically** updates the Matter state. **DO NOT** call `api.matter.updateAccessoryState()` in handlers!
|
|
325
|
-
|
|
326
|
-
```typescript
|
|
327
|
-
handlers: {
|
|
328
|
-
onOff: {
|
|
329
|
-
on: async () => {
|
|
330
|
-
// Control physical device
|
|
331
|
-
await myDeviceAPI.turnOn()
|
|
332
|
-
|
|
333
|
-
// ❌ WRONG: Do NOT manually update state here!
|
|
334
|
-
// api.matter.updateAccessoryState(...)
|
|
335
|
-
|
|
336
|
-
// ✅ Homebridge automatically updates state after this handler
|
|
337
|
-
},
|
|
338
|
-
},
|
|
339
|
-
}
|
|
340
|
-
```
|
|
341
|
-
|
|
342
|
-
### Flow B: Physical Device → Home App (MANUAL)
|
|
343
|
-
|
|
344
|
-
When your physical device changes externally (button press, cloud app, automation):
|
|
345
|
-
|
|
346
|
-
```
|
|
347
|
-
1. Physical device changes state
|
|
348
|
-
2. ❌ Homebridge has NO IDEA this happened!
|
|
349
|
-
3. You MUST detect the change (events/polling)
|
|
350
|
-
4. You MUST call api.matter.updateAccessoryState()
|
|
351
|
-
5. Then all controllers are notified
|
|
352
|
-
```
|
|
353
|
-
|
|
354
|
-
**⚠️ Key Point**: You **must** monitor your device and explicitly update Matter state when the physical device changes.
|
|
355
|
-
|
|
356
|
-
```typescript
|
|
357
|
-
// Example: MQTT listener detecting external changes
|
|
358
|
-
mqttClient.on('message', (topic, message) => {
|
|
359
|
-
const deviceState = JSON.parse(message.toString())
|
|
360
|
-
const deviceIsOn = deviceState.state === 'ON'
|
|
361
|
-
|
|
362
|
-
// Check if state changed
|
|
363
|
-
const currentMatterState = accessory.clusters.onOff.onOff
|
|
364
|
-
if (deviceIsOn !== currentMatterState) {
|
|
365
|
-
// ✅ Update Matter state - this is required!
|
|
366
|
-
api.matter.updateAccessoryState(
|
|
367
|
-
accessory.uuid,
|
|
368
|
-
api.matter.clusterNames.OnOff,
|
|
369
|
-
{ onOff: deviceIsOn }
|
|
370
|
-
)
|
|
371
|
-
}
|
|
372
|
-
})
|
|
373
|
-
```
|
|
374
|
-
|
|
375
|
-
### Why No Automatic Detection?
|
|
376
|
-
|
|
377
|
-
Your physical device is **not** a Matter device—it's a regular IoT device (HTTP, MQTT, cloud API, etc.). The virtual Matter device in Homebridge cannot magically detect when your physical device changes. You must explicitly tell Homebridge when changes occur.
|
|
378
|
-
|
|
379
|
-
---
|
|
380
|
-
|
|
381
|
-
## Implementing Matter Devices
|
|
382
|
-
|
|
383
|
-
Now that you understand the core concepts and two-way flow architecture, let's implement a Matter device.
|
|
384
|
-
|
|
385
|
-
### Basic Structure
|
|
386
|
-
|
|
387
|
-
Every Matter device registration follows this pattern:
|
|
388
|
-
|
|
389
|
-
```typescript
|
|
390
|
-
import type { API } from 'homebridge'
|
|
391
|
-
|
|
392
|
-
export function registerMyDevice(api: API) {
|
|
393
|
-
const accessory = {
|
|
394
|
-
// Identity
|
|
395
|
-
uuid: api.matter.uuid.generate('unique-device-id'),
|
|
396
|
-
displayName: 'My Device',
|
|
397
|
-
deviceType: api.matter.deviceTypes.OnOffLight,
|
|
398
|
-
serialNumber: 'DEVICE-001',
|
|
399
|
-
manufacturer: 'My Company',
|
|
400
|
-
model: 'Model v1',
|
|
401
|
-
|
|
402
|
-
// Optional: Persistent context storage
|
|
403
|
-
context: {
|
|
404
|
-
deviceId: 'my-device-123',
|
|
405
|
-
},
|
|
406
|
-
|
|
407
|
-
// State: Initial values for all cluster attributes
|
|
408
|
-
// These values are only used when the accessory is first created
|
|
409
|
-
// After that, Homebridge automatically persists and restores state across restarts
|
|
410
|
-
clusters: {
|
|
411
|
-
onOff: {
|
|
412
|
-
onOff: false, // Initial state (only used on first creation)
|
|
413
|
-
},
|
|
414
|
-
},
|
|
415
|
-
|
|
416
|
-
// Handlers: Respond to commands from Home app
|
|
417
|
-
handlers: {
|
|
418
|
-
onOff: {
|
|
419
|
-
on: async () => {
|
|
420
|
-
// Control your physical device
|
|
421
|
-
},
|
|
422
|
-
off: async () => {
|
|
423
|
-
// Control your physical device
|
|
424
|
-
},
|
|
425
|
-
},
|
|
426
|
-
},
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
return [accessory]
|
|
430
|
-
}
|
|
431
|
-
```
|
|
432
|
-
|
|
433
|
-
### Device Identity
|
|
434
|
-
|
|
435
|
-
Every accessory needs unique identification:
|
|
436
|
-
|
|
437
|
-
```typescript
|
|
438
|
-
{
|
|
439
|
-
uuid: api.matter.uuid.generate('unique-id'), // Must be unique per device
|
|
440
|
-
displayName: 'Living Room Light', // Name shown in Home app
|
|
441
|
-
deviceType: api.matter.deviceTypes.OnOffLight, // Matter device type
|
|
442
|
-
serialNumber: 'LIGHT-001', // Unique serial number
|
|
443
|
-
manufacturer: 'My Company', // Manufacturer name
|
|
444
|
-
model: 'Smart Light v1', // Model identifier
|
|
445
|
-
}
|
|
446
|
-
```
|
|
447
|
-
|
|
448
|
-
### Available Device Types
|
|
449
|
-
|
|
450
|
-
Access all Matter device types via `api.matter.deviceTypes`:
|
|
451
|
-
|
|
452
|
-
```typescript
|
|
453
|
-
// Common examples
|
|
454
|
-
api.matter.deviceTypes.OnOffLight
|
|
455
|
-
api.matter.deviceTypes.DimmableLight
|
|
456
|
-
api.matter.deviceTypes.TemperatureSensor
|
|
457
|
-
api.matter.deviceTypes.Thermostat
|
|
458
|
-
api.matter.deviceTypes.DoorLock
|
|
459
|
-
// ... see full list below
|
|
460
|
-
```
|
|
461
|
-
|
|
462
|
-
<details>
|
|
463
|
-
<summary><strong>Click to see all available device types</strong></summary>
|
|
464
|
-
|
|
465
|
-
#### Lighting
|
|
466
|
-
```typescript
|
|
467
|
-
api.matter.deviceTypes.OnOffLight
|
|
468
|
-
api.matter.deviceTypes.DimmableLight
|
|
469
|
-
api.matter.deviceTypes.ColorTemperatureLight
|
|
470
|
-
api.matter.deviceTypes.ExtendedColorLight
|
|
471
|
-
```
|
|
472
|
-
|
|
473
|
-
#### Switches & Outlets
|
|
474
|
-
```typescript
|
|
475
|
-
api.matter.deviceTypes.OnOffSwitch
|
|
476
|
-
api.matter.deviceTypes.OnOffOutlet
|
|
477
|
-
```
|
|
478
|
-
|
|
479
|
-
#### Sensors
|
|
480
|
-
```typescript
|
|
481
|
-
api.matter.deviceTypes.TemperatureSensor
|
|
482
|
-
api.matter.deviceTypes.HumiditySensor
|
|
483
|
-
api.matter.deviceTypes.LightSensor
|
|
484
|
-
api.matter.deviceTypes.MotionSensor
|
|
485
|
-
api.matter.deviceTypes.ContactSensor
|
|
486
|
-
api.matter.deviceTypes.LeakSensor
|
|
487
|
-
api.matter.deviceTypes.SmokeSensor
|
|
488
|
-
```
|
|
489
|
-
|
|
490
|
-
#### HVAC
|
|
491
|
-
```typescript
|
|
492
|
-
api.matter.deviceTypes.Thermostat
|
|
493
|
-
api.matter.deviceTypes.Fan
|
|
494
|
-
api.matter.deviceTypes.RoomAirConditioner
|
|
495
|
-
```
|
|
496
|
-
|
|
497
|
-
#### Security
|
|
498
|
-
```typescript
|
|
499
|
-
api.matter.deviceTypes.DoorLock
|
|
500
|
-
```
|
|
501
|
-
|
|
502
|
-
#### Window Coverings
|
|
503
|
-
```typescript
|
|
504
|
-
api.matter.deviceTypes.WindowCovering
|
|
505
|
-
```
|
|
506
|
-
|
|
507
|
-
#### Appliances
|
|
508
|
-
```typescript
|
|
509
|
-
api.matter.deviceTypes.RoboticVacuumCleaner
|
|
510
|
-
```
|
|
511
|
-
|
|
512
|
-
#### Other
|
|
513
|
-
```typescript
|
|
514
|
-
api.matter.deviceTypes.GenericSwitch
|
|
515
|
-
api.matter.deviceTypes.Pump
|
|
516
|
-
```
|
|
517
|
-
|
|
518
|
-
</details>
|
|
519
|
-
|
|
520
|
-
### Persistent Context Storage
|
|
521
|
-
|
|
522
|
-
Store custom data that persists across Homebridge restarts:
|
|
523
|
-
|
|
524
|
-
```typescript
|
|
525
|
-
{
|
|
526
|
-
context: {
|
|
527
|
-
deviceId: 'my-light-123',
|
|
528
|
-
lastKnownState: true,
|
|
529
|
-
customData: { /* anything */ }
|
|
530
|
-
},
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
// Access later:
|
|
534
|
-
const deviceId = accessory.context.deviceId
|
|
535
|
-
```
|
|
536
|
-
|
|
537
|
-
### Cluster Configuration
|
|
538
|
-
|
|
539
|
-
Define initial state for all clusters your device supports:
|
|
540
|
-
|
|
541
|
-
```typescript
|
|
542
|
-
clusters: {
|
|
543
|
-
onOff: {
|
|
544
|
-
onOff: false, // Boolean: true = on, false = off
|
|
545
|
-
},
|
|
546
|
-
levelControl: {
|
|
547
|
-
currentLevel: 127, // Number: 1-254 (127 = 50%)
|
|
548
|
-
minLevel: 1, // Minimum brightness
|
|
549
|
-
maxLevel: 254, // Maximum brightness
|
|
550
|
-
},
|
|
551
|
-
}
|
|
552
|
-
```
|
|
553
|
-
|
|
554
|
-
**Important notes about state persistence:**
|
|
555
|
-
|
|
556
|
-
- These values are **initial/default values only** - used when the accessory is first created
|
|
557
|
-
- Once created, Homebridge **automatically persists** all state changes
|
|
558
|
-
- On restart, Homebridge **restores the last known state**, not these initial values
|
|
559
|
-
- State persists across Homebridge restarts, updates, and system reboots
|
|
560
|
-
- This works the same way as HAP - you don't need to manually save/restore state
|
|
561
|
-
|
|
562
|
-
**Example lifecycle:**
|
|
563
|
-
1. First run: Accessory created with `onOff: false` (your initial value)
|
|
564
|
-
2. User turns light on in Home app → state becomes `onOff: true`
|
|
565
|
-
3. Homebridge restarts → state is still `onOff: true` (persisted)
|
|
566
|
-
4. Not reset to `onOff: false` (initial values are ignored after first creation)
|
|
567
|
-
|
|
568
|
-
---
|
|
569
|
-
|
|
570
|
-
## Reading and Updating State
|
|
571
|
-
|
|
572
|
-
After implementing your device, you'll need to read and update its state for both Flow A (handlers) and Flow B (external changes).
|
|
573
|
-
|
|
574
|
-
### Reading Current State
|
|
575
|
-
|
|
576
|
-
There are two ways to read cluster state:
|
|
577
|
-
|
|
578
|
-
#### Method 1: Direct Property Access (Recommended)
|
|
579
|
-
|
|
580
|
-
Access cluster attributes directly from the accessory object:
|
|
581
|
-
|
|
582
|
-
```typescript
|
|
583
|
-
// Read power state
|
|
584
|
-
const isOn = accessory.clusters.onOff.onOff // boolean
|
|
585
|
-
|
|
586
|
-
// Read brightness
|
|
587
|
-
const level = accessory.clusters.levelControl.currentLevel // 1-254
|
|
588
|
-
const percent = Math.round((level / 254) * 100) // Convert to percentage
|
|
589
|
-
|
|
590
|
-
// Read color
|
|
591
|
-
const hue = accessory.clusters.colorControl.currentHue // 0-254
|
|
592
|
-
const saturation = accessory.clusters.colorControl.currentSaturation // 0-254
|
|
593
|
-
```
|
|
594
|
-
|
|
595
|
-
**When to use**: This is the recommended approach in most cases when you have a reference to the accessory object.
|
|
596
|
-
|
|
597
|
-
**Benefits**:
|
|
598
|
-
- ✅ Simple and direct
|
|
599
|
-
- ✅ TypeScript autocomplete works well
|
|
600
|
-
- ✅ No additional function call overhead
|
|
601
|
-
- ✅ Used in all official examples
|
|
602
|
-
|
|
603
|
-
#### Method 2: API Method (Special Cases)
|
|
604
|
-
|
|
605
|
-
Use the API method when you don't have a reference to the accessory object:
|
|
606
|
-
|
|
607
|
-
```typescript
|
|
608
|
-
// Read state by UUID
|
|
609
|
-
const state = api.matter.getAccessoryState(uuid, api.matter.clusterNames.OnOff)
|
|
610
|
-
if (state) {
|
|
611
|
-
const isOn = state.onOff // boolean
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
// Read brightness
|
|
615
|
-
const levelState = api.matter.getAccessoryState(uuid, api.matter.clusterNames.LevelControl)
|
|
616
|
-
if (levelState) {
|
|
617
|
-
const level = levelState.currentLevel // 1-254
|
|
618
|
-
}
|
|
619
|
-
```
|
|
620
|
-
|
|
621
|
-
**When to use**:
|
|
622
|
-
- When you only have the UUID (not the accessory object)
|
|
623
|
-
- After plugin restart when you need to read state but lost local variables
|
|
624
|
-
- When multiple parts of code need to access the same accessory state
|
|
625
|
-
- For debugging and logging utilities
|
|
626
|
-
|
|
627
|
-
**Note**: Returns `undefined` if the accessory or cluster is not found.
|
|
628
|
-
|
|
629
|
-
### Updating State
|
|
630
|
-
|
|
631
|
-
Use `updateAccessoryState()` to manually update cluster attributes:
|
|
632
|
-
|
|
633
|
-
```typescript
|
|
634
|
-
api.matter.updateAccessoryState(
|
|
635
|
-
accessory.uuid, // UUID of the accessory
|
|
636
|
-
api.matter.clusterNames.OnOff, // Cluster name (use constants!)
|
|
637
|
-
{ onOff: true } // New attribute values
|
|
638
|
-
)
|
|
639
|
-
```
|
|
640
|
-
|
|
641
|
-
**When to use**:
|
|
642
|
-
|
|
643
|
-
1. **External changes (Flow B)** - When your physical device changes state externally (most common use case)
|
|
644
|
-
2. **Side effects in handlers** - When a handler needs to update OTHER attributes as a side effect
|
|
645
|
-
|
|
646
|
-
**IMPORTANT - What NOT to do**:
|
|
647
|
-
- ❌ **Never update the same attribute that the handler is already updating**
|
|
648
|
-
|
|
649
|
-
For example, in an `on()` handler, don't manually update `onOff` - it's automatically updated by Homebridge.
|
|
650
|
-
|
|
651
|
-
**Valid example - Side effect updates**:
|
|
652
|
-
|
|
653
|
-
If your physical light always resets to 100% brightness when turned on:
|
|
654
|
-
|
|
655
|
-
```typescript
|
|
656
|
-
handlers: {
|
|
657
|
-
onOff: {
|
|
658
|
-
on: async () => {
|
|
659
|
-
await myLightAPI.turnOn()
|
|
660
|
-
|
|
661
|
-
// ✅ VALID: Update brightness as a side effect
|
|
662
|
-
// The light physically resets to 100%, so update Matter to match
|
|
663
|
-
api.matter.updateAccessoryState(
|
|
664
|
-
accessory.uuid,
|
|
665
|
-
api.matter.clusterNames.LevelControl,
|
|
666
|
-
{ currentLevel: 254 } // 100% brightness
|
|
667
|
-
)
|
|
668
|
-
|
|
669
|
-
// ❌ WRONG: Don't update onOff - it's automatically updated
|
|
670
|
-
// api.matter.updateAccessoryState(
|
|
671
|
-
// accessory.uuid,
|
|
672
|
-
// api.matter.clusterNames.OnOff,
|
|
673
|
-
// { onOff: true } // Redundant and unnecessary
|
|
674
|
-
// )
|
|
675
|
-
},
|
|
676
|
-
},
|
|
677
|
-
}
|
|
678
|
-
```
|
|
679
|
-
|
|
680
|
-
### Available Cluster Names
|
|
681
|
-
|
|
682
|
-
Use these constants with `updateAccessoryState()` and `getAccessoryState()` for type safety:
|
|
683
|
-
|
|
684
|
-
```typescript
|
|
685
|
-
// Common examples
|
|
686
|
-
api.matter.clusterNames.OnOff
|
|
687
|
-
api.matter.clusterNames.LevelControl
|
|
688
|
-
api.matter.clusterNames.ColorControl
|
|
689
|
-
api.matter.clusterNames.Thermostat
|
|
690
|
-
// ... see full list below
|
|
691
|
-
```
|
|
692
|
-
|
|
693
|
-
<details>
|
|
694
|
-
<summary><strong>Click to see all available cluster names</strong></summary>
|
|
695
|
-
|
|
696
|
-
#### Control Clusters
|
|
697
|
-
```typescript
|
|
698
|
-
api.matter.clusterNames.OnOff
|
|
699
|
-
api.matter.clusterNames.LevelControl
|
|
700
|
-
api.matter.clusterNames.ColorControl
|
|
701
|
-
api.matter.clusterNames.DoorLock
|
|
702
|
-
api.matter.clusterNames.WindowCovering
|
|
703
|
-
api.matter.clusterNames.Thermostat
|
|
704
|
-
api.matter.clusterNames.FanControl
|
|
705
|
-
```
|
|
706
|
-
|
|
707
|
-
#### Sensor Clusters
|
|
708
|
-
```typescript
|
|
709
|
-
api.matter.clusterNames.TemperatureMeasurement
|
|
710
|
-
api.matter.clusterNames.RelativeHumidityMeasurement
|
|
711
|
-
api.matter.clusterNames.IlluminanceMeasurement
|
|
712
|
-
api.matter.clusterNames.OccupancySensing
|
|
713
|
-
api.matter.clusterNames.BooleanState
|
|
714
|
-
api.matter.clusterNames.SmokeCoAlarm
|
|
715
|
-
```
|
|
716
|
-
|
|
717
|
-
#### Robotic Vacuum Cleaner Clusters
|
|
718
|
-
```typescript
|
|
719
|
-
api.matter.clusterNames.RvcRunMode
|
|
720
|
-
api.matter.clusterNames.RvcOperationalState
|
|
721
|
-
api.matter.clusterNames.RvcCleanMode
|
|
722
|
-
```
|
|
723
|
-
|
|
724
|
-
#### Pump & Other
|
|
725
|
-
```typescript
|
|
726
|
-
api.matter.clusterNames.PumpConfigurationAndControl
|
|
727
|
-
```
|
|
728
|
-
|
|
729
|
-
#### Identification
|
|
730
|
-
```typescript
|
|
731
|
-
api.matter.clusterNames.Identify
|
|
732
|
-
```
|
|
733
|
-
|
|
734
|
-
#### Device Information (Read-Only)
|
|
735
|
-
These are set during registration and cannot be updated:
|
|
736
|
-
```typescript
|
|
737
|
-
api.matter.clusterNames.BasicInformation
|
|
738
|
-
api.matter.clusterNames.BridgedDeviceBasicInformation
|
|
739
|
-
```
|
|
740
|
-
|
|
741
|
-
</details>
|
|
742
|
-
|
|
743
|
-
### Updating Multiple Properties
|
|
744
|
-
|
|
745
|
-
Update each cluster separately:
|
|
746
|
-
|
|
747
|
-
```typescript
|
|
748
|
-
// Update power state
|
|
749
|
-
api.matter.updateAccessoryState(
|
|
750
|
-
accessory.uuid,
|
|
751
|
-
api.matter.clusterNames.OnOff,
|
|
752
|
-
{ onOff: true }
|
|
753
|
-
)
|
|
754
|
-
|
|
755
|
-
// Update brightness
|
|
756
|
-
api.matter.updateAccessoryState(
|
|
757
|
-
accessory.uuid,
|
|
758
|
-
api.matter.clusterNames.LevelControl,
|
|
759
|
-
{ currentLevel: 200 }
|
|
760
|
-
)
|
|
761
|
-
|
|
762
|
-
// Update multiple attributes in same cluster
|
|
763
|
-
api.matter.updateAccessoryState(
|
|
764
|
-
accessory.uuid,
|
|
765
|
-
api.matter.clusterNames.ColorControl,
|
|
766
|
-
{
|
|
767
|
-
currentHue: 180,
|
|
768
|
-
currentSaturation: 254,
|
|
769
|
-
colorMode: api.matter.types.ColorControl.ColorMode.CurrentHueAndCurrentSaturation
|
|
770
|
-
}
|
|
771
|
-
)
|
|
772
|
-
```
|
|
773
|
-
|
|
774
|
-
---
|
|
775
|
-
|
|
776
|
-
## Monitoring External Changes
|
|
777
|
-
|
|
778
|
-
To implement Flow B (physical device → Home app), you must monitor your physical device and call `updateAccessoryState()` when it changes externally.
|
|
779
|
-
|
|
780
|
-
There are two approaches:
|
|
781
|
-
|
|
782
|
-
### Recommended: Event-Based Updates
|
|
783
|
-
|
|
784
|
-
Use this when your device supports push notifications (MQTT, WebSocket, webhooks, SSE).
|
|
785
|
-
|
|
786
|
-
**Advantages**:
|
|
787
|
-
- ✅ Instant updates
|
|
788
|
-
- ✅ More efficient
|
|
789
|
-
- ✅ Better user experience
|
|
790
|
-
- ✅ Lower overhead
|
|
791
|
-
|
|
792
|
-
#### MQTT Example
|
|
793
|
-
|
|
794
|
-
```typescript
|
|
795
|
-
import mqtt from 'mqtt'
|
|
796
|
-
|
|
797
|
-
const mqttClient = mqtt.connect('mqtt://broker-url')
|
|
798
|
-
|
|
799
|
-
mqttClient.subscribe('home/light-001/status')
|
|
800
|
-
mqttClient.on('message', (topic, message) => {
|
|
801
|
-
if (topic === 'home/light-001/status') {
|
|
802
|
-
const deviceState = JSON.parse(message.toString())
|
|
803
|
-
const deviceIsOn = deviceState.state === 'ON'
|
|
804
|
-
|
|
805
|
-
// Compare with current Matter state
|
|
806
|
-
const currentMatterState = accessory.clusters.onOff.onOff
|
|
807
|
-
|
|
808
|
-
if (deviceIsOn !== currentMatterState) {
|
|
809
|
-
log.info(`Device changed: ${deviceIsOn ? 'ON' : 'OFF'}`)
|
|
810
|
-
|
|
811
|
-
// Update Matter state
|
|
812
|
-
api.matter.updateAccessoryState(
|
|
813
|
-
accessory.uuid,
|
|
814
|
-
api.matter.clusterNames.OnOff,
|
|
815
|
-
{ onOff: deviceIsOn }
|
|
816
|
-
)
|
|
817
|
-
}
|
|
818
|
-
}
|
|
819
|
-
})
|
|
820
|
-
```
|
|
821
|
-
|
|
822
|
-
#### WebSocket Example
|
|
823
|
-
|
|
824
|
-
```typescript
|
|
825
|
-
import WebSocket from 'ws'
|
|
826
|
-
|
|
827
|
-
const ws = new WebSocket('wss://api.example.com/devices/light-001/events')
|
|
828
|
-
|
|
829
|
-
ws.on('message', (data) => {
|
|
830
|
-
const event = JSON.parse(data.toString())
|
|
831
|
-
|
|
832
|
-
if (event.type === 'state_changed') {
|
|
833
|
-
const deviceIsOn = event.state === 'ON'
|
|
834
|
-
const currentMatterState = accessory.clusters.onOff.onOff
|
|
835
|
-
|
|
836
|
-
if (deviceIsOn !== currentMatterState) {
|
|
837
|
-
api.matter.updateAccessoryState(
|
|
838
|
-
accessory.uuid,
|
|
839
|
-
api.matter.clusterNames.OnOff,
|
|
840
|
-
{ onOff: deviceIsOn }
|
|
841
|
-
)
|
|
842
|
-
}
|
|
843
|
-
}
|
|
844
|
-
})
|
|
845
|
-
|
|
846
|
-
// Handle reconnection
|
|
847
|
-
ws.on('close', () => {
|
|
848
|
-
log.warn('WebSocket disconnected, reconnecting in 5s...')
|
|
849
|
-
setTimeout(() => startWebSocket(), 5000)
|
|
850
|
-
})
|
|
851
|
-
```
|
|
852
|
-
|
|
853
|
-
#### Webhook Example
|
|
854
|
-
|
|
855
|
-
```typescript
|
|
856
|
-
import express from 'express'
|
|
857
|
-
|
|
858
|
-
const app = express()
|
|
859
|
-
app.use(express.json())
|
|
860
|
-
|
|
861
|
-
app.post('/webhook/light-001/state', (req, res) => {
|
|
862
|
-
const deviceIsOn = req.body.state === 'ON'
|
|
863
|
-
const currentMatterState = accessory.clusters.onOff.onOff
|
|
864
|
-
|
|
865
|
-
if (deviceIsOn !== currentMatterState) {
|
|
866
|
-
api.matter.updateAccessoryState(
|
|
867
|
-
accessory.uuid,
|
|
868
|
-
api.matter.clusterNames.OnOff,
|
|
869
|
-
{ onOff: deviceIsOn }
|
|
870
|
-
)
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
res.sendStatus(200)
|
|
874
|
-
})
|
|
875
|
-
|
|
876
|
-
app.listen(3000)
|
|
877
|
-
```
|
|
878
|
-
|
|
879
|
-
### Fallback: Polling-Based Updates
|
|
880
|
-
|
|
881
|
-
Use this **only** if your device doesn't support events.
|
|
882
|
-
|
|
883
|
-
**Disadvantages**:
|
|
884
|
-
- ⚠️ Delayed updates (depends on interval)
|
|
885
|
-
- ⚠️ Higher network overhead
|
|
886
|
-
- ⚠️ Can strain device APIs
|
|
887
|
-
- ⚠️ Use 5-10 second intervals minimum
|
|
888
|
-
|
|
889
|
-
```typescript
|
|
890
|
-
setInterval(async () => {
|
|
891
|
-
try {
|
|
892
|
-
// Fetch state from physical device
|
|
893
|
-
const response = await fetch('https://api.example.com/devices/light-001/state')
|
|
894
|
-
const data = await response.json()
|
|
895
|
-
const deviceIsOn = data.state === 'ON'
|
|
896
|
-
|
|
897
|
-
// Compare with current Matter state
|
|
898
|
-
const currentMatterState = accessory.clusters.onOff.onOff
|
|
899
|
-
|
|
900
|
-
// Only update if changed (avoid unnecessary updates)
|
|
901
|
-
if (deviceIsOn !== currentMatterState) {
|
|
902
|
-
log.info(`Device changed (polling): ${deviceIsOn ? 'ON' : 'OFF'}`)
|
|
903
|
-
|
|
904
|
-
api.matter.updateAccessoryState(
|
|
905
|
-
accessory.uuid,
|
|
906
|
-
api.matter.clusterNames.OnOff,
|
|
907
|
-
{ onOff: deviceIsOn }
|
|
908
|
-
)
|
|
909
|
-
}
|
|
910
|
-
} catch (error) {
|
|
911
|
-
log.error(`Error polling device: ${error}`)
|
|
912
|
-
}
|
|
913
|
-
}, 5000) // Poll every 5 seconds
|
|
914
|
-
```
|
|
915
|
-
|
|
916
|
-
---
|
|
917
|
-
|
|
918
|
-
## Using Matter Types
|
|
919
|
-
|
|
920
|
-
Homebridge provides access to all Matter.js cluster types via `api.matter.types`. This gives you type-safe access to enums, types, and constants.
|
|
921
|
-
|
|
922
|
-
### Accessing Types
|
|
923
|
-
|
|
924
|
-
Matter types are available directly on the API - no imports needed:
|
|
925
|
-
|
|
926
|
-
```typescript
|
|
927
|
-
// Use types with cluster names for type-safe updates
|
|
928
|
-
api.matter.updateAccessoryState(
|
|
929
|
-
uuid,
|
|
930
|
-
api.matter.clusterNames.FanControl,
|
|
931
|
-
{ fanMode: api.matter.types.FanControl.FanMode.High }
|
|
932
|
-
)
|
|
933
|
-
|
|
934
|
-
// Everything is under api.matter
|
|
935
|
-
api.matter.updateAccessoryState(
|
|
936
|
-
uuid,
|
|
937
|
-
api.matter.clusterNames.Thermostat,
|
|
938
|
-
{ systemMode: api.matter.types.Thermostat.SystemMode.Heat }
|
|
939
|
-
)
|
|
940
|
-
```
|
|
941
|
-
|
|
942
|
-
**Consistent API Pattern**:
|
|
943
|
-
- `api.matter.deviceTypes.*` - Device type definitions
|
|
944
|
-
- `api.matter.clusterNames.*` - Cluster name constants (strings)
|
|
945
|
-
- `api.matter.types.*` - Matter.js type definitions and enums
|
|
946
|
-
- All accessible from `api.matter` - no separate imports required
|
|
947
|
-
|
|
948
|
-
### Discovering Available Values
|
|
949
|
-
|
|
950
|
-
Three ways to discover enum values:
|
|
951
|
-
|
|
952
|
-
1. **TypeScript Autocomplete**: Type `api.matter.types.FanControl.` and see suggestions
|
|
953
|
-
2. **Matter.js Reference**: https://github.com/project-chip/matter.js
|
|
954
|
-
3. **Matter Specification**: https://csa-iot.org/developer-resource/specifications-download-request/
|
|
955
|
-
|
|
956
|
-
### All Available Clusters
|
|
957
|
-
|
|
958
|
-
Matter types provides access to ALL 130+ Matter clusters:
|
|
959
|
-
|
|
960
|
-
```typescript
|
|
961
|
-
api.matter.types.FanControl
|
|
962
|
-
api.matter.types.Thermostat
|
|
963
|
-
api.matter.types.DoorLock
|
|
964
|
-
api.matter.types.ColorControl
|
|
965
|
-
api.matter.types.WindowCovering
|
|
966
|
-
api.matter.types.SmokeCoAlarm
|
|
967
|
-
api.matter.types.OccupancySensing
|
|
968
|
-
api.matter.types.TemperatureMeasurement
|
|
969
|
-
// ... and 120+ more!
|
|
970
|
-
```
|
|
971
|
-
|
|
972
|
-
---
|
|
973
|
-
|
|
974
|
-
## Best Practices
|
|
975
|
-
|
|
976
|
-
### 1. Always Compare Before Updating
|
|
977
|
-
|
|
978
|
-
Avoid unnecessary updates by checking if state actually changed:
|
|
979
|
-
|
|
980
|
-
```typescript
|
|
981
|
-
const currentState = accessory.clusters.onOff.onOff
|
|
982
|
-
if (newState !== currentState) {
|
|
983
|
-
api.matter.updateAccessoryState(...)
|
|
984
|
-
}
|
|
985
|
-
```
|
|
986
|
-
|
|
987
|
-
### 2. Use Events Over Polling
|
|
988
|
-
|
|
989
|
-
Whenever possible, use event-based updates (MQTT, WebSocket, webhooks) instead of polling for better performance and user experience.
|
|
990
|
-
|
|
991
|
-
### 3. Don't Update the Same Attribute in Handlers
|
|
992
|
-
|
|
993
|
-
Handlers automatically update their own attribute. Only manually update OTHER attributes (side effects) or for external changes (Flow B).
|
|
994
|
-
|
|
995
|
-
```typescript
|
|
996
|
-
// ❌ WRONG - Redundant update
|
|
997
|
-
handlers: {
|
|
998
|
-
onOff: {
|
|
999
|
-
on: async () => {
|
|
1000
|
-
await myDevice.turnOn()
|
|
1001
|
-
api.matter.updateAccessoryState(uuid, api.matter.clusterNames.OnOff, { onOff: true })
|
|
1002
|
-
// Redundant! onOff is already updated automatically
|
|
1003
|
-
}
|
|
1004
|
-
}
|
|
1005
|
-
}
|
|
1006
|
-
|
|
1007
|
-
// ✅ CORRECT - No manual update needed
|
|
1008
|
-
handlers: {
|
|
1009
|
-
onOff: {
|
|
1010
|
-
on: async () => {
|
|
1011
|
-
await myDevice.turnOn()
|
|
1012
|
-
// State automatically updated
|
|
1013
|
-
}
|
|
1014
|
-
}
|
|
1015
|
-
}
|
|
1016
|
-
|
|
1017
|
-
// ✅ ALSO CORRECT - Update different attribute as side effect
|
|
1018
|
-
handlers: {
|
|
1019
|
-
onOff: {
|
|
1020
|
-
on: async () => {
|
|
1021
|
-
await myDevice.turnOn()
|
|
1022
|
-
// Light resets to 100% when turned on, so update brightness too
|
|
1023
|
-
api.matter.updateAccessoryState(uuid, api.matter.clusterNames.LevelControl, { currentLevel: 254 })
|
|
1024
|
-
}
|
|
1025
|
-
}
|
|
1026
|
-
}
|
|
1027
|
-
```
|
|
1028
|
-
|
|
1029
|
-
### 4. Handle Errors Gracefully
|
|
1030
|
-
|
|
1031
|
-
Always throw errors from handlers so the Home app can be notified of failures:
|
|
1032
|
-
|
|
1033
|
-
```typescript
|
|
1034
|
-
handlers: {
|
|
1035
|
-
onOff: {
|
|
1036
|
-
on: async () => {
|
|
1037
|
-
try {
|
|
1038
|
-
// Control your physical device
|
|
1039
|
-
await myDevice.turnOn()
|
|
1040
|
-
log.info('Successfully turned on device')
|
|
1041
|
-
} catch (error) {
|
|
1042
|
-
// Log the error for debugging
|
|
1043
|
-
log.error(`Failed to turn on device: ${error}`)
|
|
1044
|
-
|
|
1045
|
-
// ✅ IMPORTANT: Re-throw the error
|
|
1046
|
-
// This propagates the error to the Matter protocol, which:
|
|
1047
|
-
// 1. Notifies the Home app that the command failed
|
|
1048
|
-
// 2. Prevents state from updating incorrectly
|
|
1049
|
-
// 3. Shows an error message to the user
|
|
1050
|
-
throw error
|
|
1051
|
-
}
|
|
1052
|
-
},
|
|
1053
|
-
|
|
1054
|
-
off: async () => {
|
|
1055
|
-
try {
|
|
1056
|
-
await myDevice.turnOff()
|
|
1057
|
-
log.info('Successfully turned off device')
|
|
1058
|
-
} catch (error) {
|
|
1059
|
-
log.error(`Failed to turn off device: ${error}`)
|
|
1060
|
-
throw error // Always re-throw!
|
|
1061
|
-
}
|
|
1062
|
-
},
|
|
1063
|
-
},
|
|
1064
|
-
}
|
|
1065
|
-
```
|
|
1066
|
-
|
|
1067
|
-
**Why throw errors?**
|
|
1068
|
-
|
|
1069
|
-
Without throwing:
|
|
1070
|
-
- ❌ Home app thinks command succeeded
|
|
1071
|
-
- ❌ State updates incorrectly (shows "on" when device is actually off)
|
|
1072
|
-
- ❌ User has no feedback that something went wrong
|
|
1073
|
-
|
|
1074
|
-
With throwing:
|
|
1075
|
-
- ✅ Home app displays error to user
|
|
1076
|
-
- ✅ State remains unchanged (accurate)
|
|
1077
|
-
- ✅ User knows to try again or investigate the issue
|
|
1078
|
-
|
|
1079
|
-
### 5. Log Clearly
|
|
1080
|
-
|
|
1081
|
-
Distinguish between the two flows in your logs:
|
|
1082
|
-
|
|
1083
|
-
```typescript
|
|
1084
|
-
log.info('[Device] Home app → Physical device: Turning ON')
|
|
1085
|
-
log.info('[Device] Physical device → Home app: State changed to ON')
|
|
1086
|
-
```
|
|
1087
|
-
|
|
1088
|
-
### 6. Use Cluster Name Constants
|
|
1089
|
-
|
|
1090
|
-
Always use `api.matter.clusterNames.*` constants instead of strings:
|
|
1091
|
-
|
|
1092
|
-
```typescript
|
|
1093
|
-
// ✅ CORRECT
|
|
1094
|
-
api.matter.updateAccessoryState(uuid, api.matter.clusterNames.OnOff, {...})
|
|
1095
|
-
|
|
1096
|
-
// ❌ WRONG
|
|
1097
|
-
api.matter.updateAccessoryState(uuid, 'onOff', {...})
|
|
1098
|
-
```
|
|
1099
|
-
|
|
1100
|
-
---
|
|
1101
|
-
|
|
1102
|
-
## API Reference
|
|
1103
|
-
|
|
1104
|
-
Complete reference for all Matter API methods and properties available in Homebridge.
|
|
1105
|
-
|
|
1106
|
-
### Platform API Methods
|
|
1107
|
-
|
|
1108
|
-
#### `api.isMatterAvailable(): boolean`
|
|
1109
|
-
|
|
1110
|
-
Check if Matter is available in the current version of Homebridge.
|
|
1111
|
-
|
|
1112
|
-
**Returns:** `true` if Homebridge version is >= 2.0.0-alpha.0
|
|
1113
|
-
|
|
1114
|
-
**Usage:**
|
|
1115
|
-
```typescript
|
|
1116
|
-
if (api.isMatterAvailable()) {
|
|
1117
|
-
log.info('Matter is available in this Homebridge version')
|
|
1118
|
-
} else {
|
|
1119
|
-
log.warn('Matter requires Homebridge >= 2.0.0-alpha.0')
|
|
1120
|
-
}
|
|
1121
|
-
```
|
|
1122
|
-
|
|
1123
|
-
**When to use:**
|
|
1124
|
-
- Plugin compatibility checks
|
|
1125
|
-
- Conditional feature loading
|
|
1126
|
-
- Version-specific functionality
|
|
1127
|
-
|
|
1128
|
-
---
|
|
1129
|
-
|
|
1130
|
-
#### `api.isMatterEnabled(): boolean`
|
|
1131
|
-
|
|
1132
|
-
Check if Matter is enabled for this bridge instance.
|
|
1133
|
-
|
|
1134
|
-
**Returns:** `true` if Matter is enabled in the bridge configuration
|
|
1135
|
-
|
|
1136
|
-
**Configuration:**
|
|
1137
|
-
- For main bridge: Set `bridge.matter = true` in config.json
|
|
1138
|
-
- For child bridge: Set `_bridge.matter = true` in platform config
|
|
1139
|
-
|
|
1140
|
-
**Usage:**
|
|
1141
|
-
```typescript
|
|
1142
|
-
if (api.isMatterEnabled()) {
|
|
1143
|
-
// Register Matter accessories
|
|
1144
|
-
api.matter.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, accessories)
|
|
1145
|
-
} else {
|
|
1146
|
-
log.info('Matter is not enabled for this bridge')
|
|
1147
|
-
}
|
|
1148
|
-
```
|
|
1149
|
-
|
|
1150
|
-
**When to use:**
|
|
1151
|
-
- Runtime checks before registering Matter accessories
|
|
1152
|
-
- Conditional accessory registration
|
|
1153
|
-
- User feedback about Matter status
|
|
1154
|
-
|
|
1155
|
-
---
|
|
1156
|
-
|
|
1157
|
-
### Matter API Properties
|
|
1158
|
-
|
|
1159
|
-
All properties are accessed via `api.matter.*`
|
|
1160
|
-
|
|
1161
|
-
#### `api.matter.uuid`
|
|
1162
|
-
|
|
1163
|
-
UUID generator for creating unique accessory identifiers (alias of `api.hap.uuid`).
|
|
1164
|
-
|
|
1165
|
-
**Type:** `HAP['uuid']`
|
|
1166
|
-
|
|
1167
|
-
**Methods:**
|
|
1168
|
-
- `generate(data: string): string` - Generate deterministic UUID from string
|
|
1169
|
-
- `isValid(uuid: string): boolean` - Validate UUID format
|
|
1170
|
-
|
|
1171
|
-
**Usage:**
|
|
1172
|
-
```typescript
|
|
1173
|
-
const uuid = api.matter.uuid.generate('my-light-123')
|
|
1174
|
-
// Output: 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX'
|
|
1175
|
-
|
|
1176
|
-
if (api.matter.uuid.isValid(uuid)) {
|
|
1177
|
-
// UUID is valid
|
|
1178
|
-
}
|
|
1179
|
-
```
|
|
1180
|
-
|
|
1181
|
-
**Important:**
|
|
1182
|
-
- UUIDs must be deterministic (same input = same output)
|
|
1183
|
-
- Use unique identifiers (device ID, MAC address, etc.)
|
|
1184
|
-
- UUIDs persist across restarts for state restoration
|
|
1185
|
-
|
|
1186
|
-
---
|
|
1187
|
-
|
|
1188
|
-
#### `api.matter.deviceTypes`
|
|
1189
|
-
|
|
1190
|
-
Available Matter device types for creating accessories.
|
|
1191
|
-
|
|
1192
|
-
**Type:** `typeof deviceTypes` (from Matter.js)
|
|
1193
|
-
|
|
1194
|
-
**Common Device Types:**
|
|
1195
|
-
- Lighting: `OnOffLight`, `DimmableLight`, `ColorTemperatureLight`, `ExtendedColorLight`
|
|
1196
|
-
- Switches & Outlets: `OnOffSwitch`, `OnOffOutlet`
|
|
1197
|
-
- Sensors: `ContactSensor`, `TemperatureSensor`, `HumiditySensor`, `OccupancySensor`, etc.
|
|
1198
|
-
- HVAC: `Thermostat`, `Fan`
|
|
1199
|
-
- Closure: `DoorLock`, `WindowCovering`
|
|
1200
|
-
- Robotic: `RoboticVacuumCleaner`
|
|
1201
|
-
|
|
1202
|
-
**Usage:**
|
|
1203
|
-
```typescript
|
|
1204
|
-
const accessory = {
|
|
1205
|
-
uuid: api.matter.uuid.generate('my-light'),
|
|
1206
|
-
displayName: 'Living Room Light',
|
|
1207
|
-
deviceType: api.matter.deviceTypes.DimmableLight,
|
|
1208
|
-
// ...
|
|
1209
|
-
}
|
|
1210
|
-
```
|
|
1211
|
-
|
|
1212
|
-
**See:** [Available Device Types](#available-device-types) for complete list
|
|
1213
|
-
|
|
1214
|
-
---
|
|
1215
|
-
|
|
1216
|
-
#### `api.matter.clusters`
|
|
1217
|
-
|
|
1218
|
-
Direct access to Matter.js cluster definitions for advanced use cases.
|
|
1219
|
-
|
|
1220
|
-
**Type:** `typeof clusters` (from Matter.js)
|
|
1221
|
-
|
|
1222
|
-
**Usage:**
|
|
1223
|
-
```typescript
|
|
1224
|
-
// Access cluster attributes programmatically
|
|
1225
|
-
const onOffAttrs = api.matter.clusters.OnOffCluster.attributes
|
|
1226
|
-
console.log(Object.keys(onOffAttrs))
|
|
1227
|
-
// Output: ['onOff', 'clusterRevision', 'featureMap', ...]
|
|
1228
|
-
|
|
1229
|
-
// Check if cluster supports specific features
|
|
1230
|
-
const levelControlFeatures = api.matter.clusters.LevelControlCluster.features
|
|
1231
|
-
```
|
|
1232
|
-
|
|
1233
|
-
**When to use:**
|
|
1234
|
-
- Advanced cluster introspection
|
|
1235
|
-
- Dynamic attribute discovery
|
|
1236
|
-
- Custom cluster implementations
|
|
1237
|
-
|
|
1238
|
-
**Note:** Most plugins should use the higher-level APIs instead.
|
|
1239
|
-
|
|
1240
|
-
---
|
|
1241
|
-
|
|
1242
|
-
#### `api.matter.clusterNames`
|
|
1243
|
-
|
|
1244
|
-
Cluster name constants for type safety and autocomplete with state methods.
|
|
1245
|
-
|
|
1246
|
-
**Type:** `typeof clusterNames`
|
|
1247
|
-
|
|
1248
|
-
**Available Names:**
|
|
1249
|
-
- `OnOff`, `LevelControl`, `ColorControl`
|
|
1250
|
-
- `DoorLock`, `WindowCovering`
|
|
1251
|
-
- `Thermostat`, `FanControl`
|
|
1252
|
-
- `TemperatureMeasurement`, `RelativeHumidityMeasurement`
|
|
1253
|
-
- And many more...
|
|
1254
|
-
|
|
1255
|
-
**Usage:**
|
|
1256
|
-
```typescript
|
|
1257
|
-
// Type-safe cluster references
|
|
1258
|
-
api.matter.updateAccessoryState(
|
|
1259
|
-
uuid,
|
|
1260
|
-
api.matter.clusterNames.OnOff, // Autocomplete available!
|
|
1261
|
-
{ onOff: true }
|
|
1262
|
-
)
|
|
1263
|
-
|
|
1264
|
-
const state = api.matter.getAccessoryState(
|
|
1265
|
-
uuid,
|
|
1266
|
-
api.matter.clusterNames.LevelControl
|
|
1267
|
-
)
|
|
1268
|
-
```
|
|
1269
|
-
|
|
1270
|
-
**Benefits:**
|
|
1271
|
-
- Autocomplete in IDEs
|
|
1272
|
-
- Compile-time error checking
|
|
1273
|
-
- Prevents typos in cluster names
|
|
1274
|
-
|
|
1275
|
-
---
|
|
1276
|
-
|
|
1277
|
-
#### `api.matter.types`
|
|
1278
|
-
|
|
1279
|
-
Type-safe enum values for cluster attributes (modes, states, etc.).
|
|
1280
|
-
|
|
1281
|
-
**Type:** `typeof MatterTypes` (from Homebridge)
|
|
1282
|
-
|
|
1283
|
-
**Common Types:**
|
|
1284
|
-
- `DoorLock.LockState` - Lock states (Locked, Unlocked, etc.)
|
|
1285
|
-
- `DoorLock.LockType` - Lock types (DeadBolt, Magnetic, etc.)
|
|
1286
|
-
- `FanControl.FanMode` - Fan modes (Off, Low, Medium, High, Auto, etc.)
|
|
1287
|
-
- `FanControl.FanModeSequence` - Supported mode sequences
|
|
1288
|
-
- `Thermostat.SystemMode` - HVAC modes (Off, Heat, Cool, Auto, etc.)
|
|
1289
|
-
- `ColorControl.ColorMode` - Color modes (HS, XY, ColorTemperature)
|
|
1290
|
-
- `RvcRunMode.ModeTag` - Vacuum run mode tags (Idle, Cleaning, Mapping)
|
|
1291
|
-
- `RvcCleanMode.ModeTag` - Vacuum clean mode tags (Vacuum, Mop)
|
|
1292
|
-
- `RvcOperationalState.OperationalState` - Vacuum states (Stopped, Running, Docked, etc.)
|
|
1293
|
-
|
|
1294
|
-
**Usage:**
|
|
1295
|
-
```typescript
|
|
1296
|
-
// Door lock states
|
|
1297
|
-
clusters: {
|
|
1298
|
-
doorLock: {
|
|
1299
|
-
lockState: api.matter.types.DoorLock.LockState.Unlocked,
|
|
1300
|
-
lockType: api.matter.types.DoorLock.LockType.DeadBolt
|
|
1301
|
-
}
|
|
1302
|
-
}
|
|
1303
|
-
|
|
1304
|
-
// Fan modes
|
|
1305
|
-
clusters: {
|
|
1306
|
-
fanControl: {
|
|
1307
|
-
fanMode: api.matter.types.FanControl.FanMode.Auto,
|
|
1308
|
-
fanModeSequence: api.matter.types.FanControl.FanModeSequence.OffLowMedHigh
|
|
1309
|
-
}
|
|
1310
|
-
}
|
|
1311
|
-
|
|
1312
|
-
// Color modes
|
|
1313
|
-
clusters: {
|
|
1314
|
-
colorControl: {
|
|
1315
|
-
colorMode: api.matter.types.ColorControl.ColorMode.ColorTemperatureMireds
|
|
1316
|
-
}
|
|
1317
|
-
}
|
|
1318
|
-
```
|
|
1319
|
-
|
|
1320
|
-
**Benefits:**
|
|
1321
|
-
- Type safety prevents invalid values
|
|
1322
|
-
- IDE autocomplete shows available options
|
|
1323
|
-
- Self-documenting code
|
|
1324
|
-
- Compile-time validation
|
|
1325
|
-
|
|
1326
|
-
**See:** [Using Matter Types](#using-matter-types) for detailed examples
|
|
1327
|
-
|
|
1328
|
-
---
|
|
1329
|
-
|
|
1330
|
-
### Matter API Methods
|
|
1331
|
-
|
|
1332
|
-
#### `api.matter.registerPlatformAccessories()`
|
|
1333
|
-
|
|
1334
|
-
Register Matter accessories with the platform (standard registration method).
|
|
1335
|
-
|
|
1336
|
-
**Signature:**
|
|
1337
|
-
```typescript
|
|
1338
|
-
registerPlatformAccessories(
|
|
1339
|
-
pluginIdentifier: string,
|
|
1340
|
-
platformName: string,
|
|
1341
|
-
accessories: MatterAccessory[]
|
|
1342
|
-
): void
|
|
1343
|
-
```
|
|
1344
|
-
|
|
1345
|
-
**Parameters:**
|
|
1346
|
-
- `pluginIdentifier` - Plugin identifier (e.g., `'homebridge-example'`)
|
|
1347
|
-
- `platformName` - Platform name (e.g., `'ExamplePlatform'`)
|
|
1348
|
-
- `accessories` - Array of Matter accessories to register
|
|
1349
|
-
|
|
1350
|
-
**Usage:**
|
|
1351
|
-
```typescript
|
|
1352
|
-
const PLUGIN_NAME = 'homebridge-example'
|
|
1353
|
-
const PLATFORM_NAME = 'ExamplePlatform'
|
|
1354
|
-
|
|
1355
|
-
const accessories = [
|
|
1356
|
-
{
|
|
1357
|
-
uuid: api.matter.uuid.generate('my-light'),
|
|
1358
|
-
displayName: 'Living Room Light',
|
|
1359
|
-
deviceType: api.matter.deviceTypes.OnOffLight,
|
|
1360
|
-
// ...
|
|
1361
|
-
}
|
|
1362
|
-
]
|
|
1363
|
-
|
|
1364
|
-
api.matter.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, accessories)
|
|
1365
|
-
```
|
|
1366
|
-
|
|
1367
|
-
**When to use:**
|
|
1368
|
-
- Standard accessory registration
|
|
1369
|
-
- Multiple accessories on shared bridge
|
|
1370
|
-
- Most common use case
|
|
1371
|
-
|
|
1372
|
-
**See also:** `publishExternalAccessories()` for isolated accessories
|
|
1373
|
-
|
|
1374
|
-
---
|
|
1375
|
-
|
|
1376
|
-
#### `api.matter.unregisterPlatformAccessories()`
|
|
1377
|
-
|
|
1378
|
-
Unregister Matter accessories by UUID.
|
|
1379
|
-
|
|
1380
|
-
**Signature:**
|
|
1381
|
-
```typescript
|
|
1382
|
-
unregisterPlatformAccessories(
|
|
1383
|
-
pluginIdentifier: string,
|
|
1384
|
-
platformName: string,
|
|
1385
|
-
accessories: MatterAccessory[]
|
|
1386
|
-
): void
|
|
1387
|
-
```
|
|
1388
|
-
|
|
1389
|
-
**Parameters:**
|
|
1390
|
-
- `pluginIdentifier` - Plugin identifier
|
|
1391
|
-
- `platformName` - Platform name
|
|
1392
|
-
- `accessories` - Array of accessories to unregister (only `uuid` is required)
|
|
1393
|
-
|
|
1394
|
-
**Usage:**
|
|
1395
|
-
```typescript
|
|
1396
|
-
// Unregister accessories
|
|
1397
|
-
const accessoriesToRemove = [
|
|
1398
|
-
{ uuid: 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX' }
|
|
1399
|
-
]
|
|
1400
|
-
|
|
1401
|
-
api.matter.unregisterPlatformAccessories(
|
|
1402
|
-
PLUGIN_NAME,
|
|
1403
|
-
PLATFORM_NAME,
|
|
1404
|
-
accessoriesToRemove
|
|
1405
|
-
)
|
|
1406
|
-
```
|
|
1407
|
-
|
|
1408
|
-
**When to use:**
|
|
1409
|
-
- Removing accessories from Homebridge
|
|
1410
|
-
- Cleanup during plugin shutdown
|
|
1411
|
-
- User-initiated accessory removal
|
|
1412
|
-
|
|
1413
|
-
---
|
|
1414
|
-
|
|
1415
|
-
#### `api.matter.publishExternalAccessories()`
|
|
1416
|
-
|
|
1417
|
-
Publish accessories on dedicated Matter bridges (isolated from other accessories).
|
|
1418
|
-
|
|
1419
|
-
**Signature:**
|
|
1420
|
-
```typescript
|
|
1421
|
-
publishExternalAccessories(
|
|
1422
|
-
pluginIdentifier: string,
|
|
1423
|
-
accessories: MatterAccessory[]
|
|
1424
|
-
): void
|
|
1425
|
-
```
|
|
1426
|
-
|
|
1427
|
-
**Parameters:**
|
|
1428
|
-
- `pluginIdentifier` - Plugin identifier
|
|
1429
|
-
- `accessories` - Array of accessories to publish externally
|
|
1430
|
-
|
|
1431
|
-
**Usage:**
|
|
1432
|
-
```typescript
|
|
1433
|
-
const accessories = [
|
|
1434
|
-
{
|
|
1435
|
-
uuid: api.matter.uuid.generate('robot-vacuum'),
|
|
1436
|
-
displayName: 'Robot Vacuum',
|
|
1437
|
-
deviceType: api.matter.deviceTypes.RoboticVacuumCleaner,
|
|
1438
|
-
// ...
|
|
1439
|
-
}
|
|
1440
|
-
]
|
|
1441
|
-
|
|
1442
|
-
// Publish on dedicated bridge
|
|
1443
|
-
api.matter.publishExternalAccessories(PLUGIN_NAME, accessories)
|
|
1444
|
-
```
|
|
1445
|
-
|
|
1446
|
-
**When to use:**
|
|
1447
|
-
- Robotic Vacuum Cleaners (required by Apple Home)
|
|
1448
|
-
- Cameras and video doorbells
|
|
1449
|
-
- Devices requiring isolation
|
|
1450
|
-
- Testing single accessories
|
|
1451
|
-
|
|
1452
|
-
**Behavior:**
|
|
1453
|
-
- Each accessory gets its own Matter server instance
|
|
1454
|
-
- Separate port allocation (e.g., 5541, 5542, etc.)
|
|
1455
|
-
- Independent QR codes for commissioning
|
|
1456
|
-
- Complete isolation from other accessories
|
|
1457
|
-
|
|
1458
|
-
**Similar to:** HAP's `api.publishExternalAccessories()`
|
|
1459
|
-
|
|
1460
|
-
---
|
|
1461
|
-
|
|
1462
|
-
#### `api.matter.updateAccessoryState()`
|
|
1463
|
-
|
|
1464
|
-
Update accessory cluster state when device changes externally (Flow B).
|
|
1465
|
-
|
|
1466
|
-
**Signature:**
|
|
1467
|
-
```typescript
|
|
1468
|
-
updateAccessoryState(
|
|
1469
|
-
uuid: string,
|
|
1470
|
-
cluster: string,
|
|
1471
|
-
attributes: Record<string, any>
|
|
1472
|
-
): void
|
|
1473
|
-
```
|
|
1474
|
-
|
|
1475
|
-
**Parameters:**
|
|
1476
|
-
- `uuid` - Accessory UUID
|
|
1477
|
-
- `cluster` - Cluster name (use `api.matter.clusterNames.*`)
|
|
1478
|
-
- `attributes` - Attributes to update (key-value pairs)
|
|
1479
|
-
|
|
1480
|
-
**Usage:**
|
|
1481
|
-
```typescript
|
|
1482
|
-
// Device turned on via native app
|
|
1483
|
-
api.matter.updateAccessoryState(
|
|
1484
|
-
uuid,
|
|
1485
|
-
api.matter.clusterNames.OnOff,
|
|
1486
|
-
{ onOff: true }
|
|
1487
|
-
)
|
|
1488
|
-
|
|
1489
|
-
// Brightness changed via physical button
|
|
1490
|
-
api.matter.updateAccessoryState(
|
|
1491
|
-
uuid,
|
|
1492
|
-
api.matter.clusterNames.LevelControl,
|
|
1493
|
-
{ currentLevel: 200 }
|
|
1494
|
-
)
|
|
1495
|
-
|
|
1496
|
-
// Update multiple attributes at once
|
|
1497
|
-
api.matter.updateAccessoryState(
|
|
1498
|
-
uuid,
|
|
1499
|
-
api.matter.clusterNames.ColorControl,
|
|
1500
|
-
{
|
|
1501
|
-
colorMode: api.matter.types.ColorControl.ColorMode.ColorTemperatureMireds,
|
|
1502
|
-
colorTemperatureMireds: 250
|
|
1503
|
-
}
|
|
1504
|
-
)
|
|
1505
|
-
```
|
|
1506
|
-
|
|
1507
|
-
**IMPORTANT:**
|
|
1508
|
-
- ❌ **DO NOT** use inside handlers (state updates automatically)
|
|
1509
|
-
- ✅ **DO** use for external changes (webhooks, polling, events)
|
|
1510
|
-
|
|
1511
|
-
**When to use:**
|
|
1512
|
-
- Native app controls
|
|
1513
|
-
- Physical button presses
|
|
1514
|
-
- Webhook notifications
|
|
1515
|
-
- Polling results
|
|
1516
|
-
- MQTT/WebSocket messages
|
|
1517
|
-
|
|
1518
|
-
**See:** [Flow B: Physical Device → Home App](#flow-b-physical-device--home-app-manual)
|
|
1519
|
-
|
|
1520
|
-
---
|
|
1521
|
-
|
|
1522
|
-
#### `api.matter.getAccessoryState()`
|
|
1523
|
-
|
|
1524
|
-
Get current cluster state from a Matter accessory.
|
|
1525
|
-
|
|
1526
|
-
**Signature:**
|
|
1527
|
-
```typescript
|
|
1528
|
-
getAccessoryState(
|
|
1529
|
-
uuid: string,
|
|
1530
|
-
cluster: string
|
|
1531
|
-
): Record<string, any> | undefined
|
|
1532
|
-
```
|
|
1533
|
-
|
|
1534
|
-
**Parameters:**
|
|
1535
|
-
- `uuid` - Accessory UUID
|
|
1536
|
-
- `cluster` - Cluster name (use `api.matter.clusterNames.*`)
|
|
1537
|
-
|
|
1538
|
-
**Returns:**
|
|
1539
|
-
- Object with current attribute values, or `undefined` if not found
|
|
1540
|
-
|
|
1541
|
-
**Usage:**
|
|
1542
|
-
```typescript
|
|
1543
|
-
// Read OnOff state
|
|
1544
|
-
const state = api.matter.getAccessoryState(uuid, api.matter.clusterNames.OnOff)
|
|
1545
|
-
if (state?.onOff) {
|
|
1546
|
-
log.info('Light is currently on')
|
|
1547
|
-
}
|
|
1548
|
-
|
|
1549
|
-
// Read level control state
|
|
1550
|
-
const levelState = api.matter.getAccessoryState(
|
|
1551
|
-
uuid,
|
|
1552
|
-
api.matter.clusterNames.LevelControl
|
|
1553
|
-
)
|
|
1554
|
-
log.info(`Current brightness: ${levelState?.currentLevel}`)
|
|
1555
|
-
|
|
1556
|
-
// Check color mode
|
|
1557
|
-
const colorState = api.matter.getAccessoryState(
|
|
1558
|
-
uuid,
|
|
1559
|
-
api.matter.clusterNames.ColorControl
|
|
1560
|
-
)
|
|
1561
|
-
if (colorState?.colorMode === api.matter.types.ColorControl.ColorMode.ColorTemperatureMireds) {
|
|
1562
|
-
log.info(`Color temp: ${colorState.colorTemperatureMireds} mireds`)
|
|
1563
|
-
}
|
|
1564
|
-
```
|
|
1565
|
-
|
|
1566
|
-
**When to use:**
|
|
1567
|
-
- Reading state after plugin restart
|
|
1568
|
-
- Verifying current state before changes
|
|
1569
|
-
- Debugging and logging
|
|
1570
|
-
- Conditional logic based on state
|
|
1571
|
-
|
|
1572
|
-
**Note:** State is persisted across restarts automatically.
|
|
1573
|
-
|
|
1574
|
-
---
|
|
1575
|
-
|
|
1576
|
-
## Additional Resources
|
|
1577
|
-
|
|
1578
|
-
- **Matter.js Documentation**: https://github.com/project-chip/matter.js
|
|
1579
|
-
- **Matter Specification**: https://csa-iot.org/developer-resource/specifications-download-request/
|
|
1580
|
-
- **Homebridge Documentation**: https://developers.homebridge.io
|
|
1581
|
-
- **Example Devices**: See the `src/devices/` directory for complete working examples
|
|
1582
|
-
|
|
1583
|
-
---
|
|
1584
|
-
|
|
1585
|
-
## Appendix: Common Attribute Value Types
|
|
1586
|
-
|
|
1587
|
-
| Type | Range/Format | Common Uses | Notes |
|
|
1588
|
-
|-------------|---------------------------|--------------------------------------|------------------------------------------|
|
|
1589
|
-
| Boolean | `true`, `false` | Power states, binary sensors | Simple on/off values |
|
|
1590
|
-
| Uint8 | 0-254 | Brightness, hue, saturation | 0 often reserved, 254 = 100% |
|
|
1591
|
-
| Uint16 | 0-65535 | Color XY values, extended ranges | Full 16-bit range |
|
|
1592
|
-
| Enum | Varies | Modes, states | Use `api.matter.types` for type safety |
|
|
1593
|
-
| Temperature | Hundredths of degrees C | Thermostat, temperature sensors | 2500 = 25.00°C |
|
|
1594
|
-
| Percentage | 0-100 or 0-254 | Sensors (0-100), Controls (0-254) | Check device spec for range |
|
|
1595
|
-
| Mireds | 147-454 | Color temperature | Reciprocal megakelvin: 1000000 / kelvin |
|
|
1596
|
-
|
|
1597
|
-
---
|
|
1598
|
-
|
|
1599
|
-
## Appendix: Value Conversion Formulas
|
|
1600
|
-
|
|
1601
|
-
### Brightness (Matter ↔ Percentage)
|
|
1602
|
-
|
|
1603
|
-
```typescript
|
|
1604
|
-
// To Matter (1-254)
|
|
1605
|
-
const matterLevel = Math.max(1, Math.round((percent / 100) * 254))
|
|
1606
|
-
|
|
1607
|
-
// From Matter (to 0-100%)
|
|
1608
|
-
const percent = Math.round((matterLevel / 254) * 100)
|
|
1609
|
-
```
|
|
1610
|
-
|
|
1611
|
-
### Hue (Matter ↔ Degrees)
|
|
1612
|
-
|
|
1613
|
-
```typescript
|
|
1614
|
-
// To Matter (0-254)
|
|
1615
|
-
const matterHue = Math.round((degrees / 360) * 254)
|
|
1616
|
-
|
|
1617
|
-
// From Matter (to 0-360°)
|
|
1618
|
-
const degrees = Math.round((matterHue / 254) * 360)
|
|
1619
|
-
```
|
|
1620
|
-
|
|
1621
|
-
### Saturation (Matter ↔ Percentage)
|
|
1622
|
-
|
|
1623
|
-
```typescript
|
|
1624
|
-
// To Matter (0-254)
|
|
1625
|
-
const matterSat = Math.round((percent / 100) * 254)
|
|
1626
|
-
|
|
1627
|
-
// From Matter (to 0-100%)
|
|
1628
|
-
const percent = Math.round((matterSat / 254) * 100)
|
|
1629
|
-
```
|
|
1630
|
-
|
|
1631
|
-
### Color Temperature (Mireds ↔ Kelvin)
|
|
1632
|
-
|
|
1633
|
-
```typescript
|
|
1634
|
-
// To Mireds
|
|
1635
|
-
const mireds = Math.round(1000000 / kelvin)
|
|
1636
|
-
|
|
1637
|
-
// From Mireds
|
|
1638
|
-
const kelvin = Math.round(1000000 / mireds)
|
|
1639
|
-
```
|
|
1640
|
-
|
|
1641
|
-
### XY Color (Matter ↔ Float)
|
|
1642
|
-
|
|
1643
|
-
```typescript
|
|
1644
|
-
// To Matter (0-65535)
|
|
1645
|
-
const matterX = Math.round(floatX * 65535)
|
|
1646
|
-
const matterY = Math.round(floatY * 65535)
|
|
1647
|
-
|
|
1648
|
-
// From Matter (to 0.0-1.0)
|
|
1649
|
-
const floatX = matterX / 65535
|
|
1650
|
-
const floatY = matterY / 65535
|
|
1651
|
-
```
|
|
1652
|
-
|
|
1653
|
-
### Temperature (Matter ↔ Celsius)
|
|
1654
|
-
|
|
1655
|
-
```typescript
|
|
1656
|
-
// To Matter (hundredths)
|
|
1657
|
-
const matterTemp = Math.round(celsius * 100)
|
|
1658
|
-
|
|
1659
|
-
// From Matter
|
|
1660
|
-
const celsius = matterTemp / 100
|
|
1661
|
-
```
|
|
1662
|
-
|
|
1663
|
-
---
|
|
1664
|
-
|
|
1665
|
-
## Device Reference
|
|
1666
|
-
|
|
1667
|
-
This section documents all available Matter device types with their clusters, attributes, handlers, and usage examples.
|
|
1668
|
-
|
|
1669
|
-
### On/Off Light
|
|
1670
|
-
|
|
1671
|
-
| Property | Value |
|
|
1672
|
-
|--------------------------|--------------------------------------------------------|
|
|
1673
|
-
| **Device Type** | `api.matter.deviceTypes.OnOffLight` |
|
|
1674
|
-
| **Description** | A lighting device capable of being switched on or off. |
|
|
1675
|
-
| **Matter Specification** | § 4.1 |
|
|
1676
|
-
|
|
1677
|
-
#### Required Clusters
|
|
1678
|
-
|
|
1679
|
-
###### `OnOff` Cluster
|
|
1680
|
-
|
|
1681
|
-
Controls the power state of the light.
|
|
1682
|
-
|
|
1683
|
-
**Attributes**:
|
|
1684
|
-
|
|
1685
|
-
```typescript
|
|
1686
|
-
// All OnOff cluster attributes via api.matter
|
|
1687
|
-
const onOffAttrs = api.matter.clusters.OnOffCluster.attributes
|
|
1688
|
-
console.log(Object.keys(onOffAttrs))
|
|
1689
|
-
// Output: ['onOff', 'clusterRevision', 'featureMap', ...]
|
|
1690
|
-
```
|
|
1691
|
-
|
|
1692
|
-
| Attribute | Type | Range/Values | Description |
|
|
1693
|
-
|-----------|---------|-----------------|----------------------------------|
|
|
1694
|
-
| `onOff` | boolean | `true`, `false` | Power state (true=on, false=off) |
|
|
1695
|
-
|
|
1696
|
-
**Reading State**:
|
|
1697
|
-
|
|
1698
|
-
```typescript
|
|
1699
|
-
const isOn = accessory.clusters.onOff.onOff
|
|
1700
|
-
```
|
|
1701
|
-
|
|
1702
|
-
<details>
|
|
1703
|
-
<summary><strong>Handlers</strong></summary>
|
|
1704
|
-
|
|
1705
|
-
```typescript
|
|
1706
|
-
handlers: {
|
|
1707
|
-
onOff: {
|
|
1708
|
-
/**
|
|
1709
|
-
* Called when user turns light ON via Home app
|
|
1710
|
-
*/
|
|
1711
|
-
on: async () => {
|
|
1712
|
-
// Control your physical device
|
|
1713
|
-
await myLightAPI.turnOn()
|
|
1714
|
-
// State automatically updated by Homebridge
|
|
1715
|
-
},
|
|
1716
|
-
|
|
1717
|
-
/**
|
|
1718
|
-
* Called when user turns light OFF via Home app
|
|
1719
|
-
*/
|
|
1720
|
-
off: async () => {
|
|
1721
|
-
// Control your physical device
|
|
1722
|
-
await myLightAPI.turnOff()
|
|
1723
|
-
// State automatically updated by Homebridge
|
|
1724
|
-
},
|
|
1725
|
-
},
|
|
1726
|
-
}
|
|
1727
|
-
```
|
|
1728
|
-
|
|
1729
|
-
</details>
|
|
1730
|
-
|
|
1731
|
-
### Dimmable Light
|
|
1732
|
-
|
|
1733
|
-
| Property | Value |
|
|
1734
|
-
|--------------------------|--------------------------------------------------------|
|
|
1735
|
-
| **Device Type** | `api.matter.deviceTypes.DimmableLight` |
|
|
1736
|
-
| **Description** | A lighting device with on/off and brightness control. |
|
|
1737
|
-
| **Matter Specification** | § 4.2 |
|
|
1738
|
-
|
|
1739
|
-
#### Required Clusters
|
|
1740
|
-
|
|
1741
|
-
###### `OnOff` Cluster
|
|
1742
|
-
|
|
1743
|
-
Controls the power state of the light.
|
|
1744
|
-
|
|
1745
|
-
**Attributes**:
|
|
1746
|
-
|
|
1747
|
-
| Attribute | Type | Range/Values | Description |
|
|
1748
|
-
|-----------|---------|-----------------|----------------------------------|
|
|
1749
|
-
| `onOff` | boolean | `true`, `false` | Power state (true=on, false=off) |
|
|
1750
|
-
|
|
1751
|
-
**Reading State**:
|
|
1752
|
-
|
|
1753
|
-
```typescript
|
|
1754
|
-
const isOn = accessory.clusters.onOff.onOff
|
|
1755
|
-
```
|
|
1756
|
-
|
|
1757
|
-
###### `LevelControl` Cluster
|
|
1758
|
-
|
|
1759
|
-
Controls the brightness level of the light.
|
|
1760
|
-
|
|
1761
|
-
**Attributes**:
|
|
1762
|
-
|
|
1763
|
-
```typescript
|
|
1764
|
-
// All LevelControl cluster attributes via api.matter
|
|
1765
|
-
const levelAttrs = api.matter.clusters.LevelControlCluster.attributes
|
|
1766
|
-
console.log(Object.keys(levelAttrs))
|
|
1767
|
-
// Output: ['currentLevel', 'minLevel', 'maxLevel', 'onLevel', 'options', ...]
|
|
1768
|
-
```
|
|
1769
|
-
|
|
1770
|
-
| Attribute | Type | Range/Values | Description |
|
|
1771
|
-
|----------------|--------|--------------|--------------------------------------------------|
|
|
1772
|
-
| `currentLevel` | number | 1-254 | Current brightness (1 = 0.4%, 254 = 100%) |
|
|
1773
|
-
| `minLevel` | number | 1-254 | Minimum brightness level |
|
|
1774
|
-
| `maxLevel` | number | 1-254 | Maximum brightness level |
|
|
1775
|
-
| `onLevel` | number | 0-254 | Brightness when turned on (0 = restore previous) |
|
|
1776
|
-
|
|
1777
|
-
**Reading State**:
|
|
1778
|
-
|
|
1779
|
-
```typescript
|
|
1780
|
-
const level = accessory.clusters.levelControl.currentLevel
|
|
1781
|
-
const brightnessPercent = Math.round((level / 254) * 100)
|
|
1782
|
-
```
|
|
1783
|
-
|
|
1784
|
-
<details>
|
|
1785
|
-
<summary><strong>Handlers</strong></summary>
|
|
1786
|
-
|
|
1787
|
-
```typescript
|
|
1788
|
-
handlers: {
|
|
1789
|
-
onOff: {
|
|
1790
|
-
on: async () => {
|
|
1791
|
-
log.info('[Dimmable Light] Turning ON')
|
|
1792
|
-
await myLightAPI.turnOn()
|
|
1793
|
-
},
|
|
1794
|
-
|
|
1795
|
-
off: async () => {
|
|
1796
|
-
log.info('[Dimmable Light] Turning OFF')
|
|
1797
|
-
await myLightAPI.turnOff()
|
|
1798
|
-
},
|
|
1799
|
-
},
|
|
1800
|
-
|
|
1801
|
-
levelControl: {
|
|
1802
|
-
/**
|
|
1803
|
-
* Called when user adjusts brightness via Home app
|
|
1804
|
-
* Also called when turning on with specific brightness
|
|
1805
|
-
*/
|
|
1806
|
-
moveToLevelWithOnOff: async (request: MatterRequests.MoveToLevel) => {
|
|
1807
|
-
const { level, transitionTime } = request
|
|
1808
|
-
const brightnessPercent = Math.round((level / 254) * 100)
|
|
1809
|
-
|
|
1810
|
-
log.info(`[Dimmable Light] Setting brightness to ${brightnessPercent}%`)
|
|
1811
|
-
await myLightAPI.setBrightness(brightnessPercent, transitionTime)
|
|
1812
|
-
},
|
|
1813
|
-
},
|
|
1814
|
-
}
|
|
1815
|
-
```
|
|
1816
|
-
|
|
1817
|
-
</details>
|
|
1818
|
-
|
|
1819
|
-
---
|
|
1820
|
-
|
|
1821
|
-
### Color Temperature Light
|
|
1822
|
-
|
|
1823
|
-
| Property | Value |
|
|
1824
|
-
|--------------------------|---------------------------------------------------------------------------|
|
|
1825
|
-
| **Device Type** | `api.matter.deviceTypes.ColorTemperatureLight` |
|
|
1826
|
-
| **Description** | A lighting device with on/off, brightness, and color temperature control. |
|
|
1827
|
-
| **Matter Specification** | § 4.3 |
|
|
1828
|
-
|
|
1829
|
-
#### Required Clusters
|
|
1830
|
-
|
|
1831
|
-
###### `OnOff` Cluster
|
|
1832
|
-
|
|
1833
|
-
Controls the power state of the light.
|
|
1834
|
-
|
|
1835
|
-
**Attributes**:
|
|
1836
|
-
|
|
1837
|
-
| Attribute | Type | Range/Values | Description |
|
|
1838
|
-
|-----------|---------|-----------------|----------------------------------|
|
|
1839
|
-
| `onOff` | boolean | `true`, `false` | Power state (true=on, false=off) |
|
|
1840
|
-
|
|
1841
|
-
###### `LevelControl` Cluster
|
|
1842
|
-
|
|
1843
|
-
Controls the brightness level of the light.
|
|
1844
|
-
|
|
1845
|
-
**Attributes**:
|
|
1846
|
-
|
|
1847
|
-
| Attribute | Type | Range/Values | Description |
|
|
1848
|
-
|----------------|--------|--------------|--------------------------------------------------|
|
|
1849
|
-
| `currentLevel` | number | 1-254 | Current brightness (1 = 0.4%, 254 = 100%) |
|
|
1850
|
-
| `minLevel` | number | 1-254 | Minimum brightness level |
|
|
1851
|
-
| `maxLevel` | number | 1-254 | Maximum brightness level |
|
|
1852
|
-
|
|
1853
|
-
###### `ColorControl` Cluster
|
|
1854
|
-
|
|
1855
|
-
Controls the color temperature of the light.
|
|
1856
|
-
|
|
1857
|
-
**Attributes**:
|
|
1858
|
-
|
|
1859
|
-
```typescript
|
|
1860
|
-
// All ColorControl cluster attributes via api.matter
|
|
1861
|
-
const colorAttrs = api.matter.clusters.ColorControlCluster.attributes
|
|
1862
|
-
console.log(Object.keys(colorAttrs))
|
|
1863
|
-
```
|
|
1864
|
-
|
|
1865
|
-
| Attribute | Type | Range/Values | Description |
|
|
1866
|
-
|---------------------------------|--------|--------------|-------------------------------------------------------|
|
|
1867
|
-
| `colorMode` | number | 0-2 | Current color mode (2 = Color Temperature) |
|
|
1868
|
-
| `colorTemperatureMireds` | number | 147-454 | Color temp in mireds (reciprocal megakelvin) |
|
|
1869
|
-
| `colorTempPhysicalMinMireds` | number | 147-500 | Coolest temperature supported (e.g., 147 = ~6800K) |
|
|
1870
|
-
| `colorTempPhysicalMaxMireds` | number | 147-500 | Warmest temperature supported (e.g., 454 = ~2200K) |
|
|
1871
|
-
|
|
1872
|
-
**Reading State**:
|
|
1873
|
-
|
|
1874
|
-
```typescript
|
|
1875
|
-
const mireds = accessory.clusters.colorControl.colorTemperatureMireds
|
|
1876
|
-
const kelvin = Math.round(1000000 / mireds)
|
|
1877
|
-
```
|
|
1878
|
-
|
|
1879
|
-
**Value Conversions**:
|
|
1880
|
-
|
|
1881
|
-
```typescript
|
|
1882
|
-
// Kelvin to Mireds
|
|
1883
|
-
const mireds = Math.round(1000000 / kelvin)
|
|
1884
|
-
|
|
1885
|
-
// Mireds to Kelvin
|
|
1886
|
-
const kelvin = Math.round(1000000 / mireds)
|
|
1887
|
-
```
|
|
1888
|
-
|
|
1889
|
-
<details>
|
|
1890
|
-
<summary><strong>Handlers</strong></summary>
|
|
1891
|
-
|
|
1892
|
-
```typescript
|
|
1893
|
-
handlers: {
|
|
1894
|
-
onOff: {
|
|
1895
|
-
on: async () => {
|
|
1896
|
-
log.info('[CCT Light] Turning ON')
|
|
1897
|
-
await myLightAPI.turnOn()
|
|
1898
|
-
},
|
|
1899
|
-
|
|
1900
|
-
off: async () => {
|
|
1901
|
-
log.info('[CCT Light] Turning OFF')
|
|
1902
|
-
await myLightAPI.turnOff()
|
|
1903
|
-
},
|
|
1904
|
-
},
|
|
1905
|
-
|
|
1906
|
-
levelControl: {
|
|
1907
|
-
moveToLevelWithOnOff: async (request: MatterRequests.MoveToLevel) => {
|
|
1908
|
-
const { level } = request
|
|
1909
|
-
const brightnessPercent = Math.round((level / 254) * 100)
|
|
1910
|
-
|
|
1911
|
-
log.info(`[CCT Light] Setting brightness to ${brightnessPercent}%`)
|
|
1912
|
-
await myLightAPI.setBrightness(brightnessPercent)
|
|
1913
|
-
},
|
|
1914
|
-
},
|
|
1915
|
-
|
|
1916
|
-
colorControl: {
|
|
1917
|
-
/**
|
|
1918
|
-
* Called when user adjusts color temperature via Home app
|
|
1919
|
-
*/
|
|
1920
|
-
moveToColorTemperatureLogic: async (request: { targetMireds: number, transitionTime: number }) => {
|
|
1921
|
-
const { targetMireds, transitionTime } = request
|
|
1922
|
-
const kelvin = Math.round(1000000 / targetMireds)
|
|
1923
|
-
|
|
1924
|
-
log.info(`[CCT Light] Setting color temp to ${kelvin}K (${targetMireds} mireds)`)
|
|
1925
|
-
await myLightAPI.setColorTemperature(kelvin, transitionTime)
|
|
1926
|
-
},
|
|
1927
|
-
},
|
|
1928
|
-
}
|
|
1929
|
-
```
|
|
1930
|
-
|
|
1931
|
-
</details>
|
|
1932
|
-
|
|
1933
|
-
---
|
|
1934
|
-
|
|
1935
|
-
### Color Light
|
|
1936
|
-
|
|
1937
|
-
| Property | Value |
|
|
1938
|
-
|--------------------------|--------------------------------------------------------------------------------|
|
|
1939
|
-
| **Device Type** | `api.matter.deviceTypes.ExtendedColorLight` |
|
|
1940
|
-
| **Description** | A lighting device with on/off, brightness, and color (Hue/Saturation) control. |
|
|
1941
|
-
| **Matter Specification** | § 4.4 |
|
|
1942
|
-
|
|
1943
|
-
#### Required Clusters
|
|
1944
|
-
|
|
1945
|
-
###### `OnOff` Cluster
|
|
1946
|
-
|
|
1947
|
-
Controls the power state of the light.
|
|
1948
|
-
|
|
1949
|
-
**Attributes**:
|
|
1950
|
-
|
|
1951
|
-
| Attribute | Type | Range/Values | Description |
|
|
1952
|
-
|-----------|---------|-----------------|----------------------------------|
|
|
1953
|
-
| `onOff` | boolean | `true`, `false` | Power state (true=on, false=off) |
|
|
1954
|
-
|
|
1955
|
-
###### `LevelControl` Cluster
|
|
1956
|
-
|
|
1957
|
-
Controls the brightness level of the light.
|
|
1958
|
-
|
|
1959
|
-
**Attributes**:
|
|
1960
|
-
|
|
1961
|
-
| Attribute | Type | Range/Values | Description |
|
|
1962
|
-
|----------------|--------|--------------|--------------------------------------------------|
|
|
1963
|
-
| `currentLevel` | number | 1-254 | Current brightness (1 = 0.4%, 254 = 100%) |
|
|
1964
|
-
| `minLevel` | number | 1-254 | Minimum brightness level |
|
|
1965
|
-
| `maxLevel` | number | 1-254 | Maximum brightness level |
|
|
1966
|
-
|
|
1967
|
-
###### `ColorControl` Cluster
|
|
1968
|
-
|
|
1969
|
-
Controls the color (Hue/Saturation or XY) of the light.
|
|
1970
|
-
|
|
1971
|
-
**Attributes**:
|
|
1972
|
-
|
|
1973
|
-
| Attribute | Type | Range/Values | Description |
|
|
1974
|
-
|---------------------|--------|--------------|-------------------------------------|
|
|
1975
|
-
| `colorMode` | number | 0-2 | Current color mode (0 = HS, 1 = XY) |
|
|
1976
|
-
| `currentHue` | number | 0-254 | Current hue (maps to 0-360 degrees) |
|
|
1977
|
-
| `currentSaturation` | number | 0-254 | Current saturation (maps to 0-100%) |
|
|
1978
|
-
| `currentX` | number | 0-65535 | CIE 1931 x coordinate |
|
|
1979
|
-
| `currentY` | number | 0-65535 | CIE 1931 y coordinate |
|
|
1980
|
-
|
|
1981
|
-
**Reading State**:
|
|
1982
|
-
|
|
1983
|
-
```typescript
|
|
1984
|
-
const hue = accessory.clusters.colorControl.currentHue
|
|
1985
|
-
const saturation = accessory.clusters.colorControl.currentSaturation
|
|
1986
|
-
|
|
1987
|
-
// Convert to degrees/percentage
|
|
1988
|
-
const hueDegrees = Math.round((hue / 254) * 360)
|
|
1989
|
-
const saturationPercent = Math.round((saturation / 254) * 100)
|
|
1990
|
-
```
|
|
1991
|
-
|
|
1992
|
-
**Value Conversions**:
|
|
1993
|
-
|
|
1994
|
-
```typescript
|
|
1995
|
-
// Hue: Degrees (0-360) to Matter (0-254)
|
|
1996
|
-
const matterHue = Math.round((degrees / 360) * 254)
|
|
1997
|
-
const degrees = Math.round((matterHue / 254) * 360)
|
|
1998
|
-
|
|
1999
|
-
// Saturation: Percent (0-100) to Matter (0-254)
|
|
2000
|
-
const matterSat = Math.round((percent / 100) * 254)
|
|
2001
|
-
const percent = Math.round((matterSat / 254) * 100)
|
|
2002
|
-
|
|
2003
|
-
// XY: Float (0.0-1.0) to Matter (0-65535)
|
|
2004
|
-
const matterX = Math.round(floatX * 65535)
|
|
2005
|
-
const floatX = matterX / 65535
|
|
2006
|
-
```
|
|
2007
|
-
|
|
2008
|
-
<details>
|
|
2009
|
-
<summary><strong>Handlers</strong></summary>
|
|
2010
|
-
|
|
2011
|
-
```typescript
|
|
2012
|
-
handlers: {
|
|
2013
|
-
onOff: {
|
|
2014
|
-
on: async () => {
|
|
2015
|
-
log.info('[Color Light] Turning ON')
|
|
2016
|
-
await myLightAPI.turnOn()
|
|
2017
|
-
},
|
|
2018
|
-
|
|
2019
|
-
off: async () => {
|
|
2020
|
-
log.info('[Color Light] Turning OFF')
|
|
2021
|
-
await myLightAPI.turnOff()
|
|
2022
|
-
},
|
|
2023
|
-
},
|
|
2024
|
-
|
|
2025
|
-
levelControl: {
|
|
2026
|
-
moveToLevelWithOnOff: async (request: MatterRequests.MoveToLevel) => {
|
|
2027
|
-
const { level } = request
|
|
2028
|
-
const brightnessPercent = Math.round((level / 254) * 100)
|
|
2029
|
-
|
|
2030
|
-
log.info(`[Color Light] Setting brightness to ${brightnessPercent}%`)
|
|
2031
|
-
await myLightAPI.setBrightness(brightnessPercent)
|
|
2032
|
-
},
|
|
2033
|
-
},
|
|
2034
|
-
|
|
2035
|
-
colorControl: {
|
|
2036
|
-
/**
|
|
2037
|
-
* Called when user adjusts color via XY coordinates in Home app
|
|
2038
|
-
*/
|
|
2039
|
-
moveToColorLogic: async (request: { targetX: number, targetY: number, transitionTime: number }) => {
|
|
2040
|
-
const { targetX, targetY, transitionTime } = request
|
|
2041
|
-
const xFloat = (targetX / 65535).toFixed(4)
|
|
2042
|
-
const yFloat = (targetY / 65535).toFixed(4)
|
|
2043
|
-
|
|
2044
|
-
log.info(`[Color Light] Setting XY color to (${xFloat}, ${yFloat})`)
|
|
2045
|
-
await myLightAPI.setColorXY(xFloat, yFloat, transitionTime)
|
|
2046
|
-
},
|
|
2047
|
-
|
|
2048
|
-
/**
|
|
2049
|
-
* Called when user adjusts color via Hue/Saturation in Home app
|
|
2050
|
-
*/
|
|
2051
|
-
moveToHueAndSaturationLogic: async (request: { targetHue: number, targetSaturation: number, transitionTime: number }) => {
|
|
2052
|
-
const { targetHue, targetSaturation, transitionTime } = request
|
|
2053
|
-
const hueDegrees = Math.round((targetHue / 254) * 360)
|
|
2054
|
-
const saturationPercent = Math.round((targetSaturation / 254) * 100)
|
|
2055
|
-
|
|
2056
|
-
log.info(`[Color Light] Setting color to ${hueDegrees}°, ${saturationPercent}%`)
|
|
2057
|
-
await myLightAPI.setColorHS(hueDegrees, saturationPercent, transitionTime)
|
|
2058
|
-
},
|
|
2059
|
-
},
|
|
2060
|
-
}
|
|
2061
|
-
```
|
|
2062
|
-
|
|
2063
|
-
</details>
|
|
2064
|
-
|
|
2065
|
-
---
|
|
2066
|
-
|
|
2067
|
-
### Extended Color Light
|
|
2068
|
-
|
|
2069
|
-
| Property | Value |
|
|
2070
|
-
|--------------------------|---------------------------------------------------------------------------------------------------|
|
|
2071
|
-
| **Device Type** | `api.matter.deviceTypes.ExtendedColorLight` |
|
|
2072
|
-
| **Description** | A lighting device with on/off, brightness, color (Hue/Saturation), and color temperature control. |
|
|
2073
|
-
| **Matter Specification** | § 4.4 |
|
|
2074
|
-
|
|
2075
|
-
#### Required Clusters
|
|
2076
|
-
|
|
2077
|
-
###### `OnOff` Cluster
|
|
2078
|
-
|
|
2079
|
-
Controls the power state of the light.
|
|
2080
|
-
|
|
2081
|
-
**Attributes**:
|
|
2082
|
-
|
|
2083
|
-
| Attribute | Type | Range/Values | Description |
|
|
2084
|
-
|-----------|---------|-----------------|----------------------------------|
|
|
2085
|
-
| `onOff` | boolean | `true`, `false` | Power state (true=on, false=off) |
|
|
2086
|
-
|
|
2087
|
-
###### `LevelControl` Cluster
|
|
2088
|
-
|
|
2089
|
-
Controls the brightness level of the light.
|
|
2090
|
-
|
|
2091
|
-
**Attributes**:
|
|
2092
|
-
|
|
2093
|
-
| Attribute | Type | Range/Values | Description |
|
|
2094
|
-
|----------------|--------|--------------|--------------------------------------------------|
|
|
2095
|
-
| `currentLevel` | number | 1-254 | Current brightness (1 = 0.4%, 254 = 100%) |
|
|
2096
|
-
| `minLevel` | number | 1-254 | Minimum brightness level |
|
|
2097
|
-
| `maxLevel` | number | 1-254 | Maximum brightness level |
|
|
2098
|
-
|
|
2099
|
-
###### `ColorControl` Cluster
|
|
2100
|
-
|
|
2101
|
-
Controls both color (Hue/Saturation or XY) and color temperature of the light.
|
|
2102
|
-
|
|
2103
|
-
When updating state for Extended Color Light (Flow B), always update the `colorMode` attribute along with the color/temperature values to indicate which mode is active.
|
|
2104
|
-
|
|
2105
|
-
**Attributes**:
|
|
2106
|
-
|
|
2107
|
-
| Attribute | Type | Range/Values | Description |
|
|
2108
|
-
|---------------------------------|--------|--------------|-------------------------------------------------------|
|
|
2109
|
-
| `colorMode` | number | 0-2 | Current color mode (0 = HS, 1 = XY, 2 = ColorTemp) |
|
|
2110
|
-
| `currentHue` | number | 0-254 | Current hue (maps to 0-360 degrees) |
|
|
2111
|
-
| `currentSaturation` | number | 0-254 | Current saturation (maps to 0-100%) |
|
|
2112
|
-
| `currentX` | number | 0-65535 | CIE 1931 x coordinate |
|
|
2113
|
-
| `currentY` | number | 0-65535 | CIE 1931 y coordinate |
|
|
2114
|
-
| `colorTemperatureMireds` | number | 147-454 | Color temp in mireds (when in ColorTemp mode) |
|
|
2115
|
-
| `colorTempPhysicalMinMireds` | number | 147-500 | Coolest temperature supported |
|
|
2116
|
-
| `colorTempPhysicalMaxMireds` | number | 147-500 | Warmest temperature supported |
|
|
2117
|
-
|
|
2118
|
-
**Reading State**:
|
|
2119
|
-
|
|
2120
|
-
```typescript
|
|
2121
|
-
// Check current mode
|
|
2122
|
-
const mode = accessory.clusters.colorControl.colorMode
|
|
2123
|
-
const ColorMode = api.matter.types.ColorControl.ColorMode
|
|
2124
|
-
|
|
2125
|
-
if (mode === ColorMode.CurrentHueAndCurrentSaturation || mode === ColorMode.CurrentXAndCurrentY) {
|
|
2126
|
-
// Light is in color mode
|
|
2127
|
-
const hue = accessory.clusters.colorControl.currentHue
|
|
2128
|
-
const sat = accessory.clusters.colorControl.currentSaturation
|
|
2129
|
-
} else if (mode === ColorMode.ColorTemperatureMireds) {
|
|
2130
|
-
// Light is in white/CCT mode
|
|
2131
|
-
const mireds = accessory.clusters.colorControl.colorTemperatureMireds
|
|
2132
|
-
const kelvin = Math.round(1000000 / mireds)
|
|
2133
|
-
}
|
|
2134
|
-
```
|
|
2135
|
-
|
|
2136
|
-
<details>
|
|
2137
|
-
<summary><strong>Handlers</strong></summary>
|
|
2138
|
-
|
|
2139
|
-
```typescript
|
|
2140
|
-
handlers: {
|
|
2141
|
-
onOff: {
|
|
2142
|
-
on: async () => {
|
|
2143
|
-
log.info('[Extended Color Light] Turning ON')
|
|
2144
|
-
await myLightAPI.turnOn()
|
|
2145
|
-
},
|
|
2146
|
-
|
|
2147
|
-
off: async () => {
|
|
2148
|
-
log.info('[Extended Color Light] Turning OFF')
|
|
2149
|
-
await myLightAPI.turnOff()
|
|
2150
|
-
},
|
|
2151
|
-
},
|
|
2152
|
-
|
|
2153
|
-
levelControl: {
|
|
2154
|
-
moveToLevelWithOnOff: async (request: MatterRequests.MoveToLevel) => {
|
|
2155
|
-
const { level } = request
|
|
2156
|
-
const brightnessPercent = Math.round((level / 254) * 100)
|
|
2157
|
-
|
|
2158
|
-
log.info(`[Extended Color Light] Setting brightness to ${brightnessPercent}%`)
|
|
2159
|
-
await myLightAPI.setBrightness(brightnessPercent)
|
|
2160
|
-
},
|
|
2161
|
-
},
|
|
2162
|
-
|
|
2163
|
-
colorControl: {
|
|
2164
|
-
/**
|
|
2165
|
-
* Called when user adjusts color via XY coordinates in Home app
|
|
2166
|
-
*/
|
|
2167
|
-
moveToColorLogic: async (request: { targetX: number, targetY: number, transitionTime: number }) => {
|
|
2168
|
-
const { targetX, targetY, transitionTime } = request
|
|
2169
|
-
const xFloat = (targetX / 65535).toFixed(4)
|
|
2170
|
-
const yFloat = (targetY / 65535).toFixed(4)
|
|
2171
|
-
|
|
2172
|
-
log.info(`[Extended Color Light] Setting XY color to (${xFloat}, ${yFloat})`)
|
|
2173
|
-
await myLightAPI.setColorXY(xFloat, yFloat, transitionTime)
|
|
2174
|
-
},
|
|
2175
|
-
|
|
2176
|
-
/**
|
|
2177
|
-
* Called when user adjusts color via Hue/Saturation in Home app
|
|
2178
|
-
*/
|
|
2179
|
-
moveToHueAndSaturationLogic: async (request: { targetHue: number, targetSaturation: number, transitionTime: number }) => {
|
|
2180
|
-
const { targetHue, targetSaturation, transitionTime } = request
|
|
2181
|
-
const hueDegrees = Math.round((targetHue / 254) * 360)
|
|
2182
|
-
const saturationPercent = Math.round((targetSaturation / 254) * 100)
|
|
2183
|
-
|
|
2184
|
-
log.info(`[Extended Color Light] Setting color to ${hueDegrees}°, ${saturationPercent}%`)
|
|
2185
|
-
await myLightAPI.setColorHS(hueDegrees, saturationPercent, transitionTime)
|
|
2186
|
-
},
|
|
2187
|
-
|
|
2188
|
-
/**
|
|
2189
|
-
* Called when user adjusts color temperature via Home app
|
|
2190
|
-
*/
|
|
2191
|
-
moveToColorTemperatureLogic: async (request: { targetMireds: number, transitionTime: number }) => {
|
|
2192
|
-
const { targetMireds, transitionTime } = request
|
|
2193
|
-
const kelvin = Math.round(1000000 / targetMireds)
|
|
2194
|
-
|
|
2195
|
-
log.info(`[Extended Color Light] Setting color temp to ${kelvin}K (${targetMireds} mireds)`)
|
|
2196
|
-
await myLightAPI.setColorTemperature(kelvin, transitionTime)
|
|
2197
|
-
},
|
|
2198
|
-
},
|
|
2199
|
-
}
|
|
2200
|
-
```
|
|
2201
|
-
|
|
2202
|
-
</details>
|
|
2203
|
-
|
|
2204
|
-
---
|
|
2205
|
-
|
|
2206
|
-
### On/Off Outlet
|
|
2207
|
-
|
|
2208
|
-
| Property | Value |
|
|
2209
|
-
|--------------------------|---------------------------------------------------------------------------|
|
|
2210
|
-
| **Device Type** | `api.matter.deviceTypes.OnOffOutlet` |
|
|
2211
|
-
| **Description** | A plug-in unit (smart plug) capable of being switched on or off. |
|
|
2212
|
-
| **Matter Specification** | § 5.1 |
|
|
2213
|
-
|
|
2214
|
-
#### Required Clusters
|
|
2215
|
-
|
|
2216
|
-
###### `OnOff` Cluster
|
|
2217
|
-
|
|
2218
|
-
Controls the power state of the outlet.
|
|
2219
|
-
|
|
2220
|
-
**Attributes**:
|
|
2221
|
-
|
|
2222
|
-
| Attribute | Type | Range/Values | Description |
|
|
2223
|
-
|-----------|---------|-----------------|----------------------------------|
|
|
2224
|
-
| `onOff` | boolean | `true`, `false` | Power state (true=on, false=off) |
|
|
2225
|
-
|
|
2226
|
-
**Reading State**:
|
|
2227
|
-
|
|
2228
|
-
```typescript
|
|
2229
|
-
const isOn = accessory.clusters.onOff.onOff
|
|
2230
|
-
```
|
|
2231
|
-
|
|
2232
|
-
<details>
|
|
2233
|
-
<summary><strong>Handlers</strong></summary>
|
|
2234
|
-
|
|
2235
|
-
```typescript
|
|
2236
|
-
handlers: {
|
|
2237
|
-
onOff: {
|
|
2238
|
-
/**
|
|
2239
|
-
* Called when user turns outlet ON via Home app
|
|
2240
|
-
*/
|
|
2241
|
-
on: async () => {
|
|
2242
|
-
// Control your physical device
|
|
2243
|
-
await myOutletAPI.turnOn()
|
|
2244
|
-
// State automatically updated by Homebridge
|
|
2245
|
-
},
|
|
2246
|
-
|
|
2247
|
-
/**
|
|
2248
|
-
* Called when user turns outlet OFF via Home app
|
|
2249
|
-
*/
|
|
2250
|
-
off: async () => {
|
|
2251
|
-
// Control your physical device
|
|
2252
|
-
await myOutletAPI.turnOff()
|
|
2253
|
-
// State automatically updated by Homebridge
|
|
2254
|
-
},
|
|
2255
|
-
},
|
|
2256
|
-
}
|
|
2257
|
-
```
|
|
2258
|
-
|
|
2259
|
-
</details>
|
|
2260
|
-
|
|
2261
|
-
---
|
|
2262
|
-
|
|
2263
|
-
### On/Off Switch
|
|
2264
|
-
|
|
2265
|
-
| Property | Value |
|
|
2266
|
-
|--------------------------|---------------------------------------------------------------------------|
|
|
2267
|
-
| **Device Type** | `api.matter.deviceTypes.OnOffSwitch` |
|
|
2268
|
-
| **Description** | A switch capable of being switched on or off. |
|
|
2269
|
-
| **Matter Specification** | § 6.1 |
|
|
2270
|
-
|
|
2271
|
-
#### Required Clusters
|
|
2272
|
-
|
|
2273
|
-
###### `OnOff` Cluster
|
|
2274
|
-
|
|
2275
|
-
Controls the power state of the switch.
|
|
2276
|
-
|
|
2277
|
-
**Attributes**:
|
|
2278
|
-
|
|
2279
|
-
| Attribute | Type | Range/Values | Description |
|
|
2280
|
-
|-----------|---------|-----------------|----------------------------------|
|
|
2281
|
-
| `onOff` | boolean | `true`, `false` | Power state (true=on, false=off) |
|
|
2282
|
-
|
|
2283
|
-
**Reading State**:
|
|
2284
|
-
|
|
2285
|
-
```typescript
|
|
2286
|
-
const isOn = accessory.clusters.onOff.onOff
|
|
2287
|
-
```
|
|
2288
|
-
|
|
2289
|
-
<details>
|
|
2290
|
-
<summary><strong>Handlers</strong></summary>
|
|
2291
|
-
|
|
2292
|
-
```typescript
|
|
2293
|
-
handlers: {
|
|
2294
|
-
onOff: {
|
|
2295
|
-
/**
|
|
2296
|
-
* Called when user turns switch ON via Home app
|
|
2297
|
-
*/
|
|
2298
|
-
on: async () => {
|
|
2299
|
-
// Control your physical device
|
|
2300
|
-
await mySwitchAPI.turnOn()
|
|
2301
|
-
// State automatically updated by Homebridge
|
|
2302
|
-
},
|
|
2303
|
-
|
|
2304
|
-
/**
|
|
2305
|
-
* Called when user turns switch OFF via Home app
|
|
2306
|
-
*/
|
|
2307
|
-
off: async () => {
|
|
2308
|
-
// Control your physical device
|
|
2309
|
-
await mySwitchAPI.turnOff()
|
|
2310
|
-
// State automatically updated by Homebridge
|
|
2311
|
-
},
|
|
2312
|
-
},
|
|
2313
|
-
}
|
|
2314
|
-
```
|
|
2315
|
-
|
|
2316
|
-
</details>
|
|
2317
|
-
|
|
2318
|
-
---
|
|
2319
|
-
|
|
2320
|
-
### Contact Sensor
|
|
2321
|
-
|
|
2322
|
-
| Property | Value |
|
|
2323
|
-
|--------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------|
|
|
2324
|
-
| **Device Type** | `api.matter.deviceTypes.ContactSensor` |
|
|
2325
|
-
| **Description** | A contact sensor that detects open/closed state (e.g., door sensor, window sensor). |
|
|
2326
|
-
| **Matter Specification** | § 7.1 |
|
|
2327
|
-
|
|
2328
|
-
#### Required Clusters
|
|
2329
|
-
|
|
2330
|
-
###### `BooleanState` Cluster
|
|
2331
|
-
|
|
2332
|
-
Represents the contact sensor state using a boolean value.
|
|
2333
|
-
|
|
2334
|
-
**IMPORTANT - Inverted Semantics**: The BooleanState cluster for contact sensors uses **inverted logic**:
|
|
2335
|
-
- `true` = Contact **closed** / Normal state (door/window is closed)
|
|
2336
|
-
- `false` = Contact **open** / Triggered state (door/window is open)
|
|
2337
|
-
|
|
2338
|
-
This is opposite from intuitive expectation, so you must invert values when updating state.
|
|
2339
|
-
|
|
2340
|
-
**Attributes**:
|
|
2341
|
-
|
|
2342
|
-
| Attribute | Type | Range/Values | Description |
|
|
2343
|
-
|--------------|---------|-----------------|----------------------------------------------------------|
|
|
2344
|
-
| `stateValue` | boolean | `true`, `false` | Contact state (true=closed/normal, false=open/triggered) |
|
|
2345
|
-
|
|
2346
|
-
**Reading State**:
|
|
2347
|
-
|
|
2348
|
-
```typescript
|
|
2349
|
-
const stateValue = accessory.clusters.booleanState.stateValue
|
|
2350
|
-
// true = closed/normal, false = open/triggered
|
|
2351
|
-
const isOpen = !stateValue // Invert to get intuitive open/closed
|
|
2352
|
-
```
|
|
2353
|
-
|
|
2354
|
-
**Updating State** (Flow B):
|
|
2355
|
-
|
|
2356
|
-
```typescript
|
|
2357
|
-
// When your physical sensor reports state change
|
|
2358
|
-
function updateContactState(isOpen: boolean) {
|
|
2359
|
-
// IMPORTANT: Invert the value!
|
|
2360
|
-
// Matter BooleanState: false = open/triggered, true = closed/normal
|
|
2361
|
-
api.matter.updateAccessoryState(
|
|
2362
|
-
uuid,
|
|
2363
|
-
api.matter.clusterNames.BooleanState,
|
|
2364
|
-
{ stateValue: !isOpen } // Invert!
|
|
2365
|
-
)
|
|
2366
|
-
|
|
2367
|
-
log.info(`Contact state: ${isOpen ? 'OPEN' : 'CLOSED'}`)
|
|
2368
|
-
}
|
|
2369
|
-
|
|
2370
|
-
// Example: Door opened
|
|
2371
|
-
updateContactState(true) // Sends stateValue: false to Matter
|
|
2372
|
-
|
|
2373
|
-
// Example: Door closed
|
|
2374
|
-
updateContactState(false) // Sends stateValue: true to Matter
|
|
2375
|
-
```
|
|
2376
|
-
|
|
2377
|
-
**Initial State**:
|
|
2378
|
-
|
|
2379
|
-
```typescript
|
|
2380
|
-
clusters: {
|
|
2381
|
-
booleanState: {
|
|
2382
|
-
stateValue: true, // true = closed/normal (safe default)
|
|
2383
|
-
},
|
|
2384
|
-
}
|
|
2385
|
-
```
|
|
2386
|
-
|
|
2387
|
-
**Handler**: Contact sensors are read-only (no handlers needed).
|
|
2388
|
-
|
|
2389
|
-
---
|
|
2390
|
-
|
|
2391
|
-
### Occupancy Sensor
|
|
2392
|
-
|
|
2393
|
-
| Property | Value |
|
|
2394
|
-
|--------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------|
|
|
2395
|
-
| **Device Type** | `api.matter.deviceTypes.MotionSensor` (with OccupancySensing cluster) |
|
|
2396
|
-
| **Description** | A sensor that detects occupancy/motion using PIR, ultrasonic, or physical contact methods. |
|
|
2397
|
-
| **Matter Specification** | § 7.3 |
|
|
2398
|
-
|
|
2399
|
-
**Note**: Matter.js API calls this device type "MotionSensor" but it's actually an **Occupancy Sensor** - this is how it appears in Apple Home and other Matter controllers.
|
|
2400
|
-
|
|
2401
|
-
#### Required Clusters
|
|
2402
|
-
|
|
2403
|
-
###### `OccupancySensing` Cluster
|
|
2404
|
-
|
|
2405
|
-
Detects occupancy using various sensing methods.
|
|
2406
|
-
|
|
2407
|
-
**Attributes**:
|
|
2408
|
-
|
|
2409
|
-
| Attribute | Type | Range/Values | Description |
|
|
2410
|
-
|-----------------------------|---------|-----------------|-------------------------------------------------|
|
|
2411
|
-
| `occupancy.occupied` | boolean | `true`, `false` | Occupancy detected (true=occupied, false=clear) |
|
|
2412
|
-
| `occupancySensorType` | number | 0-2 | Sensor type (0=PIR, 1=Ultrasonic, 2=Physical) |
|
|
2413
|
-
| `occupancySensorTypeBitmap` | object | See below | Bitmap of supported sensor types |
|
|
2414
|
-
|
|
2415
|
-
**Occupancy Sensor Type Bitmap**:
|
|
2416
|
-
|
|
2417
|
-
```typescript
|
|
2418
|
-
{
|
|
2419
|
-
pir: true, // Passive infrared
|
|
2420
|
-
ultrasonic: false, // Ultrasonic
|
|
2421
|
-
physicalContact: false, // Physical contact
|
|
2422
|
-
}
|
|
2423
|
-
```
|
|
2424
|
-
|
|
2425
|
-
**Reading State**:
|
|
2426
|
-
|
|
2427
|
-
```typescript
|
|
2428
|
-
const isOccupied = accessory.clusters.occupancySensing.occupancy.occupied
|
|
2429
|
-
```
|
|
2430
|
-
|
|
2431
|
-
**Updating State** (Flow B):
|
|
2432
|
-
|
|
2433
|
-
```typescript
|
|
2434
|
-
// When your physical sensor detects motion/occupancy
|
|
2435
|
-
mqttClient.on('message', (topic, message) => {
|
|
2436
|
-
const detected = message.toString() === 'motion'
|
|
2437
|
-
|
|
2438
|
-
api.matter.updateAccessoryState(
|
|
2439
|
-
uuid,
|
|
2440
|
-
api.matter.clusterNames.OccupancySensing,
|
|
2441
|
-
{ occupancy: { occupied: detected } }
|
|
2442
|
-
)
|
|
2443
|
-
|
|
2444
|
-
log.info(`Occupancy: ${detected ? 'detected' : 'clear'}`)
|
|
2445
|
-
})
|
|
2446
|
-
```
|
|
2447
|
-
|
|
2448
|
-
**Initial State with PIR Sensor**:
|
|
2449
|
-
|
|
2450
|
-
```typescript
|
|
2451
|
-
// Configure OccupancySensor with PIR feature
|
|
2452
|
-
const OccupancySensingServer = api.matter.deviceTypes.MotionSensor.requirements.OccupancySensingServer
|
|
2453
|
-
const OccupancySensorWithPIR = api.matter.deviceTypes.MotionSensor.with(
|
|
2454
|
-
OccupancySensingServer.with('PassiveInfrared'),
|
|
2455
|
-
)
|
|
2456
|
-
|
|
2457
|
-
{
|
|
2458
|
-
deviceType: OccupancySensorWithPIR,
|
|
2459
|
-
clusters: {
|
|
2460
|
-
occupancySensing: {
|
|
2461
|
-
occupancy: {
|
|
2462
|
-
occupied: false, // No occupancy detected initially
|
|
2463
|
-
},
|
|
2464
|
-
occupancySensorType: 0, // PIR
|
|
2465
|
-
occupancySensorTypeBitmap: {
|
|
2466
|
-
pir: true,
|
|
2467
|
-
ultrasonic: false,
|
|
2468
|
-
physicalContact: false,
|
|
2469
|
-
},
|
|
2470
|
-
},
|
|
2471
|
-
},
|
|
2472
|
-
}
|
|
2473
|
-
```
|
|
2474
|
-
|
|
2475
|
-
**Handler**: Occupancy sensors are read-only (no handlers needed).
|
|
2476
|
-
|
|
2477
|
-
---
|
|
2478
|
-
|
|
2479
|
-
### Window Covering
|
|
2480
|
-
|
|
2481
|
-
| Property | Value |
|
|
2482
|
-
|--------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------|
|
|
2483
|
-
| **Device Type** | `api.matter.deviceTypes.WindowCovering` |
|
|
2484
|
-
| **Description** | A motorized window covering with lift position control (e.g., blinds, shades, curtains). |
|
|
2485
|
-
| **Matter Specification** | § 8.3 |
|
|
2486
|
-
|
|
2487
|
-
#### Required Clusters
|
|
2488
|
-
|
|
2489
|
-
###### `WindowCovering` Cluster
|
|
2490
|
-
|
|
2491
|
-
Controls lift position (and optionally tilt) of window coverings.
|
|
2492
|
-
|
|
2493
|
-
**IMPORTANT - Inverted Position Semantics**: The WindowCovering cluster uses **inverted percentage** values:
|
|
2494
|
-
- `0` = Fully **open** (100% open)
|
|
2495
|
-
- `10000` = Fully **closed** (0% open)
|
|
2496
|
-
|
|
2497
|
-
This is opposite from intuitive expectation. You must convert between "open percentage" and Matter's "closed percentage" in both directions.
|
|
2498
|
-
|
|
2499
|
-
**Attributes**:
|
|
2500
|
-
|
|
2501
|
-
| Attribute | Type | Range/Values | Description |
|
|
2502
|
-
|-------------------------------------|--------|--------------|------------------------------------------------------------------|
|
|
2503
|
-
| `currentPositionLiftPercent100ths` | number | 0-10000 | Current lift position (0=open, 10000=closed, in hundredths) |
|
|
2504
|
-
| `targetPositionLiftPercent100ths` | number | 0-10000 | Target lift position (0=open, 10000=closed, in hundredths) |
|
|
2505
|
-
| `currentPositionTiltPercent100ths` | number | 0-10000 | Current tilt angle (0=horizontal/open, 10000=vertical/closed) ¹ |
|
|
2506
|
-
| `targetPositionTiltPercent100ths` | number | 0-10000 | Target tilt angle (0=horizontal/open, 10000=vertical/closed) ¹ |
|
|
2507
|
-
|
|
2508
|
-
¹ Tilt attributes only present on Venetian blinds with tilt control
|
|
2509
|
-
|
|
2510
|
-
**Reading State**:
|
|
2511
|
-
|
|
2512
|
-
```typescript
|
|
2513
|
-
// Read lift position (convert to open percentage)
|
|
2514
|
-
const closedPercent100ths = accessory.clusters.windowCovering.currentPositionLiftPercent100ths
|
|
2515
|
-
const closedPercent = closedPercent100ths / 100
|
|
2516
|
-
const openPercent = 100 - closedPercent // Invert!
|
|
2517
|
-
|
|
2518
|
-
log.info(`Blind is ${openPercent}% open`)
|
|
2519
|
-
|
|
2520
|
-
// Read tilt position (convert to degrees for Venetian blinds)
|
|
2521
|
-
const tiltPercent100ths = accessory.clusters.windowCovering.currentPositionTiltPercent100ths
|
|
2522
|
-
const degrees = Math.round((tiltPercent100ths / 10000) * 90)
|
|
2523
|
-
log.info(`Tilt angle: ${degrees}°`) // 0° = horizontal, 90° = vertical
|
|
2524
|
-
```
|
|
2525
|
-
|
|
2526
|
-
**Value Conversions**:
|
|
2527
|
-
|
|
2528
|
-
```typescript
|
|
2529
|
-
// Open percentage (0-100) to Matter value (0-10000)
|
|
2530
|
-
function openPercentToMatter(openPercent: number): number {
|
|
2531
|
-
const closedPercent = 100 - openPercent // Invert!
|
|
2532
|
-
return Math.round(closedPercent * 100)
|
|
2533
|
-
}
|
|
2534
|
-
|
|
2535
|
-
// Matter value (0-10000) to open percentage (0-100)
|
|
2536
|
-
function matterToOpenPercent(value: number): number {
|
|
2537
|
-
const closedPercent = value / 100
|
|
2538
|
-
return 100 - closedPercent // Invert!
|
|
2539
|
-
}
|
|
2540
|
-
|
|
2541
|
-
// Tilt degrees (0-90) to Matter value (0-10000)
|
|
2542
|
-
function degreesToMatter(degrees: number): number {
|
|
2543
|
-
return Math.round((degrees / 90) * 10000)
|
|
2544
|
-
}
|
|
2545
|
-
|
|
2546
|
-
// Matter value (0-10000) to tilt degrees (0-90)
|
|
2547
|
-
function matterToDegrees(value: number): number {
|
|
2548
|
-
return Math.round((value / 10000) * 90)
|
|
2549
|
-
}
|
|
2550
|
-
```
|
|
2551
|
-
|
|
2552
|
-
<details>
|
|
2553
|
-
<summary><strong>Handlers</strong></summary>
|
|
2554
|
-
|
|
2555
|
-
```typescript
|
|
2556
|
-
import type { MatterRequests } from 'homebridge'
|
|
2557
|
-
|
|
2558
|
-
handlers: {
|
|
2559
|
-
windowCovering: {
|
|
2560
|
-
/**
|
|
2561
|
-
* Called when user adjusts lift position via Home app
|
|
2562
|
-
*/
|
|
2563
|
-
goToLiftPercentage: async (request: MatterRequests.GoToLiftPercentage) => {
|
|
2564
|
-
const { liftPercent100thsValue } = request
|
|
2565
|
-
|
|
2566
|
-
// Matter uses 0=open, 10000=closed, so invert to get open percentage
|
|
2567
|
-
const closedPercent = liftPercent100thsValue / 100
|
|
2568
|
-
const openPercent = 100 - closedPercent // Invert!
|
|
2569
|
-
|
|
2570
|
-
log.info(`Moving to ${openPercent}% open`)
|
|
2571
|
-
await myBlindAPI.setPosition(openPercent)
|
|
2572
|
-
// State automatically updated by Homebridge
|
|
2573
|
-
},
|
|
2574
|
-
|
|
2575
|
-
/**
|
|
2576
|
-
* Called when user presses UP button in Home app
|
|
2577
|
-
*/
|
|
2578
|
-
upOrOpen: async () => {
|
|
2579
|
-
log.info('Opening blind')
|
|
2580
|
-
await myBlindAPI.open()
|
|
2581
|
-
// Update state after physical device confirms
|
|
2582
|
-
api.matter.updateAccessoryState(
|
|
2583
|
-
uuid,
|
|
2584
|
-
api.matter.clusterNames.WindowCovering,
|
|
2585
|
-
{
|
|
2586
|
-
currentPositionLiftPercent100ths: 0, // 0 = fully open
|
|
2587
|
-
targetPositionLiftPercent100ths: 0,
|
|
2588
|
-
}
|
|
2589
|
-
)
|
|
2590
|
-
},
|
|
2591
|
-
|
|
2592
|
-
/**
|
|
2593
|
-
* Called when user presses DOWN button in Home app
|
|
2594
|
-
*/
|
|
2595
|
-
downOrClose: async () => {
|
|
2596
|
-
log.info('Closing blind')
|
|
2597
|
-
await myBlindAPI.close()
|
|
2598
|
-
// Update state after physical device confirms
|
|
2599
|
-
api.matter.updateAccessoryState(
|
|
2600
|
-
uuid,
|
|
2601
|
-
api.matter.clusterNames.WindowCovering,
|
|
2602
|
-
{
|
|
2603
|
-
currentPositionLiftPercent100ths: 10000, // 10000 = fully closed
|
|
2604
|
-
targetPositionLiftPercent100ths: 10000,
|
|
2605
|
-
}
|
|
2606
|
-
)
|
|
2607
|
-
},
|
|
2608
|
-
|
|
2609
|
-
/**
|
|
2610
|
-
* Called when user presses STOP button in Home app
|
|
2611
|
-
*/
|
|
2612
|
-
stopMotion: async () => {
|
|
2613
|
-
log.info('Stopping blind movement')
|
|
2614
|
-
await myBlindAPI.stop()
|
|
2615
|
-
},
|
|
2616
|
-
},
|
|
2617
|
-
}
|
|
2618
|
-
```
|
|
2619
|
-
|
|
2620
|
-
</details>
|
|
2621
|
-
|
|
2622
|
-
**Updating State** (Flow B):
|
|
2623
|
-
|
|
2624
|
-
```typescript
|
|
2625
|
-
// When your physical blind moves to a new position
|
|
2626
|
-
function updateBlindPosition(openPercent: number) {
|
|
2627
|
-
// Convert open percentage to Matter's closed percentage (0=open, 10000=closed)
|
|
2628
|
-
const closedPercent = 100 - openPercent // Invert!
|
|
2629
|
-
const value = Math.round(closedPercent * 100)
|
|
2630
|
-
|
|
2631
|
-
api.matter.updateAccessoryState(
|
|
2632
|
-
uuid,
|
|
2633
|
-
api.matter.clusterNames.WindowCovering,
|
|
2634
|
-
{
|
|
2635
|
-
currentPositionLiftPercent100ths: value,
|
|
2636
|
-
targetPositionLiftPercent100ths: value,
|
|
2637
|
-
}
|
|
2638
|
-
)
|
|
2639
|
-
|
|
2640
|
-
log.info(`Lift position: ${openPercent}% open`)
|
|
2641
|
-
}
|
|
2642
|
-
|
|
2643
|
-
// Example: Blind is 40% open
|
|
2644
|
-
updateBlindPosition(40) // Sends value: 6000 to Matter (60% closed)
|
|
2645
|
-
```
|
|
2646
|
-
|
|
2647
|
-
**For Venetian Blinds with Tilt**:
|
|
2648
|
-
|
|
2649
|
-
Add tilt control handler and update method:
|
|
2650
|
-
|
|
2651
|
-
```typescript
|
|
2652
|
-
// Handler for tilt control
|
|
2653
|
-
handlers: {
|
|
2654
|
-
windowCovering: {
|
|
2655
|
-
// ... lift handlers above ...
|
|
2656
|
-
|
|
2657
|
-
/**
|
|
2658
|
-
* Called when user adjusts tilt angle via Home app
|
|
2659
|
-
* Tilt is shown as 0-90° in Home app (0=horizontal, 90=vertical)
|
|
2660
|
-
*/
|
|
2661
|
-
goToTiltPercentage: async (request: MatterRequests.GoToTiltPercentage) => {
|
|
2662
|
-
const { tiltPercent100thsValue } = request
|
|
2663
|
-
|
|
2664
|
-
// Matter tilt: 0=horizontal/open (0°), 10000=vertical/closed (90°)
|
|
2665
|
-
const degrees = Math.round((tiltPercent100thsValue / 10000) * 90)
|
|
2666
|
-
|
|
2667
|
-
log.info(`Tilting to ${degrees}°`)
|
|
2668
|
-
await myBlindAPI.setTiltAngle(degrees)
|
|
2669
|
-
// State automatically updated by Homebridge
|
|
2670
|
-
},
|
|
2671
|
-
},
|
|
2672
|
-
}
|
|
2673
|
-
|
|
2674
|
-
// Update tilt position (Flow B)
|
|
2675
|
-
function updateTiltPosition(degrees: number) {
|
|
2676
|
-
// Convert degrees (0-90) to Matter's tilt percentage (0=horizontal, 10000=vertical)
|
|
2677
|
-
const value = Math.round((degrees / 90) * 10000)
|
|
2678
|
-
|
|
2679
|
-
api.matter.updateAccessoryState(
|
|
2680
|
-
uuid,
|
|
2681
|
-
api.matter.clusterNames.WindowCovering,
|
|
2682
|
-
{
|
|
2683
|
-
currentPositionTiltPercent100ths: value,
|
|
2684
|
-
targetPositionTiltPercent100ths: value,
|
|
2685
|
-
}
|
|
2686
|
-
)
|
|
2687
|
-
|
|
2688
|
-
log.info(`Tilt position: ${degrees}°`)
|
|
2689
|
-
}
|
|
2690
|
-
```
|
|
2691
|
-
|
|
2692
|
-
---
|
|
2693
|
-
|
|
2694
|
-
### Thermostat
|
|
2695
|
-
|
|
2696
|
-
| Property | Value |
|
|
2697
|
-
|--------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------|
|
|
2698
|
-
| **Device Type** | `api.matter.deviceTypes.Thermostat` |
|
|
2699
|
-
| **Description** | A thermostat with heating and/or cooling control, temperature setpoints, and system mode management. |
|
|
2700
|
-
| **Matter Specification** | § 9.1 |
|
|
2701
|
-
|
|
2702
|
-
#### Required Clusters
|
|
2703
|
-
|
|
2704
|
-
###### `Thermostat` Cluster
|
|
2705
|
-
|
|
2706
|
-
Controls HVAC system mode, setpoints, and reports current temperature.
|
|
2707
|
-
|
|
2708
|
-
**Attributes**:
|
|
2709
|
-
|
|
2710
|
-
| Attribute | Type | Range/Values | Description |
|
|
2711
|
-
|------------------------------|--------|--------------|-----------------------------------------------------------|
|
|
2712
|
-
| `localTemperature` | number | -27315-32767 | Current temperature (hundredths of °C) ¹ |
|
|
2713
|
-
| `occupiedHeatingSetpoint` | number | 700-3000 | Target heating temperature (hundredths of °C) |
|
|
2714
|
-
| `occupiedCoolingSetpoint` | number | 1600-3200 | Target cooling temperature (hundredths of °C) |
|
|
2715
|
-
| `systemMode` | number | 0-7 | Current system mode (0=Off, 1=Auto, 3=Cool, 4=Heat, etc.) |
|
|
2716
|
-
| `controlSequenceOfOperation` | number | 0-5 | Supported modes (2=Heating only, 4=Cooling and Heating) |
|
|
2717
|
-
| `minHeatSetpointLimit` | number | 700-3000 | Minimum heating setpoint |
|
|
2718
|
-
| `maxHeatSetpointLimit` | number | 700-3000 | Maximum heating setpoint |
|
|
2719
|
-
| `minCoolSetpointLimit` | number | 1600-3200 | Minimum cooling setpoint |
|
|
2720
|
-
| `maxCoolSetpointLimit` | number | 1600-3200 | Maximum cooling setpoint |
|
|
2721
|
-
|
|
2722
|
-
¹ Temperature values are in **hundredths of degrees Celsius**: `2500` = `25.00°C`
|
|
2723
|
-
|
|
2724
|
-
**System Mode Values**:
|
|
2725
|
-
|
|
2726
|
-
| Value | Mode | Description |
|
|
2727
|
-
|-------|-------------------|------------------------------------------------|
|
|
2728
|
-
| 0 | Off | System is off |
|
|
2729
|
-
| 1 | Auto | Automatic heating/cooling based on temperature |
|
|
2730
|
-
| 2 | Reserved | Reserved (not used) |
|
|
2731
|
-
| 3 | Cool | Cooling mode |
|
|
2732
|
-
| 4 | Heat | Heating mode |
|
|
2733
|
-
| 5 | Emergency Heating | Emergency heat mode |
|
|
2734
|
-
| 6 | Precooling | Precooling mode |
|
|
2735
|
-
| 7 | Fan Only | Fan only (no heating/cooling) |
|
|
2736
|
-
|
|
2737
|
-
**Control Sequence of Operation Values**:
|
|
2738
|
-
|
|
2739
|
-
| Value | Description |
|
|
2740
|
-
|-------|---------------------------------|
|
|
2741
|
-
| 0 | Cooling only |
|
|
2742
|
-
| 1 | Cooling with reheat |
|
|
2743
|
-
| 2 | Heating only |
|
|
2744
|
-
| 3 | Heating with reheat |
|
|
2745
|
-
| 4 | Cooling and heating |
|
|
2746
|
-
| 5 | Cooling and heating with reheat |
|
|
2747
|
-
|
|
2748
|
-
**Reading State**:
|
|
2749
|
-
|
|
2750
|
-
```typescript
|
|
2751
|
-
// Read current temperature
|
|
2752
|
-
const tempHundredths = accessory.clusters.thermostat.localTemperature
|
|
2753
|
-
const celsius = tempHundredths / 100
|
|
2754
|
-
log.info(`Current temperature: ${celsius}°C`)
|
|
2755
|
-
|
|
2756
|
-
// Read heating setpoint
|
|
2757
|
-
const heatSetpoint = accessory.clusters.thermostat.occupiedHeatingSetpoint / 100
|
|
2758
|
-
log.info(`Heating setpoint: ${heatSetpoint}°C`)
|
|
2759
|
-
|
|
2760
|
-
// Read cooling setpoint (if supported)
|
|
2761
|
-
const coolSetpoint = accessory.clusters.thermostat.occupiedCoolingSetpoint / 100
|
|
2762
|
-
log.info(`Cooling setpoint: ${coolSetpoint}°C`)
|
|
2763
|
-
|
|
2764
|
-
// Read system mode
|
|
2765
|
-
const mode = accessory.clusters.thermostat.systemMode
|
|
2766
|
-
const modeNames = ['Off', 'Auto', 'Reserved', 'Cool', 'Heat', 'Emergency Heating', 'Precooling', 'Fan Only']
|
|
2767
|
-
log.info(`System mode: ${modeNames[mode]}`)
|
|
2768
|
-
```
|
|
2769
|
-
|
|
2770
|
-
**Initial State** (Heating and Cooling):
|
|
2771
|
-
|
|
2772
|
-
```typescript
|
|
2773
|
-
clusters: {
|
|
2774
|
-
thermostat: {
|
|
2775
|
-
localTemperature: 2100, // 21.00°C
|
|
2776
|
-
occupiedHeatingSetpoint: 2000, // 20.00°C
|
|
2777
|
-
occupiedCoolingSetpoint: 2400, // 24.00°C
|
|
2778
|
-
minHeatSetpointLimit: 700, // 7.00°C
|
|
2779
|
-
maxHeatSetpointLimit: 3000, // 30.00°C
|
|
2780
|
-
minCoolSetpointLimit: 1600, // 16.00°C
|
|
2781
|
-
maxCoolSetpointLimit: 3200, // 32.00°C
|
|
2782
|
-
controlSequenceOfOperation: 4, // Cooling and Heating
|
|
2783
|
-
systemMode: 0, // Off
|
|
2784
|
-
},
|
|
2785
|
-
}
|
|
2786
|
-
```
|
|
2787
|
-
|
|
2788
|
-
<details>
|
|
2789
|
-
<summary><strong>Handlers</strong></summary>
|
|
2790
|
-
|
|
2791
|
-
```typescript
|
|
2792
|
-
import type { MatterRequests } from 'homebridge'
|
|
2793
|
-
|
|
2794
|
-
handlers: {
|
|
2795
|
-
thermostat: {
|
|
2796
|
-
/**
|
|
2797
|
-
* Called when user changes system mode in Home app
|
|
2798
|
-
*/
|
|
2799
|
-
systemModeChange: async (request: { systemMode: number, oldSystemMode: number }) => {
|
|
2800
|
-
const { systemMode, oldSystemMode } = request
|
|
2801
|
-
|
|
2802
|
-
// Matter Thermostat SystemMode enum: 0=Off, 1=Auto, 3=Cool, 4=Heat, 5=EmergencyHeat, 6=Precooling, 7=FanOnly
|
|
2803
|
-
const modeNames = ['Off', 'Auto', 'Reserved', 'Cool', 'Heat', 'Emergency Heating', 'Precooling', 'Fan Only']
|
|
2804
|
-
const modeName = modeNames[systemMode] || `Unknown (${systemMode})`
|
|
2805
|
-
|
|
2806
|
-
log.info(`System mode changed to: ${modeName}`)
|
|
2807
|
-
await myThermostatAPI.setSystemMode(systemMode)
|
|
2808
|
-
// State automatically updated by Homebridge
|
|
2809
|
-
},
|
|
2810
|
-
|
|
2811
|
-
/**
|
|
2812
|
-
* Called when user adjusts heating setpoint in Home app
|
|
2813
|
-
*/
|
|
2814
|
-
heatingSetpointChange: async (request: { heatingSetpoint: number, oldHeatingSetpoint: number }) => {
|
|
2815
|
-
const celsius = request.heatingSetpoint / 100
|
|
2816
|
-
|
|
2817
|
-
log.info(`Heating setpoint changed to: ${celsius}°C`)
|
|
2818
|
-
await myThermostatAPI.setHeatingSetpoint(celsius)
|
|
2819
|
-
// State automatically updated by Homebridge
|
|
2820
|
-
},
|
|
2821
|
-
|
|
2822
|
-
/**
|
|
2823
|
-
* Called when user adjusts cooling setpoint in Home app
|
|
2824
|
-
*/
|
|
2825
|
-
coolingSetpointChange: async (request: { coolingSetpoint: number, oldCoolingSetpoint: number }) => {
|
|
2826
|
-
const celsius = request.coolingSetpoint / 100
|
|
2827
|
-
|
|
2828
|
-
log.info(`Cooling setpoint changed to: ${celsius}°C`)
|
|
2829
|
-
await myThermostatAPI.setCoolingSetpoint(celsius)
|
|
2830
|
-
// State automatically updated by Homebridge
|
|
2831
|
-
},
|
|
2832
|
-
},
|
|
2833
|
-
}
|
|
2834
|
-
```
|
|
2835
|
-
|
|
2836
|
-
</details>
|
|
2837
|
-
|
|
2838
|
-
**Updating State** (Flow B):
|
|
2839
|
-
|
|
2840
|
-
```typescript
|
|
2841
|
-
// Update current temperature
|
|
2842
|
-
function updateTemperature(celsius: number) {
|
|
2843
|
-
const value = Math.round(celsius * 100)
|
|
2844
|
-
|
|
2845
|
-
api.matter.updateAccessoryState(
|
|
2846
|
-
uuid,
|
|
2847
|
-
api.matter.clusterNames.Thermostat,
|
|
2848
|
-
{ localTemperature: value }
|
|
2849
|
-
)
|
|
2850
|
-
|
|
2851
|
-
log.info(`Temperature: ${celsius}°C`)
|
|
2852
|
-
}
|
|
2853
|
-
|
|
2854
|
-
// Update heating setpoint
|
|
2855
|
-
function updateHeatingSetpoint(celsius: number) {
|
|
2856
|
-
const value = Math.round(celsius * 100)
|
|
2857
|
-
|
|
2858
|
-
api.matter.updateAccessoryState(
|
|
2859
|
-
uuid,
|
|
2860
|
-
api.matter.clusterNames.Thermostat,
|
|
2861
|
-
{ occupiedHeatingSetpoint: value }
|
|
2862
|
-
)
|
|
2863
|
-
|
|
2864
|
-
log.info(`Heating setpoint: ${celsius}°C`)
|
|
2865
|
-
}
|
|
2866
|
-
|
|
2867
|
-
// Update cooling setpoint
|
|
2868
|
-
function updateCoolingSetpoint(celsius: number) {
|
|
2869
|
-
const value = Math.round(celsius * 100)
|
|
2870
|
-
|
|
2871
|
-
api.matter.updateAccessoryState(
|
|
2872
|
-
uuid,
|
|
2873
|
-
api.matter.clusterNames.Thermostat,
|
|
2874
|
-
{ occupiedCoolingSetpoint: value }
|
|
2875
|
-
)
|
|
2876
|
-
|
|
2877
|
-
log.info(`Cooling setpoint: ${celsius}°C`)
|
|
2878
|
-
}
|
|
2879
|
-
|
|
2880
|
-
// Update system mode
|
|
2881
|
-
function updateSystemMode(mode: number) {
|
|
2882
|
-
api.matter.updateAccessoryState(
|
|
2883
|
-
uuid,
|
|
2884
|
-
api.matter.clusterNames.Thermostat,
|
|
2885
|
-
{ systemMode: mode }
|
|
2886
|
-
)
|
|
2887
|
-
|
|
2888
|
-
const modeNames = ['Off', 'Auto', 'Reserved', 'Cool', 'Heat', 'Emergency Heating', 'Precooling', 'Fan Only']
|
|
2889
|
-
log.info(`System mode: ${modeNames[mode]}`)
|
|
2890
|
-
}
|
|
2891
|
-
```
|
|
2892
|
-
|
|
2893
|
-
---
|
|
2894
|
-
|
|
2895
|
-
### Fan
|
|
2896
|
-
|
|
2897
|
-
| Property | Value |
|
|
2898
|
-
|--------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------|
|
|
2899
|
-
| **Device Type** | `api.matter.deviceTypes.Fan` |
|
|
2900
|
-
| **Description** | A fan with variable speed control and multiple fan modes. |
|
|
2901
|
-
| **Matter Specification** | § 9.2 |
|
|
2902
|
-
|
|
2903
|
-
#### Required Clusters
|
|
2904
|
-
|
|
2905
|
-
###### `FanControl` Cluster
|
|
2906
|
-
|
|
2907
|
-
Controls fan mode and speed.
|
|
2908
|
-
|
|
2909
|
-
**Attributes**:
|
|
2910
|
-
|
|
2911
|
-
| Attribute | Type | Range/Values | Description |
|
|
2912
|
-
|--------------------|--------|--------------|---------------------------------------------------------------|
|
|
2913
|
-
| `fanMode` | number | 0-6 | Current fan mode (0=Off, 1=Low, 2=Medium, 3=High, 4=On, etc.) |
|
|
2914
|
-
| `fanModeSequence` | number | 0-4 | Supported fan mode sequence |
|
|
2915
|
-
| `percentSetting` | number | 0-100 | Target fan speed percentage (0=off, 1-100=on with speed) |
|
|
2916
|
-
| `percentCurrent` | number | 0-100 | Current fan speed percentage |
|
|
2917
|
-
|
|
2918
|
-
**Fan Mode Values**:
|
|
2919
|
-
|
|
2920
|
-
| Value | Mode | Description |
|
|
2921
|
-
|-------|--------|--------------------|
|
|
2922
|
-
| 0 | Off | Fan is off |
|
|
2923
|
-
| 1 | Low | Low speed |
|
|
2924
|
-
| 2 | Medium | Medium speed |
|
|
2925
|
-
| 3 | High | High speed |
|
|
2926
|
-
| 4 | On | On (no speed info) |
|
|
2927
|
-
| 5 | Auto | Automatic mode |
|
|
2928
|
-
| 6 | Smart | Smart mode |
|
|
2929
|
-
|
|
2930
|
-
**Fan Mode Sequence Values**:
|
|
2931
|
-
|
|
2932
|
-
| Value | Description | Available Modes |
|
|
2933
|
-
|-------|-----------------------|---------------------------|
|
|
2934
|
-
| 0 | Off/Low/Med/High | Off, Low, Med, High |
|
|
2935
|
-
| 1 | Off/Low/High | Off, Low, High |
|
|
2936
|
-
| 2 | Off/Low/Med/High/Auto | Off, Low, Med, High, Auto |
|
|
2937
|
-
| 3 | Off/Low/High/Auto | Off, Low, High, Auto |
|
|
2938
|
-
| 4 | Off/On/Auto | Off, On, Auto |
|
|
2939
|
-
|
|
2940
|
-
**Reading State**:
|
|
2941
|
-
|
|
2942
|
-
```typescript
|
|
2943
|
-
const mode = accessory.clusters.fanControl.fanMode
|
|
2944
|
-
const speed = accessory.clusters.fanControl.percentSetting
|
|
2945
|
-
|
|
2946
|
-
const modeNames = ['Off', 'Low', 'Medium', 'High', 'On', 'Auto', 'Smart']
|
|
2947
|
-
log.info(`Fan mode: ${modeNames[mode]}, Speed: ${speed}%`)
|
|
2948
|
-
```
|
|
2949
|
-
|
|
2950
|
-
**Detecting On/Off vs Speed Changes**:
|
|
2951
|
-
|
|
2952
|
-
The `percentSetting` attribute is used for both on/off control and speed adjustment. To distinguish between the two:
|
|
2953
|
-
|
|
2954
|
-
```typescript
|
|
2955
|
-
// In percentSettingChange handler
|
|
2956
|
-
fanControl: {
|
|
2957
|
-
percentSettingChange: async (request: { percentSetting: number | null, oldPercentSetting: number | null }) => {
|
|
2958
|
-
const percent = request.percentSetting ?? 0
|
|
2959
|
-
const isOff = percent === 0
|
|
2960
|
-
const wasOff = (request.oldPercentSetting ?? 0) === 0
|
|
2961
|
-
|
|
2962
|
-
// Check if on/off state changed
|
|
2963
|
-
if (isOff !== wasOff) {
|
|
2964
|
-
log.info(`Fan turned ${isOff ? 'off' : 'on'}`)
|
|
2965
|
-
await myFanAPI.setPower(!isOff)
|
|
2966
|
-
}
|
|
2967
|
-
|
|
2968
|
-
// Update speed (only if not turning off)
|
|
2969
|
-
if (!isOff) {
|
|
2970
|
-
log.info(`Fan speed changed to: ${percent}%`)
|
|
2971
|
-
await myFanAPI.setSpeed(percent)
|
|
2972
|
-
}
|
|
2973
|
-
},
|
|
2974
|
-
}
|
|
2975
|
-
```
|
|
2976
|
-
|
|
2977
|
-
<details>
|
|
2978
|
-
<summary><strong>Handlers</strong></summary>
|
|
2979
|
-
|
|
2980
|
-
```typescript
|
|
2981
|
-
import type { MatterRequests } from 'homebridge'
|
|
2982
|
-
|
|
2983
|
-
handlers: {
|
|
2984
|
-
fanControl: {
|
|
2985
|
-
/**
|
|
2986
|
-
* Called when user uses step control (increase/decrease button)
|
|
2987
|
-
*/
|
|
2988
|
-
step: async (request: MatterRequests.FanStep) => {
|
|
2989
|
-
const { direction, wrap, lowestOff } = request
|
|
2990
|
-
const dirStr = direction === 0 ? 'increase' : 'decrease'
|
|
2991
|
-
|
|
2992
|
-
log.info(`Fan step ${dirStr} (wrap: ${wrap}, lowestOff: ${lowestOff})`)
|
|
2993
|
-
await myFanAPI.step(direction, wrap, lowestOff)
|
|
2994
|
-
// State automatically updated by Homebridge
|
|
2995
|
-
},
|
|
2996
|
-
|
|
2997
|
-
/**
|
|
2998
|
-
* Called when user changes fan mode via Home app
|
|
2999
|
-
*/
|
|
3000
|
-
fanModeChange: async (request: { fanMode: number, oldFanMode: number }) => {
|
|
3001
|
-
const modeNames = ['Off', 'Low', 'Medium', 'High', 'On', 'Auto', 'Smart']
|
|
3002
|
-
const modeName = modeNames[request.fanMode] || `Unknown (${request.fanMode})`
|
|
3003
|
-
|
|
3004
|
-
log.info(`Fan mode changed to: ${modeName}`)
|
|
3005
|
-
await myFanAPI.setMode(request.fanMode)
|
|
3006
|
-
// State automatically updated by Homebridge
|
|
3007
|
-
},
|
|
3008
|
-
|
|
3009
|
-
/**
|
|
3010
|
-
* Called when user adjusts fan speed via Home app
|
|
3011
|
-
* Also handles on/off transitions
|
|
3012
|
-
*/
|
|
3013
|
-
percentSettingChange: async (request: { percentSetting: number | null, oldPercentSetting: number | null }) => {
|
|
3014
|
-
const percent = request.percentSetting ?? 0
|
|
3015
|
-
const isOff = percent === 0
|
|
3016
|
-
const wasOff = (request.oldPercentSetting ?? 0) === 0
|
|
3017
|
-
|
|
3018
|
-
// Detect on/off state change
|
|
3019
|
-
if (isOff !== wasOff) {
|
|
3020
|
-
log.info(`Fan turned ${isOff ? 'off' : 'on'}`)
|
|
3021
|
-
await myFanAPI.setPower(!isOff)
|
|
3022
|
-
}
|
|
3023
|
-
|
|
3024
|
-
// Update speed (only when not off)
|
|
3025
|
-
if (!isOff) {
|
|
3026
|
-
log.info(`Fan speed changed to: ${percent}%`)
|
|
3027
|
-
await myFanAPI.setSpeed(percent)
|
|
3028
|
-
}
|
|
3029
|
-
// State automatically updated by Homebridge
|
|
3030
|
-
},
|
|
3031
|
-
},
|
|
3032
|
-
}
|
|
3033
|
-
```
|
|
3034
|
-
|
|
3035
|
-
</details>
|
|
3036
|
-
|
|
3037
|
-
**Updating State** (Flow B):
|
|
3038
|
-
|
|
3039
|
-
```typescript
|
|
3040
|
-
// Update fan mode
|
|
3041
|
-
function updateFanMode(mode: number) {
|
|
3042
|
-
api.matter.updateAccessoryState(
|
|
3043
|
-
uuid,
|
|
3044
|
-
api.matter.clusterNames.FanControl,
|
|
3045
|
-
{ fanMode: mode }
|
|
3046
|
-
)
|
|
3047
|
-
|
|
3048
|
-
const modeNames = ['Off', 'Low', 'Medium', 'High', 'On', 'Auto', 'Smart']
|
|
3049
|
-
log.info(`Fan mode: ${modeNames[mode]}`)
|
|
3050
|
-
}
|
|
3051
|
-
|
|
3052
|
-
// Update fan speed
|
|
3053
|
-
function updateFanSpeed(percent: number) {
|
|
3054
|
-
api.matter.updateAccessoryState(
|
|
3055
|
-
uuid,
|
|
3056
|
-
api.matter.clusterNames.FanControl,
|
|
3057
|
-
{
|
|
3058
|
-
percentSetting: percent,
|
|
3059
|
-
percentCurrent: percent,
|
|
3060
|
-
}
|
|
3061
|
-
)
|
|
3062
|
-
|
|
3063
|
-
log.info(`Fan speed: ${percent}%`)
|
|
3064
|
-
}
|
|
3065
|
-
```
|
|
3066
|
-
|
|
3067
|
-
---
|
|
3068
|
-
|
|
3069
|
-
### Light Sensor
|
|
3070
|
-
|
|
3071
|
-
| Property | Value |
|
|
3072
|
-
|--------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------|
|
|
3073
|
-
| **Device Type** | `api.matter.deviceTypes.LightSensor` |
|
|
3074
|
-
| **Description** | A sensor that measures ambient light levels. |
|
|
3075
|
-
| **Matter Specification** | § 7.2 |
|
|
3076
|
-
|
|
3077
|
-
#### Required Clusters
|
|
3078
|
-
|
|
3079
|
-
###### `IlluminanceMeasurement` Cluster
|
|
3080
|
-
|
|
3081
|
-
Measures illuminance (light level) in lux.
|
|
3082
|
-
|
|
3083
|
-
**Attributes**:
|
|
3084
|
-
|
|
3085
|
-
| Attribute | Type | Range/Values | Description |
|
|
3086
|
-
|--------------------|--------|--------------|-------------------------------------------|
|
|
3087
|
-
| `measuredValue` | number | 0-65534 | Current light level (logarithmic scale) ¹ |
|
|
3088
|
-
| `minMeasuredValue` | number | 1-65533 | Minimum measurable light level |
|
|
3089
|
-
| `maxMeasuredValue` | number | 2-65534 | Maximum measurable light level |
|
|
3090
|
-
|
|
3091
|
-
¹ The `measuredValue` uses a logarithmic scale: `value = 10000 × log₁₀(lux)`
|
|
3092
|
-
|
|
3093
|
-
**Value Conversion**:
|
|
3094
|
-
|
|
3095
|
-
```typescript
|
|
3096
|
-
// Lux to Matter value
|
|
3097
|
-
const matterValue = Math.round(10000 * Math.log10(lux))
|
|
3098
|
-
|
|
3099
|
-
// Matter value to Lux
|
|
3100
|
-
const lux = 10 ** (matterValue / 10000)
|
|
3101
|
-
```
|
|
3102
|
-
|
|
3103
|
-
**Reading State**:
|
|
3104
|
-
|
|
3105
|
-
```typescript
|
|
3106
|
-
const matterValue = accessory.clusters.illuminanceMeasurement.measuredValue
|
|
3107
|
-
const lux = 10 ** (matterValue / 10000)
|
|
3108
|
-
log.info(`Light level: ${lux.toFixed(1)} lux`)
|
|
3109
|
-
```
|
|
3110
|
-
|
|
3111
|
-
**Updating State** (Flow B):
|
|
3112
|
-
|
|
3113
|
-
```typescript
|
|
3114
|
-
// When your physical sensor reports new light level
|
|
3115
|
-
function updateIlluminance(lux: number) {
|
|
3116
|
-
const value = Math.round(10000 * Math.log10(lux))
|
|
3117
|
-
|
|
3118
|
-
api.matter.updateAccessoryState(
|
|
3119
|
-
uuid,
|
|
3120
|
-
api.matter.clusterNames.IlluminanceMeasurement,
|
|
3121
|
-
{ measuredValue: value }
|
|
3122
|
-
)
|
|
3123
|
-
|
|
3124
|
-
log.info(`Illuminance: ${lux} lux`)
|
|
3125
|
-
}
|
|
3126
|
-
|
|
3127
|
-
// Example: 500 lux
|
|
3128
|
-
updateIlluminance(500) // Sends value: ~27000
|
|
3129
|
-
```
|
|
3130
|
-
|
|
3131
|
-
**Initial State**:
|
|
3132
|
-
|
|
3133
|
-
```typescript
|
|
3134
|
-
clusters: {
|
|
3135
|
-
illuminanceMeasurement: {
|
|
3136
|
-
measuredValue: 5000, // ~3.16 lux
|
|
3137
|
-
minMeasuredValue: 1, // Minimum
|
|
3138
|
-
maxMeasuredValue: 65534, // Maximum
|
|
3139
|
-
},
|
|
3140
|
-
}
|
|
3141
|
-
```
|
|
3142
|
-
|
|
3143
|
-
**Handler**: Light sensors are read-only (no handlers needed).
|
|
3144
|
-
|
|
3145
|
-
---
|
|
3146
|
-
|
|
3147
|
-
### Temperature Sensor
|
|
3148
|
-
|
|
3149
|
-
| Property | Value |
|
|
3150
|
-
|--------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------|
|
|
3151
|
-
| **Device Type** | `api.matter.deviceTypes.TemperatureSensor` |
|
|
3152
|
-
| **Description** | A sensor that measures ambient temperature. |
|
|
3153
|
-
| **Matter Specification** | § 7.4 |
|
|
3154
|
-
|
|
3155
|
-
#### Required Clusters
|
|
3156
|
-
|
|
3157
|
-
###### `TemperatureMeasurement` Cluster
|
|
3158
|
-
|
|
3159
|
-
Measures temperature in degrees Celsius.
|
|
3160
|
-
|
|
3161
|
-
**Attributes**:
|
|
3162
|
-
|
|
3163
|
-
| Attribute | Type | Range/Values | Description |
|
|
3164
|
-
|--------------------|--------|--------------|------------------------------------------|
|
|
3165
|
-
| `measuredValue` | number | -27315-32767 | Current temperature (hundredths of °C) ¹ |
|
|
3166
|
-
| `minMeasuredValue` | number | -27315-32767 | Minimum measurable temperature |
|
|
3167
|
-
| `maxMeasuredValue` | number | -27315-32767 | Maximum measurable temperature |
|
|
3168
|
-
|
|
3169
|
-
¹ Temperature values are in **hundredths of degrees Celsius**: `2100` = `21.00°C`
|
|
3170
|
-
|
|
3171
|
-
**Reading State**:
|
|
3172
|
-
|
|
3173
|
-
```typescript
|
|
3174
|
-
const tempHundredths = accessory.clusters.temperatureMeasurement.measuredValue
|
|
3175
|
-
const celsius = tempHundredths / 100
|
|
3176
|
-
log.info(`Temperature: ${celsius}°C`)
|
|
3177
|
-
```
|
|
3178
|
-
|
|
3179
|
-
**Updating State** (Flow B):
|
|
3180
|
-
|
|
3181
|
-
```typescript
|
|
3182
|
-
// When your physical sensor reports new temperature
|
|
3183
|
-
function updateTemperature(celsius: number) {
|
|
3184
|
-
const value = Math.round(celsius * 100)
|
|
3185
|
-
|
|
3186
|
-
api.matter.updateAccessoryState(
|
|
3187
|
-
uuid,
|
|
3188
|
-
api.matter.clusterNames.TemperatureMeasurement,
|
|
3189
|
-
{ measuredValue: value }
|
|
3190
|
-
)
|
|
3191
|
-
|
|
3192
|
-
log.info(`Temperature: ${celsius}°C`)
|
|
3193
|
-
}
|
|
3194
|
-
|
|
3195
|
-
// Example: 21.5°C
|
|
3196
|
-
updateTemperature(21.5) // Sends value: 2150
|
|
3197
|
-
```
|
|
3198
|
-
|
|
3199
|
-
**Initial State**:
|
|
3200
|
-
|
|
3201
|
-
```typescript
|
|
3202
|
-
clusters: {
|
|
3203
|
-
temperatureMeasurement: {
|
|
3204
|
-
measuredValue: 2100, // 21.00°C
|
|
3205
|
-
minMeasuredValue: -5000, // -50.00°C
|
|
3206
|
-
maxMeasuredValue: 10000, // 100.00°C
|
|
3207
|
-
},
|
|
3208
|
-
}
|
|
3209
|
-
```
|
|
3210
|
-
|
|
3211
|
-
**Handler**: Temperature sensors are read-only (no handlers needed).
|
|
3212
|
-
|
|
3213
|
-
---
|
|
3214
|
-
|
|
3215
|
-
### Humidity Sensor
|
|
3216
|
-
|
|
3217
|
-
| Property | Value |
|
|
3218
|
-
|--------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------|
|
|
3219
|
-
| **Device Type** | `api.matter.deviceTypes.HumiditySensor` |
|
|
3220
|
-
| **Description** | A sensor that measures relative humidity. |
|
|
3221
|
-
| **Matter Specification** | § 7.7 |
|
|
3222
|
-
|
|
3223
|
-
#### Required Clusters
|
|
3224
|
-
|
|
3225
|
-
###### `RelativeHumidityMeasurement` Cluster
|
|
3226
|
-
|
|
3227
|
-
Measures relative humidity as a percentage.
|
|
3228
|
-
|
|
3229
|
-
**Attributes**:
|
|
3230
|
-
|
|
3231
|
-
| Attribute | Type | Range/Values | Description |
|
|
3232
|
-
|--------------------|--------|--------------|----------------------------------------------|
|
|
3233
|
-
| `measuredValue` | number | 0-10000 | Current humidity (hundredths of a percent) ¹ |
|
|
3234
|
-
| `minMeasuredValue` | number | 0-9999 | Minimum measurable humidity |
|
|
3235
|
-
| `maxMeasuredValue` | number | 1-10000 | Maximum measurable humidity |
|
|
3236
|
-
|
|
3237
|
-
¹ Humidity values are in **hundredths of a percent**: `5500` = `55.00%`
|
|
3238
|
-
|
|
3239
|
-
**Reading State**:
|
|
3240
|
-
|
|
3241
|
-
```typescript
|
|
3242
|
-
const humidityHundredths = accessory.clusters.relativeHumidityMeasurement.measuredValue
|
|
3243
|
-
const percent = humidityHundredths / 100
|
|
3244
|
-
log.info(`Humidity: ${percent}%`)
|
|
3245
|
-
```
|
|
3246
|
-
|
|
3247
|
-
**Updating State** (Flow B):
|
|
3248
|
-
|
|
3249
|
-
```typescript
|
|
3250
|
-
// When your physical sensor reports new humidity
|
|
3251
|
-
function updateHumidity(percent: number) {
|
|
3252
|
-
const value = Math.round(percent * 100)
|
|
3253
|
-
|
|
3254
|
-
api.matter.updateAccessoryState(
|
|
3255
|
-
uuid,
|
|
3256
|
-
api.matter.clusterNames.RelativeHumidityMeasurement,
|
|
3257
|
-
{ measuredValue: value }
|
|
3258
|
-
)
|
|
3259
|
-
|
|
3260
|
-
log.info(`Humidity: ${percent}%`)
|
|
3261
|
-
}
|
|
3262
|
-
|
|
3263
|
-
// Example: 65.5%
|
|
3264
|
-
updateHumidity(65.5) // Sends value: 6550
|
|
3265
|
-
```
|
|
3266
|
-
|
|
3267
|
-
**Initial State**:
|
|
3268
|
-
|
|
3269
|
-
```typescript
|
|
3270
|
-
clusters: {
|
|
3271
|
-
relativeHumidityMeasurement: {
|
|
3272
|
-
measuredValue: 5500, // 55%
|
|
3273
|
-
minMeasuredValue: 0, // 0%
|
|
3274
|
-
maxMeasuredValue: 10000, // 100%
|
|
3275
|
-
},
|
|
3276
|
-
}
|
|
3277
|
-
```
|
|
3278
|
-
|
|
3279
|
-
**Handler**: Humidity sensors are read-only (no handlers needed).
|
|
3280
|
-
|
|
3281
|
-
---
|
|
3282
|
-
|
|
3283
|
-
### Smoke/CO Alarm
|
|
3284
|
-
|
|
3285
|
-
| Property | Value |
|
|
3286
|
-
|--------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------|
|
|
3287
|
-
| **Device Type** | `api.matter.deviceTypes.SmokeSensor` (with SmokeAlarm and CoAlarm features) |
|
|
3288
|
-
| **Description** | A combined smoke and carbon monoxide alarm sensor. |
|
|
3289
|
-
| **Matter Specification** | § 7.9 |
|
|
3290
|
-
|
|
3291
|
-
#### Required Clusters
|
|
3292
|
-
|
|
3293
|
-
###### `SmokeCoAlarm` Cluster
|
|
3294
|
-
|
|
3295
|
-
Detects smoke and carbon monoxide with three alarm states.
|
|
3296
|
-
|
|
3297
|
-
**Attributes**:
|
|
3298
|
-
|
|
3299
|
-
| Attribute | Type | Range/Values | Description |
|
|
3300
|
-
|---------------------------|---------|--------------|-------------------------------------------------------|
|
|
3301
|
-
| `smokeState` | number | 0-2 | Smoke alarm state (0=Normal, 1=Warning, 2=Critical) |
|
|
3302
|
-
| `coState` | number | 0-2 | CO alarm state (0=Normal, 1=Warning, 2=Critical) |
|
|
3303
|
-
| `batteryAlert` | number | 0-2 | Battery level alert |
|
|
3304
|
-
| `deviceMuted` | number | 0-2 | Device mute status |
|
|
3305
|
-
| `testInProgress` | boolean | true/false | Whether self-test is running |
|
|
3306
|
-
| `hardwareFaultAlert` | boolean | true/false | Hardware fault detected |
|
|
3307
|
-
| `endOfServiceAlert` | number | 0-2 | End of service life alert |
|
|
3308
|
-
| `interconnectSmokeAlarm` | number | 0-2 | Interconnected smoke alarm status |
|
|
3309
|
-
| `interconnectCoAlarm` | number | 0-2 | Interconnected CO alarm status |
|
|
3310
|
-
| `contaminationState` | number | 0-2 | Sensor contamination state |
|
|
3311
|
-
| `smokeSensitivityLevel` | number | 0-2 | Smoke sensitivity level |
|
|
3312
|
-
| `expressedState` | number | 0-10 | Overall alarm state |
|
|
3313
|
-
|
|
3314
|
-
**Alarm State Values**:
|
|
3315
|
-
|
|
3316
|
-
| Value | State | Description |
|
|
3317
|
-
|-------|----------|-------------------------|
|
|
3318
|
-
| 0 | Normal | No alarm detected |
|
|
3319
|
-
| 1 | Warning | Warning level detected |
|
|
3320
|
-
| 2 | Critical | Critical level detected |
|
|
3321
|
-
|
|
3322
|
-
**Reading State**:
|
|
3323
|
-
|
|
3324
|
-
```typescript
|
|
3325
|
-
const smokeState = accessory.clusters.smokeCoAlarm.smokeState
|
|
3326
|
-
const coState = accessory.clusters.smokeCoAlarm.coState
|
|
3327
|
-
|
|
3328
|
-
const stateNames = ['Normal', 'Warning', 'Critical']
|
|
3329
|
-
log.info(`Smoke: ${stateNames[smokeState]}, CO: ${stateNames[coState]}`)
|
|
3330
|
-
```
|
|
3331
|
-
|
|
3332
|
-
**Updating State** (Flow B):
|
|
3333
|
-
|
|
3334
|
-
```typescript
|
|
3335
|
-
// Update smoke alarm state
|
|
3336
|
-
function updateSmokeState(state: 0 | 1 | 2) {
|
|
3337
|
-
api.matter.updateAccessoryState(
|
|
3338
|
-
uuid,
|
|
3339
|
-
api.matter.clusterNames.SmokeCoAlarm,
|
|
3340
|
-
{ smokeState: state }
|
|
3341
|
-
)
|
|
3342
|
-
|
|
3343
|
-
const stateStr = ['Normal', 'Warning', 'Critical'][state]
|
|
3344
|
-
log.info(`Smoke state: ${stateStr}`)
|
|
3345
|
-
}
|
|
3346
|
-
|
|
3347
|
-
// Update CO alarm state
|
|
3348
|
-
function updateCOState(state: 0 | 1 | 2) {
|
|
3349
|
-
api.matter.updateAccessoryState(
|
|
3350
|
-
uuid,
|
|
3351
|
-
api.matter.clusterNames.SmokeCoAlarm,
|
|
3352
|
-
{ coState: state }
|
|
3353
|
-
)
|
|
3354
|
-
|
|
3355
|
-
const stateStr = ['Normal', 'Warning', 'Critical'][state]
|
|
3356
|
-
log.info(`CO state: ${stateStr}`)
|
|
3357
|
-
}
|
|
3358
|
-
|
|
3359
|
-
// Example: Smoke detected
|
|
3360
|
-
updateSmokeState(2) // Critical
|
|
3361
|
-
```
|
|
3362
|
-
|
|
3363
|
-
**Initial State with Both Features**:
|
|
3364
|
-
|
|
3365
|
-
```typescript
|
|
3366
|
-
// Configure Smoke/CO Alarm with both features
|
|
3367
|
-
const SmokeCoAlarmServer = api.matter.deviceTypes.SmokeSensor.requirements.SmokeCoAlarmServer
|
|
3368
|
-
const SmokeSensorWithBoth = api.matter.deviceTypes.SmokeSensor.with(
|
|
3369
|
-
SmokeCoAlarmServer.with('SmokeAlarm', 'CoAlarm'),
|
|
3370
|
-
)
|
|
3371
|
-
|
|
3372
|
-
{
|
|
3373
|
-
deviceType: SmokeSensorWithBoth,
|
|
3374
|
-
clusters: {
|
|
3375
|
-
smokeCoAlarm: {
|
|
3376
|
-
smokeState: 0, // Normal
|
|
3377
|
-
coState: 0, // Normal
|
|
3378
|
-
batteryAlert: 0, // Normal
|
|
3379
|
-
deviceMuted: 0, // Not muted
|
|
3380
|
-
testInProgress: false, // No test running
|
|
3381
|
-
hardwareFaultAlert: false, // No fault
|
|
3382
|
-
endOfServiceAlert: 0, // Normal
|
|
3383
|
-
interconnectSmokeAlarm: 0, // Normal
|
|
3384
|
-
interconnectCoAlarm: 0, // Normal
|
|
3385
|
-
contaminationState: 0, // Normal
|
|
3386
|
-
smokeSensitivityLevel: 1, // Standard sensitivity
|
|
3387
|
-
expressedState: 0, // Normal
|
|
3388
|
-
},
|
|
3389
|
-
},
|
|
3390
|
-
}
|
|
3391
|
-
```
|
|
3392
|
-
|
|
3393
|
-
**Handler**: Smoke/CO alarms are read-only (no handlers needed).
|
|
3394
|
-
|
|
3395
|
-
---
|
|
3396
|
-
|
|
3397
|
-
### Water Leak Detector
|
|
3398
|
-
|
|
3399
|
-
| Property | Value |
|
|
3400
|
-
|--------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------|
|
|
3401
|
-
| **Device Type** | `api.matter.deviceTypes.LeakSensor` |
|
|
3402
|
-
| **Description** | A sensor that detects water leaks. |
|
|
3403
|
-
| **Matter Specification** | § 7.12 |
|
|
3404
|
-
|
|
3405
|
-
#### Required Clusters
|
|
3406
|
-
|
|
3407
|
-
###### `BooleanState` Cluster
|
|
3408
|
-
|
|
3409
|
-
Represents water leak detection state using a boolean value.
|
|
3410
|
-
|
|
3411
|
-
**NOTE**: Unlike contact sensors, leak detectors use **standard (non-inverted) semantics**:
|
|
3412
|
-
- `false` = No leak detected / Dry (normal state)
|
|
3413
|
-
- `true` = Leak detected / Wet (triggered state)
|
|
3414
|
-
|
|
3415
|
-
**Attributes**:
|
|
3416
|
-
|
|
3417
|
-
| Attribute | Type | Range/Values | Description |
|
|
3418
|
-
|--------------|---------|-----------------|----------------------------------------------|
|
|
3419
|
-
| `stateValue` | boolean | `true`, `false` | Leak state (true=leak, false=dry) |
|
|
3420
|
-
|
|
3421
|
-
**Reading State**:
|
|
3422
|
-
|
|
3423
|
-
```typescript
|
|
3424
|
-
const leakDetected = accessory.clusters.booleanState.stateValue
|
|
3425
|
-
log.info(`Leak: ${leakDetected ? 'DETECTED' : 'None'}`)
|
|
3426
|
-
```
|
|
3427
|
-
|
|
3428
|
-
**Updating State** (Flow B):
|
|
3429
|
-
|
|
3430
|
-
```typescript
|
|
3431
|
-
// When your physical sensor reports leak state
|
|
3432
|
-
function updateLeakState(leakDetected: boolean) {
|
|
3433
|
-
api.matter.updateAccessoryState(
|
|
3434
|
-
uuid,
|
|
3435
|
-
api.matter.clusterNames.BooleanState,
|
|
3436
|
-
{ stateValue: leakDetected }
|
|
3437
|
-
)
|
|
3438
|
-
|
|
3439
|
-
log.info(`Leak: ${leakDetected ? 'detected' : 'none'}`)
|
|
3440
|
-
}
|
|
3441
|
-
|
|
3442
|
-
// Example: Leak detected
|
|
3443
|
-
updateLeakState(true) // Sends stateValue: true
|
|
3444
|
-
|
|
3445
|
-
// Example: Leak cleared
|
|
3446
|
-
updateLeakState(false) // Sends stateValue: false
|
|
3447
|
-
```
|
|
3448
|
-
|
|
3449
|
-
**Initial State**:
|
|
3450
|
-
|
|
3451
|
-
```typescript
|
|
3452
|
-
clusters: {
|
|
3453
|
-
booleanState: {
|
|
3454
|
-
stateValue: false, // false = dry/normal (safe default)
|
|
3455
|
-
},
|
|
3456
|
-
}
|
|
3457
|
-
```
|
|
3458
|
-
|
|
3459
|
-
**Handler**: Leak sensors are read-only (no handlers needed).
|
|
3460
|
-
|
|
3461
|
-
---
|
|
3462
|
-
|
|
3463
|
-
### Door Lock
|
|
3464
|
-
|
|
3465
|
-
| Property | Value |
|
|
3466
|
-
|--------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------|
|
|
3467
|
-
| **Device Type** | `api.matter.deviceTypes.DoorLock` |
|
|
3468
|
-
| **Description** | A smart lock that can be locked and unlocked, optionally with PIN code support. |
|
|
3469
|
-
| **Matter Specification** | § 8.1 |
|
|
3470
|
-
|
|
3471
|
-
#### Required Clusters
|
|
3472
|
-
|
|
3473
|
-
###### `DoorLock` Cluster
|
|
3474
|
-
|
|
3475
|
-
Controls door lock/unlock operations.
|
|
3476
|
-
|
|
3477
|
-
**Attributes**:
|
|
3478
|
-
|
|
3479
|
-
| Attribute | Type | Range/Values | Description |
|
|
3480
|
-
|-------------------|---------|--------------|-------------------------------------------------------------|
|
|
3481
|
-
| `lockState` | number | 0-2 | Current lock state (0=NotFullyLocked, 1=Locked, 2=Unlocked) |
|
|
3482
|
-
| `lockType` | number | 0-11 | Type of lock mechanism |
|
|
3483
|
-
| `actuatorEnabled` | boolean | true/false | Whether lock actuator is enabled |
|
|
3484
|
-
| `operatingMode` | number | 0-4 | Current operating mode |
|
|
3485
|
-
|
|
3486
|
-
**Lock State Values**:
|
|
3487
|
-
|
|
3488
|
-
| Value | State | Description |
|
|
3489
|
-
|-------|-----------------|--------------------------------|
|
|
3490
|
-
| 0 | NotFullyLocked | Lock is not fully engaged |
|
|
3491
|
-
| 1 | Locked | Lock is fully engaged |
|
|
3492
|
-
| 2 | Unlocked | Lock is disengaged |
|
|
3493
|
-
|
|
3494
|
-
**Lock Type Values** (common types):
|
|
3495
|
-
|
|
3496
|
-
| Value | Type | Description |
|
|
3497
|
-
|-------|------------|----------------------|
|
|
3498
|
-
| 0 | DeadBolt | Standard deadbolt |
|
|
3499
|
-
| 1 | Magnetic | Magnetic lock |
|
|
3500
|
-
| 2 | Other | Other type |
|
|
3501
|
-
|
|
3502
|
-
Access all lock types via `api.matter.types.DoorLock.LockType`:
|
|
3503
|
-
|
|
3504
|
-
```typescript
|
|
3505
|
-
api.matter.types.DoorLock.LockType.DeadBolt
|
|
3506
|
-
api.matter.types.DoorLock.LockType.Magnetic
|
|
3507
|
-
api.matter.types.DoorLock.LockType.Mortise
|
|
3508
|
-
// etc.
|
|
3509
|
-
```
|
|
3510
|
-
|
|
3511
|
-
**Reading State**:
|
|
3512
|
-
|
|
3513
|
-
```typescript
|
|
3514
|
-
const lockState = accessory.clusters.doorLock.lockState
|
|
3515
|
-
const stateNames = ['NotFullyLocked', 'Locked', 'Unlocked']
|
|
3516
|
-
log.info(`Lock state: ${stateNames[lockState]}`)
|
|
3517
|
-
```
|
|
3518
|
-
|
|
3519
|
-
<details>
|
|
3520
|
-
<summary><strong>Handlers</strong></summary>
|
|
3521
|
-
|
|
3522
|
-
```typescript
|
|
3523
|
-
import type { MatterRequests } from 'homebridge'
|
|
3524
|
-
|
|
3525
|
-
handlers: {
|
|
3526
|
-
doorLock: {
|
|
3527
|
-
/**
|
|
3528
|
-
* Called when user locks the door via Home app
|
|
3529
|
-
*/
|
|
3530
|
-
lockDoor: async (request?: MatterRequests.LockDoor) => {
|
|
3531
|
-
const pinCode = request?.pinCode // Optional PIN code
|
|
3532
|
-
|
|
3533
|
-
if (pinCode) {
|
|
3534
|
-
log.info(`Locking door with PIN: ${Buffer.from(pinCode).toString()}`)
|
|
3535
|
-
} else {
|
|
3536
|
-
log.info('Locking door')
|
|
3537
|
-
}
|
|
3538
|
-
|
|
3539
|
-
await myLockAPI.lock()
|
|
3540
|
-
// State automatically updated by Homebridge
|
|
3541
|
-
},
|
|
3542
|
-
|
|
3543
|
-
/**
|
|
3544
|
-
* Called when user unlocks the door via Home app
|
|
3545
|
-
*/
|
|
3546
|
-
unlockDoor: async (request?: MatterRequests.UnlockDoor) => {
|
|
3547
|
-
const pinCode = request?.pinCode // Optional PIN code
|
|
3548
|
-
|
|
3549
|
-
if (pinCode) {
|
|
3550
|
-
log.info(`Unlocking door with PIN: ${Buffer.from(pinCode).toString()}`)
|
|
3551
|
-
} else {
|
|
3552
|
-
log.info('Unlocking door')
|
|
3553
|
-
}
|
|
3554
|
-
|
|
3555
|
-
await myLockAPI.unlock()
|
|
3556
|
-
// State automatically updated by Homebridge
|
|
3557
|
-
},
|
|
3558
|
-
},
|
|
3559
|
-
}
|
|
3560
|
-
```
|
|
3561
|
-
|
|
3562
|
-
</details>
|
|
3563
|
-
|
|
3564
|
-
**Updating State** (Flow B):
|
|
3565
|
-
|
|
3566
|
-
```typescript
|
|
3567
|
-
// Update lock state
|
|
3568
|
-
function updateLockState(state: 0 | 1 | 2) {
|
|
3569
|
-
api.matter.updateAccessoryState(
|
|
3570
|
-
uuid,
|
|
3571
|
-
api.matter.clusterNames.DoorLock,
|
|
3572
|
-
{ lockState: state }
|
|
3573
|
-
)
|
|
3574
|
-
|
|
3575
|
-
const stateStr = ['NotFullyLocked', 'Locked', 'Unlocked'][state]
|
|
3576
|
-
log.info(`Lock state: ${stateStr}`)
|
|
3577
|
-
}
|
|
3578
|
-
|
|
3579
|
-
// Example: Lock engaged
|
|
3580
|
-
updateLockState(1) // Locked
|
|
3581
|
-
|
|
3582
|
-
// Example: Lock disengaged
|
|
3583
|
-
updateLockState(2) // Unlocked
|
|
3584
|
-
```
|
|
3585
|
-
|
|
3586
|
-
**Initial State**:
|
|
3587
|
-
|
|
3588
|
-
```typescript
|
|
3589
|
-
clusters: {
|
|
3590
|
-
doorLock: {
|
|
3591
|
-
lockState: api.matter.types.DoorLock.LockState.Unlocked, // 2
|
|
3592
|
-
lockType: api.matter.types.DoorLock.LockType.DeadBolt, // 0
|
|
3593
|
-
actuatorEnabled: true,
|
|
3594
|
-
operatingMode: api.matter.types.DoorLock.OperatingMode.Normal, // 0
|
|
3595
|
-
},
|
|
3596
|
-
}
|
|
3597
|
-
```
|
|
3598
|
-
|
|
3599
|
-
---
|
|
3600
|
-
|
|
3601
|
-
### Robotic Vacuum Cleaner
|
|
3602
|
-
|
|
3603
|
-
| Property | Value |
|
|
3604
|
-
|--------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------|
|
|
3605
|
-
| **Device Type** | `api.matter.deviceTypes.RoboticVacuumCleaner` |
|
|
3606
|
-
| **Description** | A robotic vacuum cleaner with run modes, operational states, and cleaning modes. |
|
|
3607
|
-
| **Matter Specification** | § 12.1 |
|
|
3608
|
-
|
|
3609
|
-
**IMPORTANT**: Robotic vacuum cleaners **must** be published on a dedicated Matter bridge using `api.matter.publishExternalAccessories()` for Apple Home compatibility.
|
|
3610
|
-
|
|
3611
|
-
#### Required Clusters
|
|
3612
|
-
|
|
3613
|
-
###### `RvcRunMode` Cluster
|
|
3614
|
-
|
|
3615
|
-
Controls the vacuum's run mode (Idle, Cleaning, etc.).
|
|
3616
|
-
|
|
3617
|
-
**Attributes**:
|
|
3618
|
-
|
|
3619
|
-
| Attribute | Type | Description |
|
|
3620
|
-
|------------------|--------|------------------------------------------|
|
|
3621
|
-
| `supportedModes` | array | List of supported run modes |
|
|
3622
|
-
| `currentMode` | number | Current run mode |
|
|
3623
|
-
|
|
3624
|
-
**Common Run Mode Tags**:
|
|
3625
|
-
|
|
3626
|
-
| Tag Value | Name | Description |
|
|
3627
|
-
|-----------|----------|--------------------------------|
|
|
3628
|
-
| 16384 | Idle | Vacuum is idle/docked |
|
|
3629
|
-
| 16385 | Cleaning | Vacuum is cleaning |
|
|
3630
|
-
| 16386 | Mapping | Vacuum is mapping the space |
|
|
3631
|
-
|
|
3632
|
-
###### `RvcCleanMode` Cluster
|
|
3633
|
-
|
|
3634
|
-
Controls the vacuum's cleaning mode (Vacuum, Mop, etc.).
|
|
3635
|
-
|
|
3636
|
-
**Attributes**:
|
|
3637
|
-
|
|
3638
|
-
| Attribute | Type | Description |
|
|
3639
|
-
|------------------|--------|------------------------------------------|
|
|
3640
|
-
| `supportedModes` | array | List of supported clean modes |
|
|
3641
|
-
| `currentMode` | number | Current clean mode |
|
|
3642
|
-
|
|
3643
|
-
**Common Clean Mode Tags**:
|
|
3644
|
-
|
|
3645
|
-
| Tag Value | Name | Description |
|
|
3646
|
-
|-----------|--------|--------------------------------|
|
|
3647
|
-
| 16384 | Vacuum | Vacuum only mode |
|
|
3648
|
-
| 16385 | Mop | Mop only mode |
|
|
3649
|
-
| 16386 | Both | Vacuum and mop simultaneously |
|
|
3650
|
-
|
|
3651
|
-
###### `RvcOperationalState` Cluster
|
|
3652
|
-
|
|
3653
|
-
Reports the vacuum's operational state and provides control commands.
|
|
3654
|
-
|
|
3655
|
-
**Attributes**:
|
|
3656
|
-
|
|
3657
|
-
| Attribute | Type | Description |
|
|
3658
|
-
|------------------------|--------|--------------------------------------|
|
|
3659
|
-
| `operationalStateList` | array | List of supported operational states |
|
|
3660
|
-
| `operationalState` | number | Current operational state ID |
|
|
3661
|
-
|
|
3662
|
-
**Common Operational State IDs**:
|
|
3663
|
-
|
|
3664
|
-
| State ID | State | Description |
|
|
3665
|
-
|----------|---------|--------------------------------|
|
|
3666
|
-
| 0 | Stopped | Vacuum is stopped |
|
|
3667
|
-
| 1 | Running | Vacuum is actively cleaning |
|
|
3668
|
-
| 2 | Paused | Vacuum is paused |
|
|
3669
|
-
| 3 | Error | Vacuum encountered an error |
|
|
3670
|
-
|
|
3671
|
-
**Reading State**:
|
|
3672
|
-
|
|
3673
|
-
```typescript
|
|
3674
|
-
const runMode = accessory.clusters.rvcRunMode.currentMode
|
|
3675
|
-
const cleanMode = accessory.clusters.rvcCleanMode.currentMode
|
|
3676
|
-
const opState = accessory.clusters.rvcOperationalState.operationalState
|
|
3677
|
-
|
|
3678
|
-
const runModes = ['Idle', 'Cleaning']
|
|
3679
|
-
const cleanModes = ['Vacuum', 'Mop']
|
|
3680
|
-
const opStates = ['Stopped', 'Running', 'Paused', 'Error']
|
|
3681
|
-
|
|
3682
|
-
log.info(`Run: ${runModes[runMode]}, Clean: ${cleanModes[cleanMode]}, State: ${opStates[opState]}`)
|
|
3683
|
-
```
|
|
3684
|
-
|
|
3685
|
-
<details>
|
|
3686
|
-
<summary><strong>Handlers</strong></summary>
|
|
3687
|
-
|
|
3688
|
-
```typescript
|
|
3689
|
-
import type { MatterRequests } from 'homebridge'
|
|
3690
|
-
|
|
3691
|
-
handlers: {
|
|
3692
|
-
rvcRunMode: {
|
|
3693
|
-
/**
|
|
3694
|
-
* Called when user changes run mode via Home app
|
|
3695
|
-
*/
|
|
3696
|
-
changeToMode: async (request: MatterRequests.ChangeToMode) => {
|
|
3697
|
-
const { newMode } = request
|
|
3698
|
-
const modeStr = ['Idle', 'Cleaning'][newMode] || 'Unknown'
|
|
3699
|
-
|
|
3700
|
-
log.info(`Changing run mode to: ${modeStr}`)
|
|
3701
|
-
await myVacuumAPI.setRunMode(newMode)
|
|
3702
|
-
// State automatically updated by Homebridge
|
|
3703
|
-
},
|
|
3704
|
-
},
|
|
3705
|
-
|
|
3706
|
-
rvcCleanMode: {
|
|
3707
|
-
/**
|
|
3708
|
-
* Called when user changes clean mode via Home app
|
|
3709
|
-
*/
|
|
3710
|
-
changeToMode: async (request: MatterRequests.ChangeToMode) => {
|
|
3711
|
-
const { newMode } = request
|
|
3712
|
-
const modeStr = ['Vacuum', 'Mop'][newMode] || 'Unknown'
|
|
3713
|
-
|
|
3714
|
-
log.info(`Changing clean mode to: ${modeStr}`)
|
|
3715
|
-
await myVacuumAPI.setCleanMode(newMode)
|
|
3716
|
-
// State automatically updated by Homebridge
|
|
3717
|
-
},
|
|
3718
|
-
},
|
|
3719
|
-
|
|
3720
|
-
rvcOperationalState: {
|
|
3721
|
-
/**
|
|
3722
|
-
* Called when user starts the vacuum
|
|
3723
|
-
*/
|
|
3724
|
-
start: async () => {
|
|
3725
|
-
log.info('Starting vacuum')
|
|
3726
|
-
await myVacuumAPI.start()
|
|
3727
|
-
// Update state to Running (1)
|
|
3728
|
-
api.matter.updateAccessoryState(
|
|
3729
|
-
uuid,
|
|
3730
|
-
api.matter.clusterNames.RvcOperationalState,
|
|
3731
|
-
{ operationalState: 1 }
|
|
3732
|
-
)
|
|
3733
|
-
},
|
|
3734
|
-
|
|
3735
|
-
/**
|
|
3736
|
-
* Called when user pauses the vacuum
|
|
3737
|
-
*/
|
|
3738
|
-
pause: async () => {
|
|
3739
|
-
log.info('Pausing vacuum')
|
|
3740
|
-
await myVacuumAPI.pause()
|
|
3741
|
-
// Update state to Paused (2)
|
|
3742
|
-
api.matter.updateAccessoryState(
|
|
3743
|
-
uuid,
|
|
3744
|
-
api.matter.clusterNames.RvcOperationalState,
|
|
3745
|
-
{ operationalState: 2 }
|
|
3746
|
-
)
|
|
3747
|
-
},
|
|
3748
|
-
|
|
3749
|
-
/**
|
|
3750
|
-
* Called when user stops the vacuum
|
|
3751
|
-
*/
|
|
3752
|
-
stop: async () => {
|
|
3753
|
-
log.info('Stopping vacuum')
|
|
3754
|
-
await myVacuumAPI.stop()
|
|
3755
|
-
// Update state to Stopped (0)
|
|
3756
|
-
api.matter.updateAccessoryState(
|
|
3757
|
-
uuid,
|
|
3758
|
-
api.matter.clusterNames.RvcOperationalState,
|
|
3759
|
-
{ operationalState: 0 }
|
|
3760
|
-
)
|
|
3761
|
-
},
|
|
3762
|
-
|
|
3763
|
-
/**
|
|
3764
|
-
* Called when user resumes the vacuum
|
|
3765
|
-
*/
|
|
3766
|
-
resume: async () => {
|
|
3767
|
-
log.info('Resuming vacuum')
|
|
3768
|
-
await myVacuumAPI.resume()
|
|
3769
|
-
// Update state to Running (1)
|
|
3770
|
-
api.matter.updateAccessoryState(
|
|
3771
|
-
uuid,
|
|
3772
|
-
api.matter.clusterNames.RvcOperationalState,
|
|
3773
|
-
{ operationalState: 1 }
|
|
3774
|
-
)
|
|
3775
|
-
},
|
|
3776
|
-
},
|
|
3777
|
-
}
|
|
3778
|
-
```
|
|
3779
|
-
|
|
3780
|
-
</details>
|
|
3781
|
-
|
|
3782
|
-
**Updating State** (Flow B):
|
|
3783
|
-
|
|
3784
|
-
```typescript
|
|
3785
|
-
// Update operational state
|
|
3786
|
-
function updateOperationalState(state: number) {
|
|
3787
|
-
api.matter.updateAccessoryState(
|
|
3788
|
-
uuid,
|
|
3789
|
-
api.matter.clusterNames.RvcOperationalState,
|
|
3790
|
-
{ operationalState: state }
|
|
3791
|
-
)
|
|
3792
|
-
|
|
3793
|
-
const states = ['Stopped', 'Running', 'Paused', 'Error']
|
|
3794
|
-
log.info(`Operational state: ${states[state]}`)
|
|
3795
|
-
}
|
|
3796
|
-
|
|
3797
|
-
// Update run mode
|
|
3798
|
-
function updateRunMode(mode: number) {
|
|
3799
|
-
api.matter.updateAccessoryState(
|
|
3800
|
-
uuid,
|
|
3801
|
-
api.matter.clusterNames.RvcRunMode,
|
|
3802
|
-
{ currentMode: mode }
|
|
3803
|
-
)
|
|
3804
|
-
|
|
3805
|
-
const modes = ['Idle', 'Cleaning']
|
|
3806
|
-
log.info(`Run mode: ${modes[mode]}`)
|
|
3807
|
-
}
|
|
3808
|
-
|
|
3809
|
-
// Update clean mode
|
|
3810
|
-
function updateCleanMode(mode: number) {
|
|
3811
|
-
api.matter.updateAccessoryState(
|
|
3812
|
-
uuid,
|
|
3813
|
-
api.matter.clusterNames.RvcCleanMode,
|
|
3814
|
-
{ currentMode: mode }
|
|
3815
|
-
)
|
|
3816
|
-
|
|
3817
|
-
const modes = ['Vacuum', 'Mop']
|
|
3818
|
-
log.info(`Clean mode: ${modes[mode]}`)
|
|
3819
|
-
}
|
|
3820
|
-
```
|
|
3821
|
-
|
|
3822
|
-
**Initial State**:
|
|
3823
|
-
|
|
3824
|
-
```typescript
|
|
3825
|
-
clusters: {
|
|
3826
|
-
rvcRunMode: {
|
|
3827
|
-
supportedModes: [
|
|
3828
|
-
{ label: 'Idle', mode: 0, modeTags: [{ value: 16384 }] },
|
|
3829
|
-
{ label: 'Cleaning', mode: 1, modeTags: [{ value: 16385 }] },
|
|
3830
|
-
],
|
|
3831
|
-
currentMode: 0, // Idle
|
|
3832
|
-
},
|
|
3833
|
-
rvcCleanMode: {
|
|
3834
|
-
supportedModes: [
|
|
3835
|
-
{ label: 'Vacuum', mode: 0, modeTags: [{ value: 16384 }] },
|
|
3836
|
-
{ label: 'Mop', mode: 1, modeTags: [{ value: 16385 }] },
|
|
3837
|
-
],
|
|
3838
|
-
currentMode: 0, // Vacuum
|
|
3839
|
-
},
|
|
3840
|
-
rvcOperationalState: {
|
|
3841
|
-
operationalStateList: [
|
|
3842
|
-
{ operationalStateId: 0, operationalStateLabel: 'Stopped' },
|
|
3843
|
-
{ operationalStateId: 1, operationalStateLabel: 'Running' },
|
|
3844
|
-
{ operationalStateId: 2, operationalStateLabel: 'Paused' },
|
|
3845
|
-
{ operationalStateId: 3, operationalStateLabel: 'Error' },
|
|
3846
|
-
],
|
|
3847
|
-
operationalState: 0, // Stopped
|
|
3848
|
-
},
|
|
3849
|
-
}
|
|
3850
|
-
```
|
|
3851
|
-
|
|
3852
|
-
**Publishing** (Required for Apple Home):
|
|
3853
|
-
|
|
3854
|
-
```typescript
|
|
3855
|
-
// IMPORTANT: Robotic vacuums must be published externally
|
|
3856
|
-
const accessories = [
|
|
3857
|
-
{
|
|
3858
|
-
uuid: api.matter.uuid.generate('robot-vacuum'),
|
|
3859
|
-
displayName: 'Robot Vacuum',
|
|
3860
|
-
deviceType: api.matter.deviceTypes.RoboticVacuumCleaner,
|
|
3861
|
-
// ... configuration
|
|
3862
|
-
}
|
|
3863
|
-
]
|
|
3864
|
-
|
|
3865
|
-
// Use publishExternalAccessories instead of registerPlatformAccessories
|
|
3866
|
-
api.matter.publishExternalAccessories(PLUGIN_NAME, accessories)
|
|
3867
|
-
|
|
3868
|
-
// This creates a dedicated Matter bridge with its own QR code
|
|
3869
|
-
```
|