zigbee-clusters 2.7.2 → 2.8.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/lib/Cluster.js CHANGED
@@ -403,29 +403,39 @@ class Cluster extends EventEmitter {
403
403
  /**
404
404
  * Command which reads a given set of attributes from the remote cluster.
405
405
  * Note: do not mix regular and manufacturer specific attributes.
406
- * @param {string[]} attributeNames
406
+ * @param {Array<string | number>} attributes - Attribute names or numeric IDs
407
407
  * @param {object} [opts=] - Optional parameters
408
- * @param {number} [opts.timeout] - Optional timeout in milliseconds for waiting for response
409
- * @returns {Promise<Object.<string, unknown>>} - Object with values (e.g. `{ onOff: true }`)
408
+ * @param {number} [opts.timeout] - Optional timeout in ms
409
+ * @returns {Promise<Object.<string, unknown>>} - Keyed by attribute name or
410
+ * numeric ID
410
411
  */
411
- async readAttributes(attributeNames, opts) {
412
- if (attributeNames instanceof Array === false) {
413
- throw new Error('Expected attribute names array, as of zigbee-clusters@2.0.0 call readAttributes([\'myAttr\'])');
412
+ async readAttributes(attributes, opts) {
413
+ if (attributes instanceof Array === false) {
414
+ throw new Error('Expected attributes array, as of zigbee-clusters@2.0.0 call readAttributes([\'myAttr\'])');
414
415
  }
415
416
 
416
- if (!attributeNames.length) {
417
- attributeNames = Object.keys(this.constructor.attributes);
418
- }
419
- const mismatch = attributeNames.find(n => !this.constructor.attributes[n]);
420
- if (mismatch) {
421
- throw new TypeError(`${mismatch} is not a valid attribute of ${this.name}`);
417
+ if (!attributes.length) {
418
+ attributes = Object.keys(this.constructor.attributes);
422
419
  }
423
420
 
424
421
  const idToName = {};
425
- const attrIds = new Set(attributeNames.map(a => {
426
- idToName[this.constructor.attributes[a].id] = a;
427
- return this.constructor.attributes[a].id;
428
- }));
422
+ const numericIds = new Set();
423
+ const attrIds = new Set();
424
+ for (const attr of attributes) {
425
+ if (typeof attr === 'number') {
426
+ if (!Number.isInteger(attr) || attr < 0 || attr > 0xFFFF) {
427
+ throw new TypeError(`${attr} is not a valid numeric attribute ID for ${this.name}, must be an integer between 0 and 0xFFFF`);
428
+ }
429
+ numericIds.add(attr);
430
+ if (idToName[attr] === undefined) idToName[attr] = attr;
431
+ attrIds.add(attr);
432
+ } else if (this.constructor.attributes[attr]) {
433
+ idToName[this.constructor.attributes[attr].id] = attr;
434
+ attrIds.add(this.constructor.attributes[attr].id);
435
+ } else {
436
+ throw new TypeError(`${attr} is not a valid attribute of ${this.name}`);
437
+ }
438
+ }
429
439
 
430
440
  const resultObj = {};
431
441
  while (attrIds.size) {
@@ -433,19 +443,23 @@ class Cluster extends EventEmitter {
433
443
  const manufacturerId = this._checkForManufacturerSpecificAttributes(Array.from(attrIds));
434
444
  debug(this.logId, 'read attributes', [...attrIds], manufacturerId ? `manufacturer specific id ${manufacturerId}` : '');
435
445
 
436
- const { attributes } = await super.readAttributes({
446
+ const response = await super.readAttributes({
437
447
  attributes: [...attrIds],
438
448
  manufacturerId,
439
449
  }, opts);
440
450
 
441
- debug(this.logId, 'read attributes result', { attributes });
442
- const result = this.constructor.attributeArrayStatusDataType.fromBuffer(attributes, 0);
451
+ debug(this.logId, 'read attributes result', response);
452
+ const { attributeArrayStatusDataType } = this.constructor;
453
+ const result = attributeArrayStatusDataType.fromBuffer(response.attributes, 0);
443
454
  if (!result.length) break;
444
455
 
445
456
  result.forEach(a => {
446
457
  attrIds.delete(a.id);
447
458
  if (a.status === 'SUCCESS') {
448
459
  resultObj[idToName[a.id]] = a.value;
460
+ if (numericIds.has(a.id) && idToName[a.id] !== a.id) {
461
+ resultObj[a.id] = a.value;
462
+ }
449
463
  }
450
464
  });
451
465
  }
@@ -690,8 +704,13 @@ class Cluster extends EventEmitter {
690
704
  for (const attr of attributes) {
691
705
  const attribute = this.constructor.attributesById[attr.id];
692
706
  const discoveredAttribute = {
693
- acl: attr.acl,
707
+ acl: {
708
+ readable: !!attr.acl.readable,
709
+ writable: !!attr.acl.writable,
710
+ reportable: !!attr.acl.reportable,
711
+ },
694
712
  id: attr.id,
713
+ dataTypeId: attr.dataTypeId,
695
714
  };
696
715
 
697
716
  // If the attribute is implemented in zigbee-clusters add name
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zigbee-clusters",
3
- "version": "2.7.2",
3
+ "version": "2.8.1",
4
4
  "description": "Zigbee Cluster Library for Node.js",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -89,7 +89,7 @@ function zclTypeToTS(dataType) {
89
89
  * @param {Function} ClusterClass - Cluster class with static ATTRIBUTES/COMMANDS
90
90
  * @returns {object} Cluster definition
91
91
  */
92
- function parseCluster(ClusterClass) {
92
+ function parseCluster(ClusterClass, exportName) {
93
93
  const clusterName = ClusterClass.NAME;
94
94
  const clusterId = ClusterClass.ID;
95
95
  const attributes = [];
@@ -132,7 +132,11 @@ function parseCluster(ClusterClass) {
132
132
  }
133
133
 
134
134
  return {
135
- clusterName, clusterId, attributes, commands,
135
+ exportName,
136
+ clusterName,
137
+ clusterId,
138
+ attributes,
139
+ commands,
136
140
  };
137
141
  }
138
142
 
@@ -141,8 +145,9 @@ function parseCluster(ClusterClass) {
141
145
  * @param {string} clusterName
142
146
  * @returns {string}
143
147
  */
144
- function toInterfaceName(clusterName) {
145
- const name = clusterName.charAt(0).toUpperCase() + clusterName.slice(1);
148
+ function toInterfaceName(cluster) {
149
+ if (cluster.exportName) return cluster.exportName;
150
+ const name = cluster.clusterName.charAt(0).toUpperCase() + cluster.clusterName.slice(1);
146
151
  return `${name}Cluster`;
147
152
  }
148
153
 
@@ -152,7 +157,7 @@ function toInterfaceName(clusterName) {
152
157
  * @returns {string} TypeScript interface code
153
158
  */
154
159
  function generateClusterInterface(cluster) {
155
- const interfaceName = toInterfaceName(cluster.clusterName);
160
+ const interfaceName = toInterfaceName(cluster);
156
161
  const lines = [];
157
162
 
158
163
  // Generate attributes interface
@@ -172,7 +177,8 @@ function generateClusterInterface(cluster) {
172
177
  if (cluster.attributes.length > 0) {
173
178
  const attrNames = cluster.attributes.map(a => `'${a.name}'`).join(' | ');
174
179
  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>;`);
180
+ lines.push(` readAttributes(attributeNames: Array<keyof ${interfaceName}Attributes | number>, opts?: { timeout?: number }): Promise<Partial<${interfaceName}Attributes> & Record<number, unknown>>;`);
181
+ lines.push(` writeAttributes(attributes: Partial<${interfaceName}Attributes>, opts?: { timeout?: number }): Promise<unknown>;`);
176
182
  }
177
183
 
178
184
  // Add command methods
@@ -188,9 +194,9 @@ function generateClusterInterface(cluster) {
188
194
  const allArgsOptional = cmd.args.every(a => a.tsType === 'Buffer');
189
195
  const argsType = `{ ${cmd.args.map(a => `${a.name}${a.tsType === 'Buffer' ? '?' : ''}: ${a.tsType}`).join('; ')} }`;
190
196
  // If all args are optional, make the entire args object optional
191
- lines.push(` ${cmd.name}(args${allArgsOptional ? '?' : ''}: ${argsType}): Promise<${returnType}>;`);
197
+ lines.push(` ${cmd.name}(args${allArgsOptional ? '?' : ''}: ${argsType}, opts?: ClusterCommandOptions): Promise<${returnType}>;`);
192
198
  } else {
193
- lines.push(` ${cmd.name}(): Promise<${returnType}>;`);
199
+ lines.push(` ${cmd.name}(opts?: ClusterCommandOptions): Promise<${returnType}>;`);
194
200
  }
195
201
  }
196
202
 
@@ -225,30 +231,47 @@ type ConstructorOptions = {
225
231
  endpointDescriptors: EndpointDescriptor[];
226
232
  sendFrame: (endpointId: number, clusterId: number, frame: Buffer) => Promise<void>;
227
233
  };
234
+
235
+ type ClusterCommandOptions = {
236
+ timeout?: number;
237
+ waitForResponse?: boolean;
238
+ disableDefaultResponse?: boolean;
239
+ };
240
+
241
+ type ZCLNodeConstructorInput = {
242
+ endpointDescriptors?: EndpointDescriptor[];
243
+ sendFrame: (endpointId: number, clusterId: number, frame: Buffer) => Promise<void>;
244
+ handleFrame?: (
245
+ endpointId: number,
246
+ clusterId: number,
247
+ frame: Buffer,
248
+ meta?: unknown
249
+ ) => Promise<void>;
250
+ };
228
251
  `);
229
252
 
230
253
  // Base ZCLNodeCluster interface
231
254
  lines.push(`export interface ZCLNodeCluster extends EventEmitter {
232
- discoverCommandsGenerated(opts?: {
255
+ discoverCommandsGenerated(params?: {
233
256
  startValue?: number;
234
257
  maxResults?: number;
235
- }): Promise<number[]>;
258
+ }, opts?: { timeout?: number }): Promise<(string | number)[]>;
236
259
 
237
- discoverCommandsReceived(opts?: {
260
+ discoverCommandsReceived(params?: {
238
261
  startValue?: number;
239
262
  maxResults?: number;
240
- }): Promise<number[]>;
263
+ }, opts?: { timeout?: number }): Promise<(string | number)[]>;
241
264
 
242
265
  readAttributes(
243
- attributeNames: string[],
266
+ attributes: Array<string | number>,
244
267
  opts?: { timeout?: number }
245
268
  ): Promise<{ [x: string]: unknown }>;
246
269
 
247
- writeAttributes(attributes?: object): Promise<void>;
270
+ writeAttributes(attributes?: object, opts?: { timeout?: number }): Promise<unknown>;
248
271
 
249
- configureReporting(attributes?: object): Promise<void>;
272
+ configureReporting(attributes?: object, opts?: { timeout?: number }): Promise<void>;
250
273
 
251
- readReportingConfiguration(attributes?: (string | number)[]): Promise<{
274
+ readReportingConfiguration(attributes?: (string | number)[], opts?: { timeout?: number }): Promise<{
252
275
  status: string;
253
276
  direction: 'reported' | 'received';
254
277
  attributeId: number;
@@ -259,11 +282,12 @@ type ConstructorOptions = {
259
282
  timeoutPeriod?: number;
260
283
  }[]>;
261
284
 
262
- discoverAttributes(): Promise<(string | number)[]>;
285
+ discoverAttributes(opts?: { timeout?: number }): Promise<(string | number)[]>;
263
286
 
264
- discoverAttributesExtended(): Promise<{
287
+ discoverAttributesExtended(opts?: { timeout?: number }): Promise<{
265
288
  name?: string;
266
289
  id: number;
290
+ dataTypeId: number;
267
291
  acl: { readable: boolean; writable: boolean; reportable: boolean };
268
292
  }[]>;
269
293
  }
@@ -279,7 +303,7 @@ type ConstructorOptions = {
279
303
  lines.push('/** Type-safe cluster registry */');
280
304
  lines.push('export interface ClusterRegistry {');
281
305
  for (const cluster of clusters) {
282
- const interfaceName = toInterfaceName(cluster.clusterName);
306
+ const interfaceName = toInterfaceName(cluster);
283
307
  lines.push(` ${cluster.clusterName}?: ${interfaceName};`);
284
308
  }
285
309
  lines.push('}');
@@ -306,7 +330,7 @@ export interface ZCLNode {
306
330
  // Module declaration for CommonJS compatibility
307
331
  lines.push(`declare module "zigbee-clusters" {
308
332
  export const ZCLNode: {
309
- new (options: ConstructorOptions): ZCLNode;
333
+ new (node: ZCLNodeConstructorInput): ZCLNode;
310
334
  };
311
335
  export const CLUSTER: {
312
336
  [key: string]: { ID: number; NAME: string; ATTRIBUTES: unknown; COMMANDS: unknown };
@@ -315,8 +339,15 @@ export interface ZCLNode {
315
339
 
316
340
  // Export all cluster classes
317
341
  for (const cluster of clusters) {
318
- const interfaceName = toInterfaceName(cluster.clusterName);
319
- lines.push(` export const ${interfaceName}: ${interfaceName};`);
342
+ const interfaceName = toInterfaceName(cluster);
343
+ const exportName = cluster.exportName || interfaceName;
344
+ lines.push(` export const ${exportName}: {`);
345
+ lines.push(` new (...args: any[]): ${interfaceName};`);
346
+ lines.push(' ID: number;');
347
+ lines.push(' NAME: string;');
348
+ lines.push(' ATTRIBUTES: unknown;');
349
+ lines.push(' COMMANDS: unknown;');
350
+ lines.push(' };');
320
351
  }
321
352
 
322
353
  lines.push('}');
@@ -338,7 +369,7 @@ function main() {
338
369
  for (const [name, value] of Object.entries(clustersModule)) {
339
370
  if (name.endsWith('Cluster') && typeof value === 'function' && value.NAME) {
340
371
  try {
341
- const cluster = parseCluster(value);
372
+ const cluster = parseCluster(value, name);
342
373
  clusters.push(cluster);
343
374
  console.log(` ✓ ${cluster.clusterName} (${cluster.attributes.length} attrs, ${cluster.commands.length} cmds)`);
344
375
  } catch (err) {