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
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/* eslint-disable no-console, global-require */
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Type generation script for zigbee-clusters
|
|
7
|
+
* Loads cluster modules directly and generates TypeScript interfaces
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
|
|
13
|
+
const OUTPUT_FILE = path.join(__dirname, '../index.d.ts');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Convert a ZCLDataType object to TypeScript type string
|
|
17
|
+
* @param {object} dataType - ZCLDataType object with shortName and args
|
|
18
|
+
* @returns {string} TypeScript type string
|
|
19
|
+
*/
|
|
20
|
+
function zclTypeToTS(dataType) {
|
|
21
|
+
if (!dataType || !dataType.shortName) return 'unknown';
|
|
22
|
+
|
|
23
|
+
const { shortName, args } = dataType;
|
|
24
|
+
|
|
25
|
+
// No data
|
|
26
|
+
if (shortName === 'noData') return 'null';
|
|
27
|
+
|
|
28
|
+
// Boolean
|
|
29
|
+
if (shortName === 'bool') return 'boolean';
|
|
30
|
+
|
|
31
|
+
// Numeric types
|
|
32
|
+
if (/^u?int\d+$/.test(shortName)) return 'number';
|
|
33
|
+
// data8-32 use readUIntBE, return number
|
|
34
|
+
if (/^data(8|16|24|32)$/.test(shortName)) return 'number';
|
|
35
|
+
// data40-64 use buf.slice, return Buffer
|
|
36
|
+
if (/^data(40|48|56|64)$/.test(shortName)) return 'Buffer';
|
|
37
|
+
// Float types
|
|
38
|
+
if (shortName === 'single' || shortName === 'double') return 'number';
|
|
39
|
+
|
|
40
|
+
// String types
|
|
41
|
+
if (shortName === 'string' || shortName === '_FixedString') return 'string';
|
|
42
|
+
// EUI addresses return colon-separated hex strings
|
|
43
|
+
if (shortName === 'EUI64' || shortName === 'EUI48') return 'string';
|
|
44
|
+
// key128 returns colon-separated hex string
|
|
45
|
+
if (shortName === 'key128') return 'string';
|
|
46
|
+
|
|
47
|
+
// Buffer types
|
|
48
|
+
if (shortName === 'octstr' || shortName === '_buffer' || shortName === '_buffer8' || shortName === '_buffer16') {
|
|
49
|
+
return 'Buffer';
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Enum types - extract keys from args[0]
|
|
53
|
+
if (/^enum(4|8|16|32)$/.test(shortName)) {
|
|
54
|
+
if (args && args[0] && typeof args[0] === 'object') {
|
|
55
|
+
const keys = Object.keys(args[0]);
|
|
56
|
+
if (keys.length > 0) {
|
|
57
|
+
return keys.map(k => `'${k}'`).join(' | ');
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return 'number';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Map/bitmap types - extract flag names from args
|
|
64
|
+
if (/^map(4|\d+)$/.test(shortName)) {
|
|
65
|
+
if (args && args.length > 0) {
|
|
66
|
+
const flags = args.filter(a => typeof a === 'string');
|
|
67
|
+
if (flags.length > 0) {
|
|
68
|
+
return `Partial<{ ${flags.map(f => `${f}: boolean`).join('; ')} }>`;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return 'Partial<Record<string, boolean>>';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Array types (note: shortName has underscore prefix: _Array0, _Array8)
|
|
75
|
+
// Recursively determine element type from args[0]
|
|
76
|
+
if (/^_?Array(0|8|16)$/.test(shortName)) {
|
|
77
|
+
if (args && args[0]) {
|
|
78
|
+
const elementType = zclTypeToTS(args[0]);
|
|
79
|
+
return `${elementType}[]`;
|
|
80
|
+
}
|
|
81
|
+
return 'unknown[]';
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return 'unknown';
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Parse a cluster and extract its type information
|
|
89
|
+
* @param {Function} ClusterClass - Cluster class with static ATTRIBUTES/COMMANDS
|
|
90
|
+
* @returns {object} Cluster definition
|
|
91
|
+
*/
|
|
92
|
+
function parseCluster(ClusterClass) {
|
|
93
|
+
const clusterName = ClusterClass.NAME;
|
|
94
|
+
const clusterId = ClusterClass.ID;
|
|
95
|
+
const attributes = [];
|
|
96
|
+
const commands = [];
|
|
97
|
+
|
|
98
|
+
// Parse attributes
|
|
99
|
+
const attrs = ClusterClass.ATTRIBUTES || {};
|
|
100
|
+
for (const [name, def] of Object.entries(attrs)) {
|
|
101
|
+
if (def && def.type) {
|
|
102
|
+
attributes.push({
|
|
103
|
+
name,
|
|
104
|
+
tsType: zclTypeToTS(def.type),
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Parse commands
|
|
110
|
+
const cmds = ClusterClass.COMMANDS || {};
|
|
111
|
+
for (const [name, def] of Object.entries(cmds)) {
|
|
112
|
+
const cmdArgs = [];
|
|
113
|
+
const responseArgs = [];
|
|
114
|
+
if (def && def.args) {
|
|
115
|
+
for (const [argName, argType] of Object.entries(def.args)) {
|
|
116
|
+
cmdArgs.push({
|
|
117
|
+
name: argName,
|
|
118
|
+
tsType: zclTypeToTS(argType),
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
// Parse response type if present
|
|
123
|
+
if (def && def.response && def.response.args) {
|
|
124
|
+
for (const [argName, argType] of Object.entries(def.response.args)) {
|
|
125
|
+
responseArgs.push({
|
|
126
|
+
name: argName,
|
|
127
|
+
tsType: zclTypeToTS(argType),
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
commands.push({ name, args: cmdArgs, responseArgs });
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
clusterName, clusterId, attributes, commands,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Convert cluster name to PascalCase interface name
|
|
141
|
+
* @param {string} clusterName
|
|
142
|
+
* @returns {string}
|
|
143
|
+
*/
|
|
144
|
+
function toInterfaceName(clusterName) {
|
|
145
|
+
const name = clusterName.charAt(0).toUpperCase() + clusterName.slice(1);
|
|
146
|
+
return `${name}Cluster`;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Generate TypeScript interface for a cluster
|
|
151
|
+
* @param {object} cluster - Parsed cluster definition
|
|
152
|
+
* @returns {string} TypeScript interface code
|
|
153
|
+
*/
|
|
154
|
+
function generateClusterInterface(cluster) {
|
|
155
|
+
const interfaceName = toInterfaceName(cluster.clusterName);
|
|
156
|
+
const lines = [];
|
|
157
|
+
|
|
158
|
+
// Generate attributes interface
|
|
159
|
+
if (cluster.attributes.length > 0) {
|
|
160
|
+
lines.push(`export interface ${interfaceName}Attributes {`);
|
|
161
|
+
for (const attr of cluster.attributes) {
|
|
162
|
+
lines.push(` ${attr.name}?: ${attr.tsType};`);
|
|
163
|
+
}
|
|
164
|
+
lines.push('}');
|
|
165
|
+
lines.push('');
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Generate cluster interface
|
|
169
|
+
lines.push(`export interface ${interfaceName} extends ZCLNodeCluster {`);
|
|
170
|
+
|
|
171
|
+
// Add typed readAttributes if we have attributes
|
|
172
|
+
if (cluster.attributes.length > 0) {
|
|
173
|
+
const attrNames = cluster.attributes.map(a => `'${a.name}'`).join(' | ');
|
|
174
|
+
lines.push(` readAttributes<K extends ${attrNames}>(attributeNames: K[], opts?: { timeout?: number }): Promise<Pick<${interfaceName}Attributes, K>>;`);
|
|
175
|
+
lines.push(` writeAttributes(attributes: Partial<${interfaceName}Attributes>): Promise<void>;`);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Add command methods
|
|
179
|
+
for (const cmd of cluster.commands) {
|
|
180
|
+
// Determine return type based on response args
|
|
181
|
+
let returnType = 'void';
|
|
182
|
+
if (cmd.responseArgs && cmd.responseArgs.length > 0) {
|
|
183
|
+
returnType = `{ ${cmd.responseArgs.map(a => `${a.name}: ${a.tsType}`).join('; ')} }`;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (cmd.args.length > 0) {
|
|
187
|
+
// Buffer arguments are optional - ZCL allows empty octet strings
|
|
188
|
+
const allArgsOptional = cmd.args.every(a => a.tsType === 'Buffer');
|
|
189
|
+
const argsType = `{ ${cmd.args.map(a => `${a.name}${a.tsType === 'Buffer' ? '?' : ''}: ${a.tsType}`).join('; ')} }`;
|
|
190
|
+
// If all args are optional, make the entire args object optional
|
|
191
|
+
lines.push(` ${cmd.name}(args${allArgsOptional ? '?' : ''}: ${argsType}): Promise<${returnType}>;`);
|
|
192
|
+
} else {
|
|
193
|
+
lines.push(` ${cmd.name}(): Promise<${returnType}>;`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
lines.push('}');
|
|
198
|
+
|
|
199
|
+
return lines.join('\n');
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Generate the full index.d.ts file
|
|
204
|
+
* @param {object[]} clusters - Array of parsed cluster definitions
|
|
205
|
+
* @returns {string} Complete TypeScript definitions file
|
|
206
|
+
*/
|
|
207
|
+
function generateTypesFile(clusters) {
|
|
208
|
+
const lines = [];
|
|
209
|
+
|
|
210
|
+
// Header
|
|
211
|
+
lines.push('// Auto-generated TypeScript definitions for zigbee-clusters');
|
|
212
|
+
lines.push('// Generated by scripts/generate-types.js');
|
|
213
|
+
lines.push('');
|
|
214
|
+
lines.push('import * as EventEmitter from "events";');
|
|
215
|
+
lines.push('');
|
|
216
|
+
|
|
217
|
+
// Base types
|
|
218
|
+
lines.push(`type EndpointDescriptor = {
|
|
219
|
+
endpointId: number;
|
|
220
|
+
inputClusters: number[];
|
|
221
|
+
outputClusters: number[];
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
type ConstructorOptions = {
|
|
225
|
+
endpointDescriptors: EndpointDescriptor[];
|
|
226
|
+
sendFrame: (endpointId: number, clusterId: number, frame: Buffer) => Promise<void>;
|
|
227
|
+
};
|
|
228
|
+
`);
|
|
229
|
+
|
|
230
|
+
// Base ZCLNodeCluster interface
|
|
231
|
+
lines.push(`export interface ZCLNodeCluster extends EventEmitter {
|
|
232
|
+
discoverCommandsGenerated(opts?: {
|
|
233
|
+
startValue?: number;
|
|
234
|
+
maxResults?: number;
|
|
235
|
+
}): Promise<number[]>;
|
|
236
|
+
|
|
237
|
+
discoverCommandsReceived(opts?: {
|
|
238
|
+
startValue?: number;
|
|
239
|
+
maxResults?: number;
|
|
240
|
+
}): Promise<number[]>;
|
|
241
|
+
|
|
242
|
+
readAttributes(
|
|
243
|
+
attributeNames: string[],
|
|
244
|
+
opts?: { timeout?: number }
|
|
245
|
+
): Promise<{ [x: string]: unknown }>;
|
|
246
|
+
|
|
247
|
+
writeAttributes(attributes?: object): Promise<void>;
|
|
248
|
+
|
|
249
|
+
configureReporting(attributes?: object): Promise<void>;
|
|
250
|
+
|
|
251
|
+
readReportingConfiguration(attributes?: (string | number)[]): Promise<{
|
|
252
|
+
status: string;
|
|
253
|
+
direction: 'reported' | 'received';
|
|
254
|
+
attributeId: number;
|
|
255
|
+
attributeDataType?: number;
|
|
256
|
+
minInterval?: number;
|
|
257
|
+
maxInterval?: number;
|
|
258
|
+
minChange?: number;
|
|
259
|
+
timeoutPeriod?: number;
|
|
260
|
+
}[]>;
|
|
261
|
+
|
|
262
|
+
discoverAttributes(): Promise<(string | number)[]>;
|
|
263
|
+
|
|
264
|
+
discoverAttributesExtended(): Promise<{
|
|
265
|
+
name?: string;
|
|
266
|
+
id: number;
|
|
267
|
+
acl: { readable: boolean; writable: boolean; reportable: boolean };
|
|
268
|
+
}[]>;
|
|
269
|
+
}
|
|
270
|
+
`);
|
|
271
|
+
|
|
272
|
+
// Generate individual cluster interfaces
|
|
273
|
+
for (const cluster of clusters) {
|
|
274
|
+
lines.push(generateClusterInterface(cluster));
|
|
275
|
+
lines.push('');
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Generate cluster registry type
|
|
279
|
+
lines.push('/** Type-safe cluster registry */');
|
|
280
|
+
lines.push('export interface ClusterRegistry {');
|
|
281
|
+
for (const cluster of clusters) {
|
|
282
|
+
const interfaceName = toInterfaceName(cluster.clusterName);
|
|
283
|
+
lines.push(` ${cluster.clusterName}?: ${interfaceName};`);
|
|
284
|
+
}
|
|
285
|
+
lines.push('}');
|
|
286
|
+
lines.push('');
|
|
287
|
+
|
|
288
|
+
// Generate endpoint type
|
|
289
|
+
lines.push(`export type ZCLNodeEndpoint = {
|
|
290
|
+
clusters: ClusterRegistry & {
|
|
291
|
+
[clusterName: string]: ZCLNodeCluster | undefined;
|
|
292
|
+
};
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
export interface ZCLNode {
|
|
296
|
+
endpoints: { [endpointId: number | string]: ZCLNodeEndpoint };
|
|
297
|
+
handleFrame: (
|
|
298
|
+
endpointId: number,
|
|
299
|
+
clusterId: number,
|
|
300
|
+
frame: Buffer,
|
|
301
|
+
meta?: unknown
|
|
302
|
+
) => Promise<void>;
|
|
303
|
+
}
|
|
304
|
+
`);
|
|
305
|
+
|
|
306
|
+
// Module declaration for CommonJS compatibility
|
|
307
|
+
lines.push(`declare module "zigbee-clusters" {
|
|
308
|
+
export const ZCLNode: {
|
|
309
|
+
new (options: ConstructorOptions): ZCLNode;
|
|
310
|
+
};
|
|
311
|
+
export const CLUSTER: {
|
|
312
|
+
[key: string]: { ID: number; NAME: string; ATTRIBUTES: unknown; COMMANDS: unknown };
|
|
313
|
+
};
|
|
314
|
+
export { ZCLNodeCluster };`);
|
|
315
|
+
|
|
316
|
+
// Export all cluster classes
|
|
317
|
+
for (const cluster of clusters) {
|
|
318
|
+
const interfaceName = toInterfaceName(cluster.clusterName);
|
|
319
|
+
lines.push(` export const ${interfaceName}: ${interfaceName};`);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
lines.push('}');
|
|
323
|
+
|
|
324
|
+
return lines.join('\n');
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Main entry point
|
|
329
|
+
*/
|
|
330
|
+
function main() {
|
|
331
|
+
console.log('Loading cluster modules...');
|
|
332
|
+
|
|
333
|
+
// Load all clusters via the index
|
|
334
|
+
const clustersModule = require('../lib/clusters');
|
|
335
|
+
const clusters = [];
|
|
336
|
+
|
|
337
|
+
// Get all exported cluster classes (end with 'Cluster')
|
|
338
|
+
for (const [name, value] of Object.entries(clustersModule)) {
|
|
339
|
+
if (name.endsWith('Cluster') && typeof value === 'function' && value.NAME) {
|
|
340
|
+
try {
|
|
341
|
+
const cluster = parseCluster(value);
|
|
342
|
+
clusters.push(cluster);
|
|
343
|
+
console.log(` ✓ ${cluster.clusterName} (${cluster.attributes.length} attrs, ${cluster.commands.length} cmds)`);
|
|
344
|
+
} catch (err) {
|
|
345
|
+
console.warn(` ✗ Failed to parse ${name}: ${err.message}`);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Sort clusters alphabetically
|
|
351
|
+
clusters.sort((a, b) => a.clusterName.localeCompare(b.clusterName));
|
|
352
|
+
|
|
353
|
+
console.log(`\nGenerating ${OUTPUT_FILE}...`);
|
|
354
|
+
const output = generateTypesFile(clusters);
|
|
355
|
+
fs.writeFileSync(OUTPUT_FILE, output);
|
|
356
|
+
|
|
357
|
+
console.log(`Done! Generated types for ${clusters.length} clusters.`);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
main();
|