isy-nodejs 1.0.1-alpha.11 → 1.0.1-alpha.16

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/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "isy-nodejs",
3
3
  "description": "Node.js wrapper for ISY interface including websockets for change notifications. Fork of isy-js by Rod Toll. Designed to be used in a node.js application.",
4
4
  "license": "MIT",
5
- "version": "1.0.1-alpha.11",
5
+ "version": "1.0.1-alpha.16",
6
6
  "author": {
7
7
  "name": "Pradeep Mouli"
8
8
  },
@@ -69,5 +69,5 @@
69
69
  "publishConfig": {
70
70
  "access": "public"
71
71
  },
72
- "gitHead": "7d11decc2649f39d8f68cd78ff6ab0fdb9d85a77"
72
+ "gitHead": "b4a88111ae80ba556773857491645f48f15f7f33"
73
73
  }
package/src/ISY.ts CHANGED
@@ -3,7 +3,6 @@ import { writeFile } from 'fs';
3
3
 
4
4
  import { Parser, type ParserOptions } from 'xml2js';
5
5
  import { parseBooleans, parseNumbers } from 'xml2js/lib/processors.js';
6
- import { XmlDocument } from 'xmldoc';
7
6
 
8
7
  import axios from 'axios';
9
8
  import { EventEmitter } from 'events';
@@ -43,8 +42,9 @@ import * as Utils from './Utils.js';
43
42
 
44
43
  import { X2jOptions, XMLParser } from 'fast-xml-parser';
45
44
  import { NodeFactory } from './Devices/NodeFactory.js';
45
+ import type { Config } from './Model/Config.js';
46
46
  import { findPackageJson } from './Utils.js';
47
-
47
+ import path from 'path';
48
48
 
49
49
  export {
50
50
  Category as Categories,
@@ -157,7 +157,7 @@ export class ISY extends EventEmitter implements Disposable {
157
157
 
158
158
  public static instance: ISY;
159
159
 
160
- public configInfo: any;
160
+ public configInfo: Config;
161
161
  public elkAlarmPanel: ELKAlarmPanelDevice;
162
162
  public guardianTimer: any;
163
163
  public id: string;
@@ -167,14 +167,12 @@ export class ISY extends EventEmitter implements Disposable {
167
167
  public nodesLoaded: boolean = false;
168
168
  public productId = 5226;
169
169
  public productName = 'eisy';
170
- public serverVersion: any;
170
+ public firmwareVersion: any;
171
171
  public vendorName = 'Universal Devices, Inc.';
172
172
  public webSocket: WebSocket.Client;
173
173
 
174
174
  public apiVersion: string;
175
175
 
176
-
177
-
178
176
  // #endregion Properties (30)
179
177
 
180
178
  // #region Constructors (1)
@@ -230,11 +228,7 @@ export class ISY extends EventEmitter implements Disposable {
230
228
  // #region Public Methods (24)
231
229
 
232
230
  [Symbol.dispose](): void {
233
- try {
234
- this.webSocket?.close();
235
- } catch (e) {
236
- this.logger.error(`Error closing websocket: ${e.message}`);
237
- }
231
+ this.close();
238
232
  }
239
233
 
240
234
  public override emit(event: 'InitializeCompleted' | 'NodeAdded' | 'NodeRemoved' | 'NodeChanged', node?: ISYNode<any, any, any, any>): boolean {
@@ -292,7 +286,7 @@ export class ISY extends EventEmitter implements Disposable {
292
286
  }
293
287
 
294
288
  public async handleInitializeError(step: string, reason: any): Promise<any> {
295
- this.logger.error(`Error initializing ISY (${step}): ${JSON.stringify(reason)}`);
289
+ this.logger.error(`Error initializing ISY (${step}): ${Utils.logStringify(reason)}`);
296
290
  return Promise.reject(reason);
297
291
  }
298
292
 
@@ -340,7 +334,7 @@ export class ISY extends EventEmitter implements Disposable {
340
334
  }
341
335
  break;
342
336
  case EventType.Heartbeat:
343
- this.logger.debug(`Received ${EventType[Number(stringControl)]} Signal from ISY: ${JSON.stringify(evt)}`);
337
+ this.logger.debug(`Received ${EventType[Number(stringControl)]} Signal from ISY: ${Utils.logStringify(evt)}`);
344
338
  break;
345
339
 
346
340
  default:
@@ -354,13 +348,13 @@ export class ISY extends EventEmitter implements Disposable {
354
348
  this.logger.error(`Error handling event for ${impactedDevice.name}: ${e.message}`);
355
349
  }
356
350
  } else {
357
- this.logger.warn(`${EventType[stringControl]} Event for Unidentified Device: ${JSON.stringify(evt)}`);
351
+ this.logger.debug(`${EventType[stringControl]} Event for Unidentified Device: ${JSON.stringify(evt)}`);
358
352
  }
359
353
  } else {
360
354
  if (stringControl === EventType.NodeChanged) {
361
- this.logger.info(`Received Node Change Event: ${JSON.stringify(evt)}. These are currently unsupported.`);
355
+ this.logger.debug(`Received Node Change Event: ${JSON.stringify(evt)}. These are currently unsupported.`);
362
356
  }
363
- this.logger.debug(`${EventType[Number(stringControl)]} Event: ${JSON.stringify(evt)}`);
357
+ this.logger.debug(`${EventType[Number(stringControl)]} Event: ${Utils.logStringify(evt)}`);
364
358
  }
365
359
 
366
360
  break;
@@ -371,7 +365,7 @@ export class ISY extends EventEmitter implements Disposable {
371
365
  public async initialize(): Promise<any> {
372
366
  const that = this;
373
367
  try {
374
- this.apiVersion = (await findPackageJson()).content.version;
368
+ this.apiVersion = (await findPackageJson()).version;
375
369
  await this.loadConfig();
376
370
  await this.loadNodes();
377
371
  await this.loadVariables(VariableType.Integer);
@@ -435,19 +429,18 @@ export class ISY extends EventEmitter implements Disposable {
435
429
  public async loadConfig(): Promise<any> {
436
430
  try {
437
431
  this.logger.info('Loading ISY Config');
438
- const configuration = (await this.sendRequest('config')).configuration;
432
+ const configuration = (await this.sendRequest('config')).configuration as Config;
439
433
  if (this.isDebugEnabled) {
440
- writeFile(this.storagePath + '/ISYConfigDump.json', JSON.stringify(configuration), this.logger.error);
434
+ writeFile(path.resolve(this.storagePath, 'ISYConfigDump.json'), JSON.stringify(configuration), this.logger.error);
441
435
  }
442
436
 
443
437
  const controls = configuration.controls;
444
438
  this.model = configuration.deviceSpecs.model;
445
- this.serverVersion = configuration.app_version;
439
+ this.firmwareVersion = configuration.app_full_version;
446
440
  this.vendorName = configuration.deviceSpecs.make;
447
441
  this.productId = configuration.product.id;
448
442
  this.productName = configuration.product.desc;
449
443
  this.id = configuration.root.id;
450
- // TODO: Check Installed Features
451
444
  // this.logger.info(result.configuration);
452
445
  if (controls !== undefined) {
453
446
  // this.logger.info(controls.control);
@@ -496,7 +489,9 @@ export class ISY extends EventEmitter implements Disposable {
496
489
  }
497
490
  }
498
491
 
499
- public override on(event: 'InitializeCompleted' | 'NodeAdded' | 'NodeRemoved' | 'NodeChanged', listener: (node?: ISYNode<any, any, any, any>) => void): this {
492
+ public override on(event: 'initializeCompleted', listener: () => void): this;
493
+ public override on(event: 'nodeAdded' | 'nodeRemoved' | 'nodeChanged', listener: (node?: ISYNode<any, any, any, any>) => void): this;
494
+ override on(event: 'initializeCompleted' | 'nodeAdded' | 'nodeRemoved' | 'nodeChanged', listener: (node?: ISYNode<any, any, any, any>) => void): this {
500
495
  return super.on(event, listener);
501
496
  }
502
497
 
@@ -635,7 +630,11 @@ export class ISY extends EventEmitter implements Disposable {
635
630
  }
636
631
 
637
632
  public close() {
638
- if (this.webSocket) this.webSocket.close();
633
+ try {
634
+ this.webSocket?.close();
635
+ } catch (e) {
636
+ this.logger.error(`Error closing websocket: ${e.message}`);
637
+ }
639
638
  }
640
639
 
641
640
  // #endregion Public Methods (24)
@@ -712,17 +711,17 @@ export class ISY extends EventEmitter implements Disposable {
712
711
  });
713
712
  }
714
713
 
715
- #loadElkNodes(result: any) {
714
+ /* #loadElkNodes(result: any) {
716
715
  const document = new XmlDocument(result);
717
716
  const nodes = document.childNamed('areas').childNamed('area').childrenNamed('zone');
718
717
  for (let index = 0; index < nodes.length; index++) {
719
718
  const id = nodes[index].attr.id;
720
719
  const name = nodes[index].attr.name;
721
720
  const alarmDef = nodes[index].attr.alarmDef;
722
- const newDevice = new ElkAlarmSensorDevice(this, name, 1, id /*TODO: Handle CO Sensor vs. Door/Window Sensor */);
721
+ const newDevice = new ElkAlarmSensorDevice(this, name, 1, id TODO: Handle CO Sensor vs. Door/Window Sensor );
723
722
  this.zoneMap[newDevice.zone] = newDevice;
724
723
  }
725
- }
724
+ } */
726
725
 
727
726
  async #readDeviceNodes(obj: { nodes: { node: NodeInfo[] } }) {
728
727
  this.logger.info('Loading Device Nodes');
@@ -14,15 +14,16 @@ import { StorageService } from '@project-chip/matter.js/environment';
14
14
  import { Level, levelFromString, Logger as MatterLogger } from '@project-chip/matter.js/log';
15
15
  import { ServerNode } from '@project-chip/matter.js/node';
16
16
  import { QrCode } from '@project-chip/matter.js/schema';
17
- import { resolve } from 'path';
17
+ import path, { resolve } from 'path';
18
+ import type { ISYDeviceNode } from '../../Devices/ISYDeviceNode.js';
18
19
  import { InsteonDimmableDevice, InsteonKeypadButtonDevice, InsteonRelayDevice, ISY } from '../../ISY.js';
19
20
  import { ISYBridgedDeviceBehavior } from '../Behaviors/ISYBridgedDeviceBehavior.js';
20
21
  import { ISYDimmableBehavior, ISYOnOffBehavior } from '../Behaviors/ISYOnOffBehavior.js';
21
22
  import '../Mappings/Insteon.js';
22
- import type { ISYDeviceNode } from '../../Devices/ISYDeviceNode.js';
23
23
 
24
24
  // #region Interfaces (1)
25
25
 
26
+ export let instance: ServerNode;
26
27
  export interface Config {
27
28
  // #region Properties (8)
28
29
 
@@ -55,12 +56,21 @@ export async function createMatterServer(isy?: ISY, config?: Config): Promise<Se
55
56
  try {
56
57
  MatterLogger.addLogger(
57
58
  'polyLogger',
58
- (level, message) => logger.log(Level[level].toLowerCase().replace('notice', 'info'), message.slice(23).remove(Level[level]).trimStart()) /*Preserve existing formatting, but trim off date*/,
59
+ (lvl, message) => {
60
+ let msg = message.slice(23).remove(Level[lvl]).trimStart();
61
+ let level = Level[lvl].toLowerCase().replace('notice', 'info');
62
+ if (msg.startsWith('EndpointStructureLogger')) {
63
+ if (lvl === Level.INFO) level = 'debug';
64
+ }
65
+ logger.log(level, msg);
66
+ },
67
+ /*Preserve existing formatting, but trim off date*/
59
68
  {
60
69
  defaultLogLevel: levelFromString(logger.level),
61
70
  logFormat: 'plain'
62
71
  }
63
72
  );
73
+
64
74
  } finally {
65
75
  MatterLogger.defaultLogLevel = levelFromString(logger.level);
66
76
  }
@@ -68,8 +78,8 @@ export async function createMatterServer(isy?: ISY, config?: Config): Promise<Se
68
78
  config = await initializeConfiguration(isy, config);
69
79
 
70
80
  logger.info(`Matter config read: ${JSON.stringify(config)}`);
71
-
72
- const server = await ServerNode.create({
81
+ MatterLogger.removeLogger('default');
82
+ let server = await ServerNode.create({
73
83
  // Required: Give the Node a unique ID which is used to store the state of this node
74
84
  id: config.uniqueId,
75
85
 
@@ -216,12 +226,12 @@ export async function createMatterServer(isy?: ISY, config?: Config): Promise<Se
216
226
  // logEndpoint(EndpointServer.forEndpoint(server), {logAttributePrimitiveValues: true, logAttributeObjectValues: true});
217
227
  //else if(logger.isDebugEnabled())
218
228
  // {
219
- logEndpoint(EndpointServer.forEndpoint(server), { logAttributePrimitiveValues: true, logAttributeObjectValues: false });
229
+ logEndpoint(EndpointServer.forEndpoint(server), { logAttributePrimitiveValues: true, logAttributeObjectValues: false, logClusterGlobalAttributes: false });
220
230
  // }
221
231
  if (server.lifecycle.isOnline) {
222
232
  const { qrPairingCode, manualPairingCode } = server.state.commissioning.pairingCodes;
223
233
 
224
- logger.info(QrCode.get(qrPairingCode));
234
+ logger.info('/n' + QrCode.get(qrPairingCode));
225
235
  logger.info(`QR Code URL: https://project-chip.github.io/connectedhomeip/qrcode.html?data=${qrPairingCode}`);
226
236
  logger.info(`Manual pairing code: ${manualPairingCode}`);
227
237
  }
@@ -229,22 +239,36 @@ export async function createMatterServer(isy?: ISY, config?: Config): Promise<Se
229
239
  {
230
240
  e[1].initialize(e[0] as any);
231
241
  }*/
232
-
242
+ instance = server;
233
243
  return server;
234
244
  }
235
245
 
246
+ type PairingCodeData = {
247
+ qrPairingCode: string;
248
+ manualPairingCode: string;
249
+ renderedQrPairingCode: string;
250
+ url: string | URL;
251
+ };
252
+
253
+ export function getPairingCode(server: ServerNode = instance): PairingCodeData {
254
+ let codes = server.state.commissioning.pairingCodes as PairingCodeData;
255
+ codes.renderedQrPairingCode = QrCode.get(codes.qrPairingCode);
256
+ codes.url = `https://project-chip.github.io/connectedhomeip/qrcode.html?data=${codes.qrPairingCode}`;
257
+ return codes;
258
+ }
259
+
236
260
  async function initializeConfiguration(isy: ISY, config?: Config): Promise<Config> {
237
261
  var logger = isy.logger;
238
262
 
239
263
  const environment = NodeJsEnvironment();
240
264
 
241
265
  const storageService = environment.get(StorageService);
242
- environment.vars.set('storage.path', `${ISY.instance.storagePath}matter`);
266
+ environment.vars.set('storage.path', path.resolve(isy.storagePath, 'matter'));
243
267
  environment.vars.use(() => {
244
268
  const location = environment.vars.get('storage.path', environment.vars.get('path.root', '.'));
245
269
  storageService.location = location;
246
270
  });
247
- storageService.factory = (namespace) => new StorageBackendDisk(resolve(storageService.location ?? '.', namespace), environment.vars.get('storage.clear', false));
271
+ storageService.factory = (namespace) => new StorageBackendDisk(resolve(storageService.location ?? '.', namespace), environment.vars.get('storage.clear', false));
248
272
  logger.info(`Matter storage location: ${storageService.location} (Directory)`);
249
273
 
250
274
  const deviceStorage = (await storageService.open('device')).createContext('data');
package/src/Utils.ts CHANGED
@@ -8,7 +8,7 @@ import { EventEmitter as BaseEventEmitter } from 'events';
8
8
  import { Category } from './Definitions/Global/Categories.js';
9
9
 
10
10
  //import { get } from 'http';
11
- import PackageJson from '@npmcli/package-json';
11
+ import type { PackageJson } from '@npmcli/package-json';
12
12
  import type { Identity } from '@project-chip/matter.js/util';
13
13
  import { existsSync } from 'fs';
14
14
  import path from 'path';
@@ -277,12 +277,44 @@ function getImportMeta() {
277
277
  }
278
278
  }
279
279
  export async function findPackageJson(currentPath: string = getImportMeta()?.dirname): Promise<PackageJson> {
280
- while (currentPath !== '/') {
281
- const packageJsonPath = path.join(currentPath, 'package.json');
282
- if (existsSync(packageJsonPath)) {
283
- return await PackageJson.load(currentPath);
280
+ try {
281
+ while (currentPath !== '/') {
282
+ const packageJsonPath = path.join(currentPath, 'package.json');
283
+ if (existsSync(packageJsonPath)) {
284
+ return await import(packageJsonPath);
285
+ }
286
+ currentPath = path.join(currentPath, '..');
284
287
  }
285
- currentPath = path.join(currentPath, '..');
286
- }
288
+ } catch {}
287
289
  return null;
288
290
  }
291
+
292
+ export function logStringify(obj: any, indent = 2) {
293
+ let cache = [];
294
+ const retVal = JSON.stringify(
295
+ obj,
296
+ (key, value) => {
297
+ if (typeof value === 'object' && value !== null) {
298
+ if (cache.includes(value)) {
299
+ // Circular reference found, discard key
300
+ return;
301
+ }
302
+ // Store value in our collection
303
+ cache.push(value);
304
+ }
305
+ if (value instanceof Map) {
306
+ return [...value];
307
+ }
308
+ if (value instanceof Set) {
309
+ return [...value];
310
+ }
311
+ if (key.toLowerCase().includes('password')) {
312
+ return '********';
313
+ }
314
+ return value;
315
+ },
316
+ indent
317
+ );
318
+ cache = null;
319
+ return retVal;
320
+ }