zigbee-clusters 2.6.0 → 2.8.0
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/AGENTS.md +451 -0
- package/README.md +22 -0
- package/index.d.ts +1009 -272
- package/lib/Cluster.js +39 -20
- package/lib/clusters/doorLock.js +614 -2
- package/lib/clusters/metering.js +245 -9
- package/lib/clusters/occupancySensing.js +49 -13
- package/lib/clusters/windowCovering.js +58 -23
- package/package.json +4 -1
- package/scripts/generate-types.js +360 -0
package/AGENTS.md
ADDED
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to AI agents when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Commands
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm test # Run all tests (mocha)
|
|
9
|
+
npm run lint # ESLint (extends athom config)
|
|
10
|
+
npm run build # Generate JSDoc documentation
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Run single test file:
|
|
14
|
+
```bash
|
|
15
|
+
npx mocha test/onOff.js
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Architecture
|
|
19
|
+
|
|
20
|
+
Zigbee Cluster Library (ZCL) implementation for Node.js, designed for Homey's Zigbee stack.
|
|
21
|
+
|
|
22
|
+
### Core Classes
|
|
23
|
+
|
|
24
|
+
- **ZCLNode** (`lib/Node.js`) - Entry point. Wraps Homey's ZigBeeNode, manages endpoints, routes incoming frames.
|
|
25
|
+
- **Endpoint** (`lib/Endpoint.js`) - Represents device endpoint. Contains `clusters` (client) and `bindings` (server). Routes frames to appropriate cluster.
|
|
26
|
+
- **Cluster** (`lib/Cluster.js`) - Base class for all clusters. Handles frame parsing, command execution, attribute operations. Commands auto-generate methods via `_addPrototypeMethods`.
|
|
27
|
+
- **BoundCluster** (`lib/BoundCluster.js`) - Server-side cluster for receiving commands from remote nodes.
|
|
28
|
+
|
|
29
|
+
### Data Flow
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
ZCLNode.handleFrame() → Endpoint.handleFrame() → Cluster/BoundCluster.handleFrame()
|
|
33
|
+
Cluster.sendCommand() → Endpoint.sendFrame() → ZCLNode.sendFrame()
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Cluster Implementation Pattern
|
|
37
|
+
|
|
38
|
+
Each cluster in `lib/clusters/` follows:
|
|
39
|
+
1. Define `ATTRIBUTES` object with `{id, type}` using `ZCLDataTypes`
|
|
40
|
+
2. Define `COMMANDS` object with `{id, args?}`
|
|
41
|
+
3. Extend `Cluster` with static getters: `ID`, `NAME`, `ATTRIBUTES`, `COMMANDS`
|
|
42
|
+
4. Call `Cluster.addCluster(MyCluster)` to register
|
|
43
|
+
|
|
44
|
+
Example: `lib/clusters/onOff.js`
|
|
45
|
+
|
|
46
|
+
### Key Types
|
|
47
|
+
|
|
48
|
+
- `ZCLDataTypes` - Primitive types (uint8, bool, enum8, etc.) from `@athombv/data-types`
|
|
49
|
+
- `ZCLStruct` - Composite types for command arguments
|
|
50
|
+
- `CLUSTER` constant - Maps cluster names to `{ID, NAME, ATTRIBUTES, COMMANDS}`
|
|
51
|
+
|
|
52
|
+
### Cluster ID References
|
|
53
|
+
|
|
54
|
+
Prefer `Cluster.ID` over hardcoded numbers:
|
|
55
|
+
```javascript
|
|
56
|
+
// Good
|
|
57
|
+
const OnOffCluster = require('../lib/clusters/onOff');
|
|
58
|
+
inputClusters: [OnOffCluster.ID]
|
|
59
|
+
|
|
60
|
+
// Avoid
|
|
61
|
+
inputClusters: [6]
|
|
62
|
+
inputClusters: [0x0006]
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Test Pattern
|
|
66
|
+
|
|
67
|
+
Tests use mock nodes from `test/util/mockNode.js`:
|
|
68
|
+
|
|
69
|
+
**Single node with loopback** (command → same node's BoundCluster):
|
|
70
|
+
```javascript
|
|
71
|
+
const { createMockNode } = require('./util');
|
|
72
|
+
const OnOffCluster = require('../lib/clusters/onOff');
|
|
73
|
+
|
|
74
|
+
const node = createMockNode({
|
|
75
|
+
loopback: true,
|
|
76
|
+
endpoints: [{ endpointId: 1, inputClusters: [OnOffCluster.ID] }],
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
node.endpoints[1].bind('onOff', new (class extends BoundCluster {
|
|
80
|
+
async setOn() { /* handle command */ }
|
|
81
|
+
})());
|
|
82
|
+
|
|
83
|
+
await node.endpoints[1].clusters.onOff.setOn(); // loops back to BoundCluster
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**Connected node pair** (node A sends → node B receives):
|
|
87
|
+
```javascript
|
|
88
|
+
const { createConnectedNodePair } = require('./util');
|
|
89
|
+
|
|
90
|
+
const [sender, receiver] = createConnectedNodePair(
|
|
91
|
+
{ endpoints: [{ endpointId: 1, inputClusters: [6] }] },
|
|
92
|
+
{ endpoints: [{ endpointId: 1, inputClusters: [6] }] },
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
receiver.endpoints[1].bind('onOff', new BoundCluster());
|
|
96
|
+
await sender.endpoints[1].clusters.onOff.toggle();
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**Server-to-client notifications** (device → controller) still require manual frames:
|
|
100
|
+
```javascript
|
|
101
|
+
const { ZCLStandardHeader } = require('../lib/zclFrames');
|
|
102
|
+
|
|
103
|
+
node.endpoints[1].clusters.iasZone.onZoneStatusChangeNotification = data => { ... };
|
|
104
|
+
|
|
105
|
+
const frame = new ZCLStandardHeader();
|
|
106
|
+
frame.cmdId = IASZoneCluster.COMMANDS.zoneStatusChangeNotification.id;
|
|
107
|
+
frame.frameControl.directionToClient = true;
|
|
108
|
+
frame.frameControl.clusterSpecific = true;
|
|
109
|
+
frame.data = Buffer.from([...]);
|
|
110
|
+
|
|
111
|
+
node.handleFrame(1, IASZoneCluster.ID, frame.toBuffer(), {});
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**Preset mock devices** for common sensor types:
|
|
115
|
+
```javascript
|
|
116
|
+
const { MOCK_DEVICES } = require('./util');
|
|
117
|
+
|
|
118
|
+
const sensor = MOCK_DEVICES.motionSensor();
|
|
119
|
+
const boundCluster = sensor.endpoints[1].bindings.iasZone;
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Key Files
|
|
123
|
+
|
|
124
|
+
- `index.js` - Public exports
|
|
125
|
+
- `lib/clusters/index.js` - All cluster exports + `CLUSTER` constant
|
|
126
|
+
- `lib/zclTypes.js` - ZCL data types
|
|
127
|
+
- `lib/zclFrames.js` - ZCL frame parsing/serialization
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## Adding/Updating Cluster Definitions
|
|
132
|
+
|
|
133
|
+
Reusable guide for adding new clusters or updating existing cluster definitions.
|
|
134
|
+
|
|
135
|
+
### Source Reference
|
|
136
|
+
|
|
137
|
+
- **Spec PDF**: `docs/zigbee-cluster-specification-r8.pdf`
|
|
138
|
+
- **Extract text**: `pdftotext docs/zigbee-cluster-specification-r8.pdf -`
|
|
139
|
+
- **Cluster files**: `lib/clusters/*.js`
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
### File Structure Template
|
|
144
|
+
|
|
145
|
+
```javascript
|
|
146
|
+
'use strict';
|
|
147
|
+
|
|
148
|
+
const Cluster = require('../Cluster');
|
|
149
|
+
const { ZCLDataTypes } = require('../zclTypes');
|
|
150
|
+
|
|
151
|
+
// Reusable enum definitions (if needed)
|
|
152
|
+
const EXAMPLE_ENUM = ZCLDataTypes.enum8({
|
|
153
|
+
value1: 0,
|
|
154
|
+
value2: 1,
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// ============================================================================
|
|
158
|
+
// Server Attributes
|
|
159
|
+
// ============================================================================
|
|
160
|
+
const ATTRIBUTES = {
|
|
161
|
+
// Section Name (0x0000 - 0x000F)
|
|
162
|
+
|
|
163
|
+
// Description from spec, copied 1-on-1.
|
|
164
|
+
// Multi-line if needed, wrapped at 100 chars.
|
|
165
|
+
attrName: { id: 0x0000, type: ZCLDataTypes.uint8 }, // Mandatory
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
// ============================================================================
|
|
169
|
+
// Commands
|
|
170
|
+
// ============================================================================
|
|
171
|
+
const COMMANDS = {
|
|
172
|
+
// --- Client to Server Commands ---
|
|
173
|
+
|
|
174
|
+
// Description from spec.
|
|
175
|
+
commandName: { // Mandatory
|
|
176
|
+
id: 0x0000,
|
|
177
|
+
args: {
|
|
178
|
+
argName: ZCLDataTypes.uint8,
|
|
179
|
+
},
|
|
180
|
+
response: {
|
|
181
|
+
id: 0x0000,
|
|
182
|
+
args: { status: ZCLDataTypes.uint8 },
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
|
|
186
|
+
// --- Server to Client Commands ---
|
|
187
|
+
|
|
188
|
+
// Description from spec.
|
|
189
|
+
notificationName: { // Optional
|
|
190
|
+
id: 0x0020, // 32
|
|
191
|
+
direction: Cluster.DIRECTION_SERVER_TO_CLIENT,
|
|
192
|
+
args: {
|
|
193
|
+
argName: ZCLDataTypes.uint8,
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
class ExampleCluster extends Cluster {
|
|
199
|
+
static get ID() {
|
|
200
|
+
return 0x0000; // Add decimal comment if > 9, e.g.: return 0x0101; // 257
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
static get NAME() {
|
|
204
|
+
return 'example';
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
static get ATTRIBUTES() {
|
|
208
|
+
return ATTRIBUTES;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
static get COMMANDS() {
|
|
212
|
+
return COMMANDS;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
Cluster.addCluster(ExampleCluster);
|
|
217
|
+
module.exports = ExampleCluster;
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
### Comment Format Rules
|
|
223
|
+
|
|
224
|
+
1. **Description placement**: ABOVE the attribute/command
|
|
225
|
+
2. **M/O marker placement**:
|
|
226
|
+
- Single-line: at END of line (`attrName: { ... }, // Mandatory`)
|
|
227
|
+
- Multi-line: on OPENING brace (`attrName: { // Mandatory`)
|
|
228
|
+
- NEVER on closing brace
|
|
229
|
+
3. **Copy exactly**: Text from spec, 1-on-1
|
|
230
|
+
4. **Skip if >5 sentences**: Skip and only refer to section in spec
|
|
231
|
+
5. **Line wrap**: Respect 100 char limit (ESLint)
|
|
232
|
+
6. **M/O source for attrs**: Server-side column in spec table
|
|
233
|
+
7. **M/O source for cmds**:
|
|
234
|
+
- Server "receives" → server-side M/O
|
|
235
|
+
- Server "generates" → client-side M/O
|
|
236
|
+
8. **Conditional (M*)**: Use `// Conditional¹` with footnote for attrs/cmds marked "M*" in spec
|
|
237
|
+
- Reason on new line below: `// ¹ Reason here`
|
|
238
|
+
- Shared reasons use same footnote number
|
|
239
|
+
|
|
240
|
+
#### Example
|
|
241
|
+
|
|
242
|
+
```javascript
|
|
243
|
+
currentLevel: { id: 0x0000, type: ZCLDataTypes.uint8 }, // Mandatory
|
|
244
|
+
pirOccupiedToUnoccupiedDelay: { id: 0x0010, type: ZCLDataTypes.uint16 }, // 16, Conditional¹
|
|
245
|
+
pirUnoccupiedToOccupiedDelay: { id: 0x0011, type: ZCLDataTypes.uint16 }, // 17, Conditional¹
|
|
246
|
+
// ¹ PIR sensor type supported
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
### Attribute Definition Rules
|
|
252
|
+
|
|
253
|
+
| Field | Format | Notes |
|
|
254
|
+
|-------|--------|-------|
|
|
255
|
+
| `id` | Hex (`0x0000`) | Always 4-digit format (0x0000); add decimal comment if > 9 |
|
|
256
|
+
| `type` | `ZCLDataTypes.*` | See type reference below |
|
|
257
|
+
| M/O | Inline comment | `// Mandatory`, `// Optional`, or `// Conditional` |
|
|
258
|
+
|
|
259
|
+
#### Hex with Decimal Comments
|
|
260
|
+
|
|
261
|
+
For hex values > 9 (where hex differs from decimal), add decimal in comment:
|
|
262
|
+
```javascript
|
|
263
|
+
id: 0x000A, // 10
|
|
264
|
+
id: 0x0010, // 16
|
|
265
|
+
id: 0x0100, // 256
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
For values 0-9, no decimal comment needed:
|
|
269
|
+
```javascript
|
|
270
|
+
id: 0x0000,
|
|
271
|
+
id: 0x0009,
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
For multi-line definitions, decimal goes on the `id:` line, M/O on opening brace:
|
|
275
|
+
```javascript
|
|
276
|
+
operatingMode: { // Optional
|
|
277
|
+
id: 0x0025, // 37
|
|
278
|
+
type: ZCLDataTypes.enum8({
|
|
279
|
+
normal: 0,
|
|
280
|
+
vacation: 1,
|
|
281
|
+
}),
|
|
282
|
+
},
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
#### Section Comments
|
|
286
|
+
|
|
287
|
+
Group attrs by function with section headers:
|
|
288
|
+
```javascript
|
|
289
|
+
// Section Name (0x0000 - 0x000F)
|
|
290
|
+
attr1: { ... },
|
|
291
|
+
attr2: { ... },
|
|
292
|
+
|
|
293
|
+
// Another Section (0x0010 - 0x001F)
|
|
294
|
+
attr3: { ... },
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
---
|
|
298
|
+
|
|
299
|
+
### Command Definition Rules
|
|
300
|
+
|
|
301
|
+
| Field | Required | Notes |
|
|
302
|
+
|-------|----------|-------|
|
|
303
|
+
| `id` | Yes | Always 4-digit hex format (0x0000); add decimal comment if > 9 |
|
|
304
|
+
| `args` | If has params | Object with typed fields |
|
|
305
|
+
| `response` | If expects response | Has own `id` and `args` |
|
|
306
|
+
| `direction` | For server→client | `Cluster.DIRECTION_SERVER_TO_CLIENT` |
|
|
307
|
+
|
|
308
|
+
#### Command Sections
|
|
309
|
+
|
|
310
|
+
```javascript
|
|
311
|
+
// --- Client to Server Commands ---
|
|
312
|
+
lockDoor: { id: 0x0000, ... }, // Mandatory
|
|
313
|
+
|
|
314
|
+
// --- Server to Client Commands ---
|
|
315
|
+
operationEventNotification: { // Optional
|
|
316
|
+
id: 0x0020, // 32
|
|
317
|
+
direction: Cluster.DIRECTION_SERVER_TO_CLIENT,
|
|
318
|
+
...
|
|
319
|
+
},
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
#### Command Direction Rules
|
|
323
|
+
|
|
324
|
+
Focus on **client→server commands** with inline `response:` when applicable:
|
|
325
|
+
```javascript
|
|
326
|
+
lockDoor: {
|
|
327
|
+
id: 0x0000,
|
|
328
|
+
args: { pinCode: ZCLDataTypes.octstr },
|
|
329
|
+
response: {
|
|
330
|
+
id: 0x0000,
|
|
331
|
+
args: { status: ZCLDataTypes.uint8 },
|
|
332
|
+
},
|
|
333
|
+
},
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
**Server→client commands** (events/notifications) should be evaluated per case:
|
|
337
|
+
- Implement if commonly needed (e.g., `operationEventNotification` for door locks)
|
|
338
|
+
- Skip obscure or rarely-used notifications unless specifically requested
|
|
339
|
+
- These require `direction: Cluster.DIRECTION_SERVER_TO_CLIENT`
|
|
340
|
+
|
|
341
|
+
---
|
|
342
|
+
|
|
343
|
+
### ZCLDataTypes Reference
|
|
344
|
+
|
|
345
|
+
#### Primitives
|
|
346
|
+
- `bool`, `uint8`, `uint16`, `uint24`, `uint32`, `uint48`, `uint64`
|
|
347
|
+
- `int8`, `int16`, `int24`, `int32`
|
|
348
|
+
- `string`, `octstr`
|
|
349
|
+
|
|
350
|
+
#### Enums
|
|
351
|
+
```javascript
|
|
352
|
+
ZCLDataTypes.enum8({
|
|
353
|
+
valueName: 0,
|
|
354
|
+
anotherValue: 1,
|
|
355
|
+
})
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
#### Bitmaps
|
|
359
|
+
```javascript
|
|
360
|
+
ZCLDataTypes.map8('bit0', 'bit1', 'bit2')
|
|
361
|
+
ZCLDataTypes.map16('bit0', 'bit1', ...)
|
|
362
|
+
ZCLDataTypes.map64(...)
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
#### Arrays
|
|
366
|
+
```javascript
|
|
367
|
+
ZCLDataTypes.Array0(ZCLDataTypes.uint8)
|
|
368
|
+
ZCLDataTypes.Array8(...)
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
#### Reusable Enums
|
|
372
|
+
Define at module level if used multiple times:
|
|
373
|
+
```javascript
|
|
374
|
+
const USER_STATUS_ENUM = ZCLDataTypes.enum8({
|
|
375
|
+
available: 0,
|
|
376
|
+
occupied: 1,
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
// Then use in commands:
|
|
380
|
+
args: { userStatus: USER_STATUS_ENUM }
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
#### Reusable Bitmaps
|
|
384
|
+
Same pattern for bitmaps used multiple times:
|
|
385
|
+
```javascript
|
|
386
|
+
const ALARM_MASK = ZCLDataTypes.map8(
|
|
387
|
+
'generalHardwareFault',
|
|
388
|
+
'generalSoftwareFault',
|
|
389
|
+
'reserved2',
|
|
390
|
+
'reserved3',
|
|
391
|
+
);
|
|
392
|
+
|
|
393
|
+
// Then use in attributes/commands:
|
|
394
|
+
alarmMask: { id: 0x0010, type: ALARM_MASK },
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
---
|
|
398
|
+
|
|
399
|
+
### Workflow: Adding/Updating a Cluster
|
|
400
|
+
|
|
401
|
+
#### 1. Extract Spec Section
|
|
402
|
+
```bash
|
|
403
|
+
pdftotext docs/zigbee-cluster-specification-r8.pdf - | grep -A 500 "X.Y.Z Cluster Name"
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
#### 2. Identify Elements
|
|
407
|
+
From spec tables, extract:
|
|
408
|
+
- Cluster ID and Name
|
|
409
|
+
- All attributes (ID, name, type, M/O)
|
|
410
|
+
- All commands (ID, name, args, direction, M/O)
|
|
411
|
+
- Descriptions (≤5 sentences)
|
|
412
|
+
|
|
413
|
+
#### 3. Create/Update File
|
|
414
|
+
- Use template above
|
|
415
|
+
- Follow naming: `lib/clusters/clusterName.js`
|
|
416
|
+
- Export in `lib/clusters/index.js`
|
|
417
|
+
|
|
418
|
+
#### 4. Validate
|
|
419
|
+
```bash
|
|
420
|
+
npm run lint
|
|
421
|
+
npm test
|
|
422
|
+
npm run build
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
---
|
|
426
|
+
|
|
427
|
+
### Checklist for Each Cluster
|
|
428
|
+
|
|
429
|
+
- [ ] Cluster ID correct (hex in class, matches spec)
|
|
430
|
+
- [ ] Cluster NAME matches spec (camelCase)
|
|
431
|
+
- [ ] All mandatory attrs present with `// Mandatory`
|
|
432
|
+
- [ ] All mandatory cmds present with `// Mandatory`
|
|
433
|
+
- [ ] Conditional attrs/cmds marked `// Conditional` (M* in spec)
|
|
434
|
+
- [ ] Optional attrs/cmds marked `// Optional`
|
|
435
|
+
- [ ] Descriptions copied from spec (≤5 sentences)
|
|
436
|
+
- [ ] Section comments group related attrs
|
|
437
|
+
- [ ] Client/server cmd sections separated
|
|
438
|
+
- [ ] Server→client cmds have `direction` field
|
|
439
|
+
- [ ] Responses defined where applicable
|
|
440
|
+
- [ ] Reusable enums/bitmaps extracted if used 2+ times
|
|
441
|
+
- [ ] Hex IDs used consistently (with decimal comments if > 9)
|
|
442
|
+
- [ ] Lint passes
|
|
443
|
+
- [ ] Tests pass
|
|
444
|
+
|
|
445
|
+
---
|
|
446
|
+
|
|
447
|
+
### Reference Examples
|
|
448
|
+
|
|
449
|
+
- **Best documented**: `lib/clusters/doorLock.js`
|
|
450
|
+
- **Simple attrs only**: `lib/clusters/metering.js`
|
|
451
|
+
- **Color/enum heavy**: `lib/clusters/colorControl.js`
|
package/README.md
CHANGED
|
@@ -301,6 +301,28 @@ zclNode.endpoints[1].clusters["scenes"].ikeaSceneMove({ mode: 0, transitionTime:
|
|
|
301
301
|
|
|
302
302
|
This also works for `BoundClusters`, if a node sends commands to Homey using a custom cluster it is necessary to implement a custom `BoundCluster` and bind it to the `ZCLNode` instance. For an example check the implementation in the `com.ikea.tradfri` driver [remote_control](https://github.com/athombv/com.ikea.tradfri-example/tree/master/drivers/remote_control/device.js).
|
|
303
303
|
|
|
304
|
+
## TypeScript Types
|
|
305
|
+
|
|
306
|
+
This project includes auto-generated TypeScript definitions (`index.d.ts`) for all clusters, attributes, and commands.
|
|
307
|
+
|
|
308
|
+
### Manual Generation
|
|
309
|
+
|
|
310
|
+
To regenerate TypeScript types after modifying cluster definitions:
|
|
311
|
+
|
|
312
|
+
```bash
|
|
313
|
+
npm run generate-types
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
This runs `scripts/generate-types.js` which loads all cluster modules and generates typed interfaces.
|
|
317
|
+
|
|
318
|
+
### Automatic Generation (GitHub Actions)
|
|
319
|
+
|
|
320
|
+
TypeScript types are automatically regenerated when changes are pushed to the `develop` branch that affect:
|
|
321
|
+
- `lib/clusters/**/*.js` - cluster definitions
|
|
322
|
+
- `scripts/generate-types.js` - the generator script
|
|
323
|
+
|
|
324
|
+
The workflow commits updated types back to `develop` if changes are detected.
|
|
325
|
+
|
|
304
326
|
## Contributing
|
|
305
327
|
|
|
306
328
|
Great if you'd like to contribute to this project, a few things to take note of before submitting a PR:
|