isy-nodejs 1.0.1-alpha.1 → 1.0.1-alpha.3

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.
Files changed (107) hide show
  1. package/dist/cjs/ISY.js +113 -110
  2. package/dist/cjs/ISY.js.map +1 -1
  3. package/dist/cjs/ISYNode.js +22 -6
  4. package/dist/cjs/ISYNode.js.map +1 -1
  5. package/dist/cjs/types/ISY.d.ts +37 -37
  6. package/dist/cjs/types/ISY.d.ts.map +1 -1
  7. package/dist/cjs/types/ISYNode.d.ts.map +1 -1
  8. package/dist/esm/ISY.js +152 -149
  9. package/dist/esm/ISY.js.map +1 -1
  10. package/dist/esm/ISYNode.js +22 -6
  11. package/dist/esm/ISYNode.js.map +1 -1
  12. package/dist/esm/types/ISY.d.ts +37 -37
  13. package/dist/esm/types/ISY.d.ts.map +1 -1
  14. package/dist/esm/types/ISYNode.d.ts.map +1 -1
  15. package/dist/tsconfig.cjs.tsbuildinfo +1 -1
  16. package/dist/tsconfig.esm.tsbuildinfo +1 -1
  17. package/package.json +3 -5
  18. package/src/ISY.ts +790 -820
  19. package/src/ISYNode.ts +20 -7
  20. package/dist/esm/Devices/Brultech/index.js +0 -4
  21. package/dist/esm/Devices/Brultech/index.js.map +0 -1
  22. package/dist/esm/Devices/Constructor.js +0 -2
  23. package/dist/esm/Devices/Constructor.js.map +0 -1
  24. package/dist/esm/Devices/DeviceFactory.js +0 -13
  25. package/dist/esm/Devices/DeviceFactory.js.map +0 -1
  26. package/dist/esm/Devices/DeviceMap.js +0 -4
  27. package/dist/esm/Devices/DeviceMap.js.map +0 -1
  28. package/dist/esm/Devices/Elk/ElkAlarmPanelDevice.js +0 -181
  29. package/dist/esm/Devices/Elk/ElkAlarmPanelDevice.js.map +0 -1
  30. package/dist/esm/Devices/Elk/ElkAlarmSensorDevice.js +0 -107
  31. package/dist/esm/Devices/Elk/ElkAlarmSensorDevice.js.map +0 -1
  32. package/dist/esm/Devices/EndpointFor.js +0 -31
  33. package/dist/esm/Devices/EndpointFor.js.map +0 -1
  34. package/dist/esm/Devices/ISYDevice.js +0 -35
  35. package/dist/esm/Devices/ISYDevice.js.map +0 -1
  36. package/dist/esm/Devices/ISYDeviceNode.js +0 -62
  37. package/dist/esm/Devices/ISYDeviceNode.js.map +0 -1
  38. package/dist/esm/Devices/ISYMultiNodeDevice.js +0 -89
  39. package/dist/esm/Devices/ISYMultiNodeDevice.js.map +0 -1
  40. package/dist/esm/Devices/Insteon/InsteonBallastDimmerDevice.js +0 -8
  41. package/dist/esm/Devices/Insteon/InsteonBallastDimmerDevice.js.map +0 -1
  42. package/dist/esm/Devices/Insteon/InsteonBaseDevice.js +0 -47
  43. package/dist/esm/Devices/Insteon/InsteonBaseDevice.js.map +0 -1
  44. package/dist/esm/Devices/Insteon/InsteonCOSensorDevice.js +0 -14
  45. package/dist/esm/Devices/Insteon/InsteonCOSensorDevice.js.map +0 -1
  46. package/dist/esm/Devices/Insteon/InsteonDevice.js +0 -17
  47. package/dist/esm/Devices/Insteon/InsteonDevice.js.map +0 -1
  48. package/dist/esm/Devices/Insteon/InsteonDeviceFactory.js +0 -875
  49. package/dist/esm/Devices/Insteon/InsteonDeviceFactory.js.map +0 -1
  50. package/dist/esm/Devices/Insteon/InsteonDimmableDevice.js +0 -32
  51. package/dist/esm/Devices/Insteon/InsteonDimmableDevice.js.map +0 -1
  52. package/dist/esm/Devices/Insteon/InsteonDimmerOutletDevice.js +0 -7
  53. package/dist/esm/Devices/Insteon/InsteonDimmerOutletDevice.js.map +0 -1
  54. package/dist/esm/Devices/Insteon/InsteonDimmerSwitchDevice.js +0 -8
  55. package/dist/esm/Devices/Insteon/InsteonDimmerSwitchDevice.js.map +0 -1
  56. package/dist/esm/Devices/Insteon/InsteonDoorWindowSensorDevice.js +0 -11
  57. package/dist/esm/Devices/Insteon/InsteonDoorWindowSensorDevice.js.map +0 -1
  58. package/dist/esm/Devices/Insteon/InsteonFanDevice.js +0 -56
  59. package/dist/esm/Devices/Insteon/InsteonFanDevice.js.map +0 -1
  60. package/dist/esm/Devices/Insteon/InsteonKeypadDevice.js +0 -8
  61. package/dist/esm/Devices/Insteon/InsteonKeypadDevice.js.map +0 -1
  62. package/dist/esm/Devices/Insteon/InsteonKeypadDimmerDevice.js +0 -8
  63. package/dist/esm/Devices/Insteon/InsteonKeypadDimmerDevice.js.map +0 -1
  64. package/dist/esm/Devices/Insteon/InsteonKeypadRelayDevice.js +0 -8
  65. package/dist/esm/Devices/Insteon/InsteonKeypadRelayDevice.js.map +0 -1
  66. package/dist/esm/Devices/Insteon/InsteonLeakSensorDevice.js +0 -11
  67. package/dist/esm/Devices/Insteon/InsteonLeakSensorDevice.js.map +0 -1
  68. package/dist/esm/Devices/Insteon/InsteonLockDevice.js +0 -32
  69. package/dist/esm/Devices/Insteon/InsteonLockDevice.js.map +0 -1
  70. package/dist/esm/Devices/Insteon/InsteonMotionSensorDevice.js +0 -36
  71. package/dist/esm/Devices/Insteon/InsteonMotionSensorDevice.js.map +0 -1
  72. package/dist/esm/Devices/Insteon/InsteonOnOffOutletDevice.js +0 -19
  73. package/dist/esm/Devices/Insteon/InsteonOnOffOutletDevice.js.map +0 -1
  74. package/dist/esm/Devices/Insteon/InsteonRelayDevice.js +0 -46
  75. package/dist/esm/Devices/Insteon/InsteonRelayDevice.js.map +0 -1
  76. package/dist/esm/Devices/Insteon/InsteonRelaySwitchDevice.js +0 -8
  77. package/dist/esm/Devices/Insteon/InsteonRelaySwitchDevice.js.map +0 -1
  78. package/dist/esm/Devices/Insteon/InsteonSmokeSensorDevice.js +0 -11
  79. package/dist/esm/Devices/Insteon/InsteonSmokeSensorDevice.js.map +0 -1
  80. package/dist/esm/Devices/Insteon/InsteonThermostatDevice.js +0 -30
  81. package/dist/esm/Devices/Insteon/InsteonThermostatDevice.js.map +0 -1
  82. package/dist/esm/Devices/Insteon/index.js +0 -31
  83. package/dist/esm/Devices/Insteon/index.js.map +0 -1
  84. package/dist/esm/Devices/Lutron/index.js +0 -4
  85. package/dist/esm/Devices/Lutron/index.js.map +0 -1
  86. package/dist/esm/Devices/MapsTo.js +0 -2
  87. package/dist/esm/Devices/MapsTo.js.map +0 -1
  88. package/dist/esm/Devices/NCD/index.js +0 -4
  89. package/dist/esm/Devices/NCD/index.js.map +0 -1
  90. package/dist/esm/Devices/NodeFactory.js +0 -48
  91. package/dist/esm/Devices/NodeFactory.js.map +0 -1
  92. package/dist/esm/Devices/Poly/index.js +0 -4
  93. package/dist/esm/Devices/Poly/index.js.map +0 -1
  94. package/dist/esm/Devices/Scene/index.js +0 -4
  95. package/dist/esm/Devices/Scene/index.js.map +0 -1
  96. package/dist/esm/Devices/UDI/index.js +0 -4
  97. package/dist/esm/Devices/UDI/index.js.map +0 -1
  98. package/dist/esm/Devices/ZWave/ZWaveBaseDevice.js +0 -38
  99. package/dist/esm/Devices/ZWave/ZWaveBaseDevice.js.map +0 -1
  100. package/dist/esm/Devices/ZWave/ZWaveDimmerSwitchDevice.js +0 -8
  101. package/dist/esm/Devices/ZWave/ZWaveDimmerSwitchDevice.js.map +0 -1
  102. package/dist/esm/Devices/ZWave/ZWaveLockDevice.js +0 -35
  103. package/dist/esm/Devices/ZWave/ZWaveLockDevice.js.map +0 -1
  104. package/dist/esm/Devices/ZWave/index.js +0 -20
  105. package/dist/esm/Devices/ZWave/index.js.map +0 -1
  106. package/dist/esm/Devices/index.js +0 -9
  107. package/dist/esm/Devices/index.js.map +0 -1
package/src/ISY.ts CHANGED
@@ -1,116 +1,115 @@
1
- import WebSocket from "faye-websocket";
2
- import { writeFile } from "fs";
3
-
4
- import { Parser, type ParserOptions } from "xml2js";
5
- import { parseBooleans, parseNumbers } from "xml2js/lib/processors.js";
6
- import { XmlDocument } from "xmldoc";
7
-
8
- import axios from "axios";
9
- import { EventEmitter } from "events";
10
- import winston, { format, Logger, loggers, type LeveledLogMethod } from "winston";
11
- import { Category } from "./Definitions/Global/Categories.js";
12
- import { Family } from "./Definitions/Global/Families.js";
13
- import type { NodeInfo } from "./Model/NodeInfo.js";
14
- import { DeviceFactory } from "./Devices/DeviceFactory.js";
15
- import { ELKAlarmPanelDevice } from "./Devices/Elk/ElkAlarmPanelDevice.js";
16
- import { ElkAlarmSensorDevice } from "./Devices/Elk/ElkAlarmSensorDevice.js";
17
- import { InsteonBaseDevice } from "./Devices/Insteon/InsteonBaseDevice.js";
18
- import { InsteonOutletDevice } from "./Devices/Insteon/InsteonDevice.js";
19
- import { InsteonDimmableDevice } from "./Devices/Insteon/InsteonDimmableDevice.js";
20
- import { InsteonDimmerOutletDevice } from "./Devices/Insteon/InsteonDimmerOutletDevice.js";
21
- import { InsteonDimmerSwitchDevice } from "./Devices/Insteon/InsteonDimmerSwitchDevice.js";
22
- import { InsteonDoorWindowSensorDevice } from "./Devices/Insteon/InsteonDoorWindowSensorDevice.js";
23
- import { InsteonFanDevice, InsteonFanMotorDevice } from "./Devices/Insteon/InsteonFanDevice.js";
24
- import { InsteonKeypadButtonDevice } from "./Devices/Insteon/InsteonKeypadDevice.js";
25
- import { InsteonKeypadDimmerDevice } from "./Devices/Insteon/InsteonKeypadDimmerDevice.js";
26
- import { InsteonKeypadRelayDevice } from "./Devices/Insteon/InsteonKeypadRelayDevice.js";
27
- import { InsteonLeakSensorDevice } from "./Devices/Insteon/InsteonLeakSensorDevice.js";
28
- import { InsteonLockDevice } from "./Devices/Insteon/InsteonLockDevice.js";
29
- import { InsteonMotionSensorDevice } from "./Devices/Insteon/InsteonMotionSensorDevice.js";
30
- import { InsteonOnOffOutletDevice } from "./Devices/Insteon/InsteonOnOffOutletDevice.js";
31
- import { InsteonRelayDevice } from "./Devices/Insteon/InsteonRelayDevice.js";
32
- import { InsteonSmokeSensorDevice } from "./Devices/Insteon/InsteonSmokeSensorDevice.js";
33
- import { InsteonThermostatDevice } from "./Devices/Insteon/InsteonThermostatDevice.js";
34
- import { EventType } from "./Events/EventType.js";
35
- import { NodeType, Props, States, VariableType } from "./ISYConstants.js";
36
- import { ISYNode } from "./ISYNode.js";
37
- import { ISYDeviceNode } from "./Devices/ISYDeviceNode.js";
38
- import { type ISYDevice } from "./ISYDevice.js";
39
- import { ISYScene } from "./ISYScene.js";
40
- import { ISYVariable } from "./ISYVariable.js";
41
-
42
- import * as Utils from "./Utils.js";
43
-
44
- import { X2jOptions, XMLParser } from "fast-xml-parser";
1
+ import WebSocket from 'faye-websocket';
2
+ import { writeFile } from 'fs';
3
+
4
+ import { Parser, type ParserOptions } from 'xml2js';
5
+ import { parseBooleans, parseNumbers } from 'xml2js/lib/processors.js';
6
+ import { XmlDocument } from 'xmldoc';
7
+
8
+ import axios from 'axios';
9
+ import { EventEmitter } from 'events';
10
+ import winston, { format, Logger, loggers, type LeveledLogMethod } from 'winston';
11
+ import { Category } from './Definitions/Global/Categories.js';
12
+ import { Family } from './Definitions/Global/Families.js';
13
+ import type { NodeInfo } from './Model/NodeInfo.js';
14
+ import { DeviceFactory } from './Devices/DeviceFactory.js';
15
+ import { ELKAlarmPanelDevice } from './Devices/Elk/ElkAlarmPanelDevice.js';
16
+ import { ElkAlarmSensorDevice } from './Devices/Elk/ElkAlarmSensorDevice.js';
17
+ import { InsteonBaseDevice } from './Devices/Insteon/InsteonBaseDevice.js';
18
+ import { InsteonOutletDevice } from './Devices/Insteon/InsteonDevice.js';
19
+ import { InsteonDimmableDevice } from './Devices/Insteon/InsteonDimmableDevice.js';
20
+ import { InsteonDimmerOutletDevice } from './Devices/Insteon/InsteonDimmerOutletDevice.js';
21
+ import { InsteonDimmerSwitchDevice } from './Devices/Insteon/InsteonDimmerSwitchDevice.js';
22
+ import { InsteonDoorWindowSensorDevice } from './Devices/Insteon/InsteonDoorWindowSensorDevice.js';
23
+ import { InsteonFanDevice, InsteonFanMotorDevice } from './Devices/Insteon/InsteonFanDevice.js';
24
+ import { InsteonKeypadButtonDevice } from './Devices/Insteon/InsteonKeypadDevice.js';
25
+ import { InsteonKeypadDimmerDevice } from './Devices/Insteon/InsteonKeypadDimmerDevice.js';
26
+ import { InsteonKeypadRelayDevice } from './Devices/Insteon/InsteonKeypadRelayDevice.js';
27
+ import { InsteonLeakSensorDevice } from './Devices/Insteon/InsteonLeakSensorDevice.js';
28
+ import { InsteonLockDevice } from './Devices/Insteon/InsteonLockDevice.js';
29
+ import { InsteonMotionSensorDevice } from './Devices/Insteon/InsteonMotionSensorDevice.js';
30
+ import { InsteonOnOffOutletDevice } from './Devices/Insteon/InsteonOnOffOutletDevice.js';
31
+ import { InsteonRelayDevice } from './Devices/Insteon/InsteonRelayDevice.js';
32
+ import { InsteonSmokeSensorDevice } from './Devices/Insteon/InsteonSmokeSensorDevice.js';
33
+ import { InsteonThermostatDevice } from './Devices/Insteon/InsteonThermostatDevice.js';
34
+ import { EventType } from './Events/EventType.js';
35
+ import { NodeType, Props, States, VariableType } from './ISYConstants.js';
36
+ import { ISYNode } from './ISYNode.js';
37
+ import { ISYDeviceNode } from './Devices/ISYDeviceNode.js';
38
+ import { type ISYDevice } from './ISYDevice.js';
39
+ import { ISYScene } from './ISYScene.js';
40
+ import { ISYVariable } from './ISYVariable.js';
41
+
42
+ import * as Utils from './Utils.js';
43
+
44
+ import { X2jOptions, XMLParser } from 'fast-xml-parser';
45
45
  import { NodeFactory } from './Devices/NodeFactory.js';
46
46
 
47
-
48
47
  export {
49
- Category as Categories,
50
- ELKAlarmPanelDevice,
51
- ElkAlarmSensorDevice,
52
- Family,
53
- InsteonBaseDevice,
54
- InsteonDimmableDevice,
55
- InsteonDimmerOutletDevice,
56
- InsteonDimmerSwitchDevice,
57
- InsteonDoorWindowSensorDevice,
58
- InsteonFanDevice,
59
- InsteonFanMotorDevice,
60
- InsteonKeypadButtonDevice,
61
- InsteonKeypadDimmerDevice,
62
- InsteonKeypadRelayDevice,
63
- InsteonLeakSensorDevice,
64
- InsteonLockDevice,
65
- InsteonMotionSensorDevice,
66
- InsteonOnOffOutletDevice,
67
- InsteonOutletDevice,
68
- InsteonRelayDevice,
69
- InsteonSmokeSensorDevice,
70
- InsteonThermostatDevice,
71
- ISYDeviceNode as ISYDevice,
72
- ISYNode,
73
- ISYScene,
74
- ISYVariable,
75
- NodeType,
76
- Props,
77
- States,
78
- Utils,
79
- VariableType,
48
+ Category as Categories,
49
+ ELKAlarmPanelDevice,
50
+ ElkAlarmSensorDevice,
51
+ Family,
52
+ InsteonBaseDevice,
53
+ InsteonDimmableDevice,
54
+ InsteonDimmerOutletDevice,
55
+ InsteonDimmerSwitchDevice,
56
+ InsteonDoorWindowSensorDevice,
57
+ InsteonFanDevice,
58
+ InsteonFanMotorDevice,
59
+ InsteonKeypadButtonDevice,
60
+ InsteonKeypadDimmerDevice,
61
+ InsteonKeypadRelayDevice,
62
+ InsteonLeakSensorDevice,
63
+ InsteonLockDevice,
64
+ InsteonMotionSensorDevice,
65
+ InsteonOnOffOutletDevice,
66
+ InsteonOutletDevice,
67
+ InsteonRelayDevice,
68
+ InsteonSmokeSensorDevice,
69
+ InsteonThermostatDevice,
70
+ ISYDeviceNode as ISYDevice,
71
+ ISYNode,
72
+ ISYScene,
73
+ ISYVariable,
74
+ NodeType,
75
+ Props,
76
+ States,
77
+ Utils,
78
+ VariableType
80
79
  };
81
80
 
82
81
  const defaultParserOptions: ParserOptions = {
83
- explicitArray: false,
84
- mergeAttrs: true,
82
+ explicitArray: false,
83
+ mergeAttrs: true,
85
84
 
86
- attrValueProcessors: [parseNumbers, parseBooleans],
87
- valueProcessors: [parseNumbers, parseBooleans],
88
- tagNameProcessors: [(tagName) => (tagName === "st" || tagName === "cmd" || tagName === "nodeDef" ? "" : tagName)],
85
+ attrValueProcessors: [parseNumbers, parseBooleans],
86
+ valueProcessors: [parseNumbers, parseBooleans],
87
+ tagNameProcessors: [(tagName) => (tagName === 'st' || tagName === 'cmd' || tagName === 'nodeDef' ? '' : tagName)]
89
88
  };
90
89
 
91
90
  const defaultXMLParserOptions: X2jOptions = {
92
- parseAttributeValue: true,
93
- allowBooleanAttributes: true,
94
- attributeNamePrefix: "",
95
- attributesGroupName: false,
96
- ignoreAttributes: false,
97
- // updateTag(tagName, jPath, attrs) {
98
- // //if(tagName === 'st' || tagName === 'cmd' || tagName === 'nodeDef')
99
- // // return false;
100
- // //return tagName;
101
- // },
102
- textNodeName: "_",
103
- commentPropName: "$comment",
104
- cdataPropName: "$cdata",
105
- ignoreDeclaration: true,
106
- tagValueProcessor: (tagName, tagValue, jPath, hasAttributes, isLeafNode) => {
107
- if (tagValue === "") return null;
108
- return tagValue;
109
- },
110
- isArray(tagName, jPath, isLeafNode, isAttribute) {
111
- if (tagName === "property") return true;
112
- return false;
113
- },
91
+ parseAttributeValue: true,
92
+ allowBooleanAttributes: true,
93
+ attributeNamePrefix: '',
94
+ attributesGroupName: false,
95
+ ignoreAttributes: false,
96
+ // updateTag(tagName, jPath, attrs) {
97
+ // //if(tagName === 'st' || tagName === 'cmd' || tagName === 'nodeDef')
98
+ // // return false;
99
+ // //return tagName;
100
+ // },
101
+ textNodeName: '_',
102
+ commentPropName: '$comment',
103
+ cdataPropName: '$cdata',
104
+ ignoreDeclaration: true,
105
+ tagValueProcessor: (tagName, tagValue, jPath, hasAttributes, isLeafNode) => {
106
+ if (tagValue === '') return null;
107
+ return tagValue;
108
+ },
109
+ isArray(tagName, jPath, isLeafNode, isAttribute) {
110
+ if (tagName === 'property') return true;
111
+ return false;
112
+ }
114
113
  };
115
114
 
116
115
  axios.defaults.transitional.forcedJSONParsing = false;
@@ -120,721 +119,692 @@ const parser = new Parser(defaultParserOptions);
120
119
  export let Controls = {};
121
120
 
122
121
  export class ISY extends EventEmitter implements Disposable {
123
- // #region Properties (30)
124
-
125
- public readonly credentials: { username: string; password: string };
126
- public readonly deviceList: Map<string, ISYDevice<any, any, any, any>> = new Map();
127
- public readonly deviceMap: Map<string, string[]> = new Map();
128
- public readonly displayNameFormat: string;
129
- public readonly elkEnabled: boolean;
130
- public readonly enableWebSocket: boolean;
131
- public readonly folderMap: Map<string, string> = new Map();
132
- public readonly host: string;
133
- public readonly nodeMap: Map<string, ISYNode<any,any, any, any>> = new Map();
134
- public readonly port: number;
135
- public readonly protocol: string;
136
- public readonly sceneList: Map<string, ISYScene> = new Map();
137
- public readonly storagePath: string;
138
- public readonly variableList: Map<string, ISYVariable<VariableType>> = new Map();
139
- public readonly wsprotocol: "ws" | "wss" = "ws";
140
- public readonly zoneMap: Map<string, ElkAlarmSensorDevice> = new Map();
141
-
142
- public static instance: ISY;
143
-
144
- public configInfo: any;
145
- public elkAlarmPanel: ELKAlarmPanelDevice;
146
- public guardianTimer: any;
147
- public id: string;
148
- public lastActivity: any;
149
- public logger: Logger;
150
- public model: any;
151
- public nodesLoaded: boolean = false;
152
- public productId = 5226;
153
- public productName = "eisy";
154
- public serverVersion: any;
155
- public vendorName = "Universal Devices, Inc.";
156
- public webSocket: WebSocket.Client;
157
-
158
- // #endregion Properties (30)
159
-
160
- // #region Constructors (1)
161
-
162
- constructor(
163
- config: {
164
- host: string;
165
- port: number;
166
- protocol: "http" | "https";
167
- username: string;
168
- password: string;
169
- enableWebSocket?: boolean;
170
- displayNameFormat?: string;
171
- elkEnabled?: boolean;
172
- },
173
- logger: Logger = new Logger(),
174
- storagePath?: string
175
- ) {
176
- super();
177
- this.enableWebSocket = config.enableWebSocket ?? true;
178
- this.storagePath = storagePath ?? "./";
179
- this.displayNameFormat = config.displayNameFormat ?? "${location ?? folder} ${spokenName ?? name}";
180
- this.host = config.host;
181
- this.port = config.port;
182
- this.credentials = {
183
- username: config.username,
184
- password: config.password,
185
- };
186
- this.protocol = config.protocol;
187
- this.wsprotocol = config.protocol === "https" ? "wss" : "ws";
188
- this.elkEnabled = config.elkEnabled ?? false;
189
-
190
- this.nodesLoaded = false;
191
- var fopts = format((info) => {
192
- info.message = JSON.stringify(info.message);
193
- return info;
194
- })({ label: "ISY" });
195
- this.logger = loggers.add("isy", {
196
- transports: logger.transports,
197
- levels: logger.levels,
198
- format: format.label({ label: "ISY" }),
199
- });
200
-
201
- this.guardianTimer = null;
202
- if (this.elkEnabled) {
203
- this.elkAlarmPanel = new ELKAlarmPanelDevice(this, 1);
204
- }
205
- ISY.instance = this;
206
- }
207
-
208
- // #endregion Constructors (1)
209
-
210
- // #region Public Getters And Setters (2)
211
-
212
- public get address() {
213
- return `${this.host}:${this.port}`;
214
- }
215
-
216
- public get isDebugEnabled() {
217
- return this.logger?.isDebugEnabled();
218
- }
219
-
220
- // #endregion Public Getters And Setters (2)
221
-
222
- // #region Public Methods (22)
223
-
224
- public override emit(
225
- event: "InitializeCompleted" | "NodeAdded" | "NodeRemoved" | "NodeChanged",
226
- node?: ISYNode<any,any,any,any>
227
- ): boolean {
228
- return super.emit(event, node);
229
- }
230
-
231
- public getDevice<T extends ISYDevice<any, any, any, any> = ISYDevice<any, any, any, any>>(
232
- address: string,
233
- parentsOnly = false
234
- ): T {
235
- let s = this.deviceList.get(address);
236
- if (!parentsOnly) {
237
- if (s === null) {
238
- s = this.deviceList[`${address.substr(0, address.length - 1)} 1`];
239
- }
240
- } else {
241
- while (s.parentAddress !== undefined && s.parentAddress !== s.address && s.parentAddress !== null) {
242
- s = this.deviceList[s.parentAddress];
243
- }
244
- }
245
-
246
- return s as T;
247
- }
248
-
249
- public getNode<T extends ISYNode<any,any,any,any> = ISYNode<any,any,any,any>>(
250
- address: string,
251
- parentsOnly = false
252
- ): T {
253
- let s = this.nodeMap.get(address);
254
- if (!parentsOnly) {
255
- if (s === null) {
256
- s = this.nodeMap[`${address.substr(0, address.length - 1)} 1`];
257
- }
258
- } else {
259
- while (s.parentAddress !== undefined && s.parentAddress !== s.address && s.parentAddress !== null) {
260
- s = this.nodeMap[s.parentAddress];
261
- }
262
- }
263
-
264
- return s as T;
265
- }
266
-
267
-
268
- public getElkAlarmPanel() {
269
- return this.elkAlarmPanel;
270
- }
271
-
272
- public getScene(address: string) {
273
- return this.sceneList.get(address);
274
- }
275
-
276
- public getVariable<P extends VariableType>(type: P, id: number): ISYVariable<P> {
277
- const key = this.#createVariableKey(type, id);
278
- if (this.variableList.has(key)) {
279
- return this.variableList[key];
280
- }
281
- return null;
282
- }
283
-
284
- public getVariableList() {
285
- return this.variableList;
286
- }
287
-
288
- public async handleInitializeError(step: string, reason: any): Promise<any> {
289
- this.logger.error(`Error initializing ISY (${step}): ${JSON.stringify(reason)}`);
290
- return Promise.reject(reason);
291
- }
292
-
293
- public handleWebSocketMessage(event: { data: any }) {
294
- this.lastActivity = new Date();
295
-
296
- parser.parseString(event.data, (err: any, res: { Event: any }) => {
297
- if (err) {
298
- throw err;
299
- }
300
- const evt = res.Event;
301
- if (evt === undefined || evt.control === undefined) {
302
- return;
303
- }
304
- let actionValue = 0;
305
- if (evt.action instanceof Object) {
306
- actionValue = evt.action._;
307
- } else if (evt.action instanceof Number || evt.action instanceof String) {
308
- actionValue = Number(evt.action);
309
- }
310
- const stringControl = Number((evt.control as string)?.replace("_", ""));
311
- switch (stringControl) {
312
- case EventType.Elk:
313
- if (actionValue === 2) {
314
- this.elkAlarmPanel.handleEvent(event);
315
- } else if (actionValue === 3) {
316
- const zeElement = evt.eventInfo.ze;
317
- const zoneId = zeElement.zone;
318
- const zoneDevice = this.zoneMap[zoneId];
319
- if (zoneDevice !== null) {
320
- if (zoneDevice.handleEvent(event)) {
321
- this.nodeChangedHandler(zoneDevice);
322
- }
323
- }
324
- }
325
-
326
- break;
327
-
328
- case EventType.Trigger:
329
- if (actionValue === 6) {
330
- const varNode = evt.eventInfo.var;
331
- const id = varNode.id;
332
- const type = varNode.type;
333
- this.getVariable(type, id)?.handleEvent(evt);
334
- }
335
- break;
336
- case EventType.Heartbeat:
337
- this.logger.debug(`Received ${EventType[Number(stringControl)]} Signal from ISY: ${JSON.stringify(evt)}`);
338
- break;
339
-
340
- default:
341
- if (evt.node !== "" && evt.node !== undefined && evt.node !== null) {
342
- //
343
- const impactedDevice = this.getDevice(evt.node);
344
- if (impactedDevice !== undefined && impactedDevice !== null) {
345
- impactedDevice.handleEvent(evt);
346
- } else {
347
- this.logger.warn(`${EventType[stringControl]} Event for Unidentified Device: ${JSON.stringify(evt)}`);
348
- }
349
- } else {
350
- if (stringControl === EventType.NodeChanged) {
351
- this.logger.info(`Received Node Change Event: ${JSON.stringify(evt)}. These are currently unsupported.`);
352
- }
353
- this.logger.debug(`${EventType[Number(stringControl)]} Event: ${JSON.stringify(evt)}`);
354
- }
355
-
356
- break;
357
- }
358
- });
359
- }
360
-
361
- public async initialize(): Promise<any> {
362
- const that = this;
363
- try {
364
- await this.loadConfig();
365
- await this.loadNodes();
366
- await this.loadVariables(VariableType.Integer);
367
- await this.loadVariables(VariableType.State);
368
- await this.refreshStatuses();
369
- await this.#finishInitialize(true);
370
- } catch (e) {
371
- this.handleInitializeError("initialize", e);
372
- } finally {
373
- if (this.nodesLoaded !== true) {
374
- that.#finishInitialize(false);
375
- }
376
- }
377
- return Promise.resolve(true);
378
- }
379
-
380
- public initializeWebSocket() {
381
- const that = this;
382
- const auth = `Basic ${Buffer.from(`${this.credentials.username}:${this.credentials.password}`).toString("base64")}`;
383
- this.logger.info(`Opening webSocket: ${this.wsprotocol}://${this.address}/rest/subscribe`);
384
- if (this.webSocket) {
385
- try {
386
- this.webSocket.close();
387
- } catch (e) {
388
- this.logger.warn(`Error closing existing websocket: ${e.message}`);
389
- }
390
- }
391
- this.webSocket = new WebSocket.Client(`${this.wsprotocol}://${this.address}/rest/subscribe`, ["ISYSUB"], {
392
- headers: {
393
- Origin: "com.universal-devices.websockets.isy",
394
- Authorization: auth,
395
- },
396
- ping: 10,
397
- });
398
-
399
- this.lastActivity = new Date();
400
-
401
- this.webSocket
402
- .on("message", (event: any) => {
403
- that.logger.silly(`Received message: ${JSON.stringify(event.data, null, 2)}`);
404
- that.handleWebSocketMessage(event);
405
- })
406
- .on("error", (err: any, response: any) => {
407
- that.logger.warn(`Websocket subscription error: ${JSON.stringify(err.message)}`);
408
- /// throw new Error('Error calling ISY' + err);
409
- })
410
- .on("fail", (data: string, response: any) => {
411
- that.logger.warn(`Websocket subscription failure: ${data}`);
412
- throw new Error("Failed calling ISY");
413
- })
414
- .on("abort", () => {
415
- that.logger.warn("Websocket subscription aborted.");
416
- throw new Error("Call to ISY was aborted");
417
- })
418
- .on("timeout", (ms: string) => {
419
- that.logger.warn(`Websocket subscription timed out after ${ms} milliseconds.`);
420
- throw new Error("Timeout contacting ISY");
421
- });
422
- }
423
-
424
- public async loadConfig(): Promise<any> {
425
- try {
426
- this.logger.info("Loading ISY Config");
427
- const configuration = (await this.sendRequest("config")).configuration;
428
- if (this.isDebugEnabled) {
429
- writeFile(this.storagePath + "/ISYConfigDump.json", JSON.stringify(configuration), this.logger.error);
430
- }
431
-
432
- const controls = configuration.controls;
433
- this.model = configuration.deviceSpecs.model;
434
- this.serverVersion = configuration.app_version;
435
- this.vendorName = configuration.deviceSpecs.make;
436
- this.productId = configuration.product.id;
437
- this.productName = configuration.product.desc;
438
- this.id = configuration.root.id;
439
- // TODO: Check Installed Features
440
- // this.logger.info(result.configuration);
441
- if (controls !== undefined) {
442
- // this.logger.info(controls.control);
443
- // var arr = new Array(controls.control);
444
- for (const ctl of controls.control) {
445
- // this.logger.info(ctl);
446
- Controls[ctl.name] = ctl;
447
- }
448
- }
449
- return configuration;
450
- } catch (e) {
451
- this.handleInitializeError("config", e);
452
- throw Error(`Error Loading Config: ${(e as Error).message}`);
453
- }
454
- }
455
-
456
- public async loadNodes(): Promise<any> {
457
- try {
458
- const result = await this.sendRequest("nodes");
459
- if (this.isDebugEnabled)
460
- writeFile(this.storagePath + "/ISYNodesDump.json", JSON.stringify(result), this.logger.error);
461
-
462
- await this.#readFolderNodes(result).catch((p) => this.logger.error("Error Loading Folders", p));
463
- await this.#readDeviceNodes(result).catch((p) => this.logger.error("Error Loading Devices", p));
464
- await this.#readSceneNodes(result).catch((p) => this.logger.error("Error Loading Scenes", p));
465
- return result;
466
- } catch (e) {
467
- throw new Error(`Error loading nodes: ${(e as Error).message}`);
468
- }
469
- }
470
-
471
- public async loadVariables(type: VariableType): Promise<any> {
472
- const that = this;
473
- return this.sendRequest(`vars/definitions/${type}`)
474
- .then((result) => that.#createVariables(type, result))
475
- .then(() => that.sendRequest(`vars/get/${type}`))
476
- .then(that.#setVariableValues.bind(that));
477
- }
478
-
479
- public nodeChangedHandler(node: ELKAlarmPanelDevice | ElkAlarmSensorDevice, propertyName = null) {
480
- const that = this;
481
- if (this.nodesLoaded) {
482
- // this.logger.info(`Node: ${node.address} changed`);
483
- // if (this.changeCallback !== undefined && this.changeCallback !== null) {
484
- // t//his.changeCallback(that, node, propertyName);
485
- // }
486
- }
487
- }
488
-
489
- public override on(
490
- event: "InitializeCompleted" | "NodeAdded" | "NodeRemoved" | "NodeChanged",
491
- listener: (node?: ISYNode<any,any,any,any>) => void
492
- ): this {
493
- return super.on(event, listener);
494
- }
495
-
496
- public async refreshStatuses() {
497
- try {
498
- const that = this;
499
- const result = await that.sendRequest("status");
500
- if (that.isDebugEnabled) {
501
- writeFile(that.storagePath + "/ISYStatusDump.json", JSON.stringify(result), this.logger.error);
502
- }
503
- this.logger.debug(result);
504
- for (const node of result.nodes.node) {
505
- this.logger.debug(node);
506
- let device = that.getDevice(node.id) as unknown as ISYNode<any,any,any,any>;
507
- if (device !== null && device !== undefined) {
508
- // let child = device.children.find((p) => p.address === node.id);
509
- // if (child) {
510
- // //Case FanLinc where we treat the light as a child of the fan.
511
- // device = child;
512
- // }
513
- device.parseResult(node.property)
514
-
515
- // if (Array.isArray(node.property)) {
516
- // for (let prop of node.property) {
517
- // device.applyStatus(prop)
518
- // // device.local[prop.id] = device.convertFrom(prop.value, prop.uom);
519
- // // device.formatted[prop.id] = prop.formatted;
520
- // // device.uom[prop.id] = prop.uom;
521
- // // device.logger(
522
- // // `Property ${Controls[prop.id].label} (${prop.id}) initialized to: ${device.local[prop.id]} (${
523
- // // device.formatted[prop.id]
524
- // // })`
525
- // // );
526
- // }
527
- // } else if (node.property) {
528
- // device.local[node.property.id] = device.convertFrom(node.property.value, node.property.uom);
529
- // device.formatted[node.property.id] = node.property.formatted;
530
- // device.uom[node.property.id] = node.property.uom;
531
- // device.logger(
532
- // `Property ${Controls[node.property.id].label} (${node.property.id}) initialized to: ${
533
- // device.local[node.property.id]
534
- // } (${device.formatted[node.property.id]})`
535
- // );
536
- // }
537
- }
538
- }
539
- } catch (e) {
540
- throw new Error(`Error refreshing statuses: ${JSON.stringify(e.message)}`);
541
- }
542
- }
543
-
544
- public async sendGetVariable(id: any, type: any, handleResult: (arg0: number, arg1: number) => void) {
545
- const uriToUse = `${this.protocol}://${this.address}/rest/vars/get/${type}/${id}`;
546
- this.logger.info(`Sending ISY command...${uriToUse}`);
547
-
548
- return this.sendRequest(uriToUse).then((p) => handleResult(p.val, p.init));
549
- }
550
-
551
- public async sendISYCommand(path: string): Promise<any> {
552
- // const uriToUse = `${this.protocol}://${this.address}/rest/${path}`;
553
- this.logger.info(`Sending command...${path}`);
554
-
555
- return this.sendRequest(path);
556
- }
557
-
558
- public async sendNodeCommand<P extends string | symbol, N extends ISYNode<any,any,any,any>>(
559
- node: N,
560
- command: string,
561
- parameters?: Record<P, string | number> | string | number
562
- ): Promise<any> {
563
- let uriToUse = `nodes/${node.address}/cmd/${command}`;
564
- if (parameters !== null && parameters !== undefined) {
565
- if (typeof parameters == "object") {
566
- var q = parameters as Record<P, string | number>;
567
- for (const paramName of Object.getOwnPropertyNames(q)) {
568
- uriToUse += `/${paramName}/${q[paramName]}`;
569
- }
570
-
571
- //uriToUse += `/${q[((p : Record<string,number|number>) => `${p[]}/${p.paramValue}` ).join('/')}`;
572
- } else if (typeof parameters == "number" || typeof parameters == "string") {
573
- uriToUse += `/${parameters}`;
574
- }
575
- }
576
- this.logger.info(`${node.name}: sending ${command} command: ${uriToUse}`);
577
- return this.sendRequest(uriToUse);
578
- }
579
-
580
- public async sendRequest(
581
- url: string,
582
- options: {
583
- parserOptions?: ParserOptions;
584
- trailingSlash: boolean;
585
- requestLogLevel?: any;
586
- responseLogLevel?: any;
587
- } = { trailingSlash: true }
588
- ): Promise<any> {
589
- const requestLogLevel = options.requestLogLevel ?? winston.config.cli.levels.debug;
590
- const responseLogLevel = options.responseLogLevel ?? winston.config.cli.levels.silly;
591
- url = `${this.protocol}://${this.address}/rest/${url}${options.trailingSlash ? "/" : ""}`;
592
- this.logger.log(`Sending request: ${url}`, requestLogLevel);
593
- try {
594
- const response = await axios.get(url, {
595
- auth: { username: this.credentials.username, password: this.credentials.password },
596
- });
597
- if (response.data) {
598
- if (response.headers["content-type"].toString().includes("xml")) {
599
- let curParser = parser;
600
- if (options.parserOptions) curParser = new Parser({ ...defaultParserOptions, ...options.parserOptions });
601
- var altParser = new XMLParser(defaultXMLParserOptions);
602
- var s = altParser.parse(response.data);
603
- this.logger.log(`Response: ${JSON.stringify(s)}`, requestLogLevel ?? "debug");
604
- return s;
605
- } else if (response.headers["content-type"].toString().includes("json")) {
606
- this.logger.log(`Response: ${JSON.stringify(response.data)}`, requestLogLevel ?? "debug");
607
- return JSON.parse(response.data);
608
- } else {
609
- this.logger.log(
610
- `Response Header: ${JSON.stringify(response.headers)} Response: ${JSON.stringify(response.data)}`,
611
- responseLogLevel ?? "debug"
612
- );
613
- return response.data;
614
- }
615
- }
616
- } catch (error) {
617
- this.logger.error(`Error sending request to ISY: ${error?.message}`);
618
- throw new Error(`Error sending request to ISY: ${JSON.stringify(error)}`);
619
- }
620
- }
621
-
622
- public async sendSetVariable(
623
- id: any,
624
- type: any,
625
- value: any,
626
- handleResult: { (success: any): void; (arg0: boolean): void; (arg0: boolean): void }
627
- ) {
628
- const uriToUse = `/rest/vars/set/${type}/${id}/${value}`;
629
- this.logger.info(`Sending ISY command...${uriToUse}`);
630
-
631
- return this.sendRequest(uriToUse);
632
- }
633
-
634
- public variableChangedHandler(variable: { id: string; type: string }) {
635
- this.logger.info(`Variable: ${variable.id} (${variable.type}) changed`);
636
- }
637
-
638
- // #endregion Public Methods (22)
639
-
640
- // #region Private Methods (11)
641
-
642
- #checkForFailure(response: any): boolean {
643
- return (
644
- response === null ||
645
- response instanceof Error ||
646
- (response.RestResponse !== undefined && response.RestResponse.status !== 200)
647
- );
648
- }
649
-
650
- #createVariableKey(type: VariableType, id: number) {
651
- return `${type}:${id}`;
652
- }
653
-
654
- #createVariables(type: VariableType, result: any) {
655
- for (const variable of result.CList.e) {
656
- const id = Number(variable.id);
657
- const name = variable.name;
658
- const newVariable = new ISYVariable(this, id, name, type);
659
- this.variableList.set(this.#createVariableKey(type, id), newVariable);
660
- }
661
- }
662
-
663
- #finishInitialize(success: boolean) {
664
- if (!this.nodesLoaded) {
665
- this.nodesLoaded = true;
666
- //initializeCompleted();
667
- if (success) {
668
- // if (this.elkEnabled) {
669
- // this.deviceList[this.elkAlarmPanel.address] = this.elkAlarmPanel;
670
- // }
671
- if (this.enableWebSocket) {
672
- this.guardianTimer = setInterval(this.#guardian.bind(this), 60000);
673
- this.initializeWebSocket();
674
- }
675
- }
676
- }
677
- }
678
-
679
- #guardian() {
680
- const timeNow = new Date();
681
-
682
- if (Number(timeNow) - Number(this.lastActivity) > 60000) {
683
- this.logger.info("Guardian: Detected no activity in more then 60 seconds. Reinitializing web sockets");
684
- this.initializeWebSocket();
685
- }
686
- }
687
-
688
- #loadElkInitialStatus(result: any) {
689
- const p = new Parser({
690
- explicitArray: false,
691
- mergeAttrs: true,
692
- });
693
- p.parseString(result, (err: any, res: { ae: any; ze: any }) => {
694
- if (err) {
695
- throw err;
696
- }
697
-
698
- for (const nodes of res.ae) {
699
- this.elkAlarmPanel.setFromAreaUpdate(nodes);
700
- }
701
- for (const nodes of res.ze) {
702
- const id = nodes.zone;
703
- const zoneDevice = this.zoneMap[id];
704
- if (zoneDevice !== null) {
705
- zoneDevice.setFromZoneUpdate(nodes);
706
- if (this.deviceList[zoneDevice.address] === null && zoneDevice.isPresent()) {
707
- this.deviceList[zoneDevice.address] = zoneDevice;
708
- // this.deviceIndex[zoneDevice.address] = zoneDevice;
709
- }
710
- }
711
- }
712
- });
713
- }
714
-
715
- #loadElkNodes(result: any) {
716
- const document = new XmlDocument(result);
717
- const nodes = document.childNamed("areas").childNamed("area").childrenNamed("zone");
718
- for (let index = 0; index < nodes.length; index++) {
719
- const id = nodes[index].attr.id;
720
- const name = nodes[index].attr.name;
721
- const alarmDef = nodes[index].attr.alarmDef;
722
- const newDevice = new ElkAlarmSensorDevice(this, name, 1, id /*TODO: Handle CO Sensor vs. Door/Window Sensor */);
723
- this.zoneMap[newDevice.zone] = newDevice;
724
- }
725
- }
726
-
727
- async #readDeviceNodes(obj: { nodes: { node: NodeInfo[] } }) {
728
- this.logger.info("Loading Device Nodes");
729
- for (const nodeInfo of obj.nodes.node) {
730
- this.logger.debug(`Loading Device Node: ${JSON.stringify(nodeInfo)}`);
731
- if (!this.deviceMap.has(nodeInfo.pnode)) {
732
- const address = nodeInfo.address;
733
- this.deviceMap[nodeInfo.pnode] = {
734
- address,
735
- };
736
- } else {
737
- this.deviceMap[nodeInfo.pnode].push(nodeInfo.address);
738
- }
739
- let newDevice: ISYDeviceNode<any,any,any,any> = null;
740
-
741
- // let deviceTypeInfo = this.isyTypeToTypeName(device.type, device.address);
742
- // this.logger.info(JSON.stringify(deviceTypeInfo));
743
-
744
- const enabled = nodeInfo.enabled ?? true;
745
- const d = await NodeFactory.get(nodeInfo);
746
- const m = DeviceFactory.getDeviceDetails(nodeInfo);
747
-
748
- if (d) {
749
- newDevice = new d(this, nodeInfo) as ISYDeviceNode<any,any,any,any>;
750
- }
751
- if (m) {
752
- newDevice = newDevice ?? new m.class(this, nodeInfo) as unknown as ISYDeviceNode<any,any,any,any>;
753
- newDevice.productName = m.name;
754
- newDevice.model = `(${m.modelNumber}) ${m.name} v.${m.version}`;
755
- newDevice.modelNumber = m.modelNumber;
756
- newDevice.version = m.version;
757
- }
758
- if (enabled) {
759
- if (newDevice === null) {
760
- this.logger.warn(
761
- `Device type resolution failed for ${nodeInfo.name} with type: ${nodeInfo.type} and nodedef: ${nodeInfo.nodeDefId}`
762
- );
763
- newDevice = new ISYDeviceNode<any,any,any,any>(this, nodeInfo);
764
- } else if (newDevice !== null) {
765
- if (m.unsupported) {
766
- this.logger.warn(
767
- "Device not currently supported: " + JSON.stringify(nodeInfo) + " /n It has been mapped to: " + d.name
768
- );
769
- }
770
- try {
771
- await newDevice.refreshNotes();
772
- } catch (e) {
773
- this.logger.debug("No notes found.");
774
- }
775
-
776
- // if (!newDevice.hidden) {
777
- // }
778
-
779
- // this.deviceList.push(newDevice);
780
- } else {
781
- }
782
- this.nodeMap.set(newDevice.address, newDevice);
783
- } else {
784
- this.logger.info(`Ignoring disabled device: ${nodeInfo.name}`);
785
- }
786
- }
787
-
788
- this.logger.info(`${this.nodeMap.size} devices added.`);
789
- }
790
-
791
- async #readFolderNodes(result: { nodes: { folder: any } }) {
792
- this.logger.info("Loading Folder Nodes");
793
- if (result?.nodes?.folder) {
794
- for (const folder of result.nodes.folder) {
795
- this.logger.info(`Loading Folder Node: ${JSON.stringify(folder)}`);
796
- this.folderMap.set(folder.address, folder.name);
797
- }
798
- }
799
- }
800
-
801
- async #readSceneNodes(result: { nodes: { group: any } }) {
802
- this.logger.info("Loading Scene Nodes");
803
- for (const scene of result.nodes?.group) {
804
- if (scene.name === "ISY" || scene.name === "IoX" || scene.name === "Auto DR") {
805
- continue;
806
- } // Skip ISY & Auto DR Scenes
807
-
808
- const newScene = new ISYScene(this, scene);
809
- try {
810
- await newScene.refreshNotes();
811
- } catch (e) {
812
- this.logger.debug("No notes found.");
813
- }
814
- this.sceneList.set(newScene.address, newScene);
815
- }
816
- }
817
-
818
- #setVariableValues(result: any) {
819
- for (const vals of result.vars.var) {
820
- const type = Number(vals.type) as VariableType;
821
- const id = Number(vals.id);
822
- const variable = this.getVariable(type, id);
823
- if (variable) {
824
- variable.init = vals.init;
825
- variable.value = vals.val;
826
- variable.lastChanged = new Date(vals.ts);
827
- }
828
- }
829
- }
830
-
831
- [Symbol.dispose](): void {
832
- try {
833
- this.webSocket.close();
834
- } catch (e) {
835
- this.logger.error(`Error closing websocket: ${e.message}`);
836
- }
837
- }
838
-
839
- // #endregion Private Methods (11)
122
+ // #region Properties (30)
123
+
124
+ public readonly credentials: { username: string; password: string };
125
+ public readonly deviceList: Map<string, ISYDevice<any, any, any, any>> = new Map();
126
+ public readonly deviceMap: Map<string, string[]> = new Map();
127
+ public readonly displayNameFormat: string;
128
+ public readonly elkEnabled: boolean;
129
+ public readonly enableWebSocket: boolean;
130
+ public readonly folderMap: Map<string, string> = new Map();
131
+ public readonly host: string;
132
+ public readonly nodeMap: Map<string, ISYNode<any, any, any, any>> = new Map();
133
+ public readonly port: number;
134
+ public readonly protocol: string;
135
+ public readonly sceneList: Map<string, ISYScene> = new Map();
136
+ public readonly storagePath: string;
137
+ public readonly variableList: Map<string, ISYVariable<VariableType>> = new Map();
138
+ public readonly wsprotocol: 'ws' | 'wss' = 'ws';
139
+ public readonly zoneMap: Map<string, ElkAlarmSensorDevice> = new Map();
140
+
141
+ public static instance: ISY;
142
+
143
+ public configInfo: any;
144
+ public elkAlarmPanel: ELKAlarmPanelDevice;
145
+ public guardianTimer: any;
146
+ public id: string;
147
+ public lastActivity: any;
148
+ public logger: Logger;
149
+ public model: any;
150
+ public nodesLoaded: boolean = false;
151
+ public productId = 5226;
152
+ public productName = 'eisy';
153
+ public serverVersion: any;
154
+ public vendorName = 'Universal Devices, Inc.';
155
+ public webSocket: WebSocket.Client;
156
+
157
+ // #endregion Properties (30)
158
+
159
+ // #region Constructors (1)
160
+
161
+ constructor(
162
+ config: {
163
+ host: string;
164
+ port: number;
165
+ protocol: 'http' | 'https';
166
+ username: string;
167
+ password: string;
168
+ enableWebSocket?: boolean;
169
+ displayNameFormat?: string;
170
+ elkEnabled?: boolean;
171
+ },
172
+ logger: Logger = new Logger(),
173
+ storagePath?: string
174
+ ) {
175
+ super();
176
+ this.enableWebSocket = config.enableWebSocket ?? true;
177
+ this.storagePath = storagePath ?? './';
178
+ this.displayNameFormat = config.displayNameFormat ?? '${location ?? folder} ${spokenName ?? name}';
179
+ this.host = config.host;
180
+ this.port = config.port;
181
+ this.credentials = {
182
+ username: config.username,
183
+ password: config.password
184
+ };
185
+ this.protocol = config.protocol;
186
+ this.wsprotocol = config.protocol === 'https' ? 'wss' : 'ws';
187
+ this.elkEnabled = config.elkEnabled ?? false;
188
+
189
+ this.nodesLoaded = false;
190
+ var fopts = format((info) => {
191
+ info.message = JSON.stringify(info.message);
192
+ return info;
193
+ })({ label: 'ISY' });
194
+ this.logger = loggers.add('isy', {
195
+ transports: logger.transports,
196
+ levels: logger.levels,
197
+ format: format.label({ label: 'ISY' })
198
+ });
199
+
200
+ this.guardianTimer = null;
201
+ if (this.elkEnabled) {
202
+ this.elkAlarmPanel = new ELKAlarmPanelDevice(this, 1);
203
+ }
204
+ ISY.instance = this;
205
+ }
206
+
207
+ // #endregion Constructors (1)
208
+
209
+ // #region Public Getters And Setters (2)
210
+
211
+ public get address() {
212
+ return `${this.host}:${this.port}`;
213
+ }
214
+
215
+ public get isDebugEnabled() {
216
+ return this.logger?.isDebugEnabled();
217
+ }
218
+
219
+ // #endregion Public Getters And Setters (2)
220
+
221
+ // #region Public Methods (22)
222
+
223
+ public override emit(event: 'InitializeCompleted' | 'NodeAdded' | 'NodeRemoved' | 'NodeChanged', node?: ISYNode<any, any, any, any>): boolean {
224
+ return super.emit(event, node);
225
+ }
226
+
227
+ public getDevice<T extends ISYDevice<any, any, any, any> = ISYDevice<any, any, any, any>>(address: string, parentsOnly = false): T {
228
+ let s = this.deviceList.get(address);
229
+ if (!parentsOnly) {
230
+ if (s === null) {
231
+ s = this.deviceList[`${address.substr(0, address.length - 1)} 1`];
232
+ }
233
+ } else {
234
+ while (s.parentAddress !== undefined && s.parentAddress !== s.address && s.parentAddress !== null) {
235
+ s = this.deviceList[s.parentAddress];
236
+ }
237
+ }
238
+
239
+ return s as T;
240
+ }
241
+
242
+ public getNode<T extends ISYNode<any, any, any, any> = ISYNode<any, any, any, any>>(address: string, parentsOnly = false): T {
243
+ let s = this.nodeMap.get(address);
244
+ if (!parentsOnly) {
245
+ if (s === null) {
246
+ s = this.nodeMap[`${address.substr(0, address.length - 1)} 1`];
247
+ }
248
+ } else {
249
+ while (s.parentAddress !== undefined && s.parentAddress !== s.address && s.parentAddress !== null) {
250
+ s = this.nodeMap[s.parentAddress];
251
+ }
252
+ }
253
+
254
+ return s as T;
255
+ }
256
+
257
+ public getElkAlarmPanel() {
258
+ return this.elkAlarmPanel;
259
+ }
260
+
261
+ public getScene(address: string) {
262
+ return this.sceneList.get(address);
263
+ }
264
+
265
+ public getVariable<P extends VariableType>(type: P, id: number): ISYVariable<P> {
266
+ const key = this.#createVariableKey(type, id);
267
+ if (this.variableList.has(key)) {
268
+ return this.variableList[key];
269
+ }
270
+ return null;
271
+ }
272
+
273
+ public getVariableList() {
274
+ return this.variableList;
275
+ }
276
+
277
+ public async handleInitializeError(step: string, reason: any): Promise<any> {
278
+ this.logger.error(`Error initializing ISY (${step}): ${JSON.stringify(reason)}`);
279
+ return Promise.reject(reason);
280
+ }
281
+
282
+ public handleWebSocketMessage(event: { data: any }) {
283
+ this.lastActivity = new Date();
284
+
285
+ parser.parseString(event.data, (err: any, res: { Event: any }) => {
286
+ if (err) {
287
+ throw err;
288
+ }
289
+ const evt = res.Event;
290
+ if (evt === undefined || evt.control === undefined) {
291
+ return;
292
+ }
293
+ let actionValue = 0;
294
+ if (evt.action instanceof Object) {
295
+ actionValue = evt.action._;
296
+ } else if (evt.action instanceof Number || evt.action instanceof String) {
297
+ actionValue = Number(evt.action);
298
+ }
299
+ const stringControl = Number((evt.control as string)?.replace('_', ''));
300
+ switch (stringControl) {
301
+ case EventType.Elk:
302
+ if (actionValue === 2) {
303
+ this.elkAlarmPanel.handleEvent(event);
304
+ } else if (actionValue === 3) {
305
+ const zeElement = evt.eventInfo.ze;
306
+ const zoneId = zeElement.zone;
307
+ const zoneDevice = this.zoneMap[zoneId];
308
+ if (zoneDevice !== null) {
309
+ if (zoneDevice.handleEvent(event)) {
310
+ this.nodeChangedHandler(zoneDevice);
311
+ }
312
+ }
313
+ }
314
+
315
+ break;
316
+
317
+ case EventType.Trigger:
318
+ if (actionValue === 6) {
319
+ const varNode = evt.eventInfo.var;
320
+ const id = varNode.id;
321
+ const type = varNode.type;
322
+ this.getVariable(type, id)?.handleEvent(evt);
323
+ }
324
+ break;
325
+ case EventType.Heartbeat:
326
+ this.logger.debug(`Received ${EventType[Number(stringControl)]} Signal from ISY: ${JSON.stringify(evt)}`);
327
+ break;
328
+
329
+ default:
330
+ if (evt.node !== '' && evt.node !== undefined && evt.node !== null) {
331
+ //
332
+ const impactedDevice = this.getNode(evt.node);
333
+ if (impactedDevice !== undefined && impactedDevice !== null) {
334
+ impactedDevice.handleEvent(evt);
335
+ } else {
336
+ this.logger.warn(`${EventType[stringControl]} Event for Unidentified Device: ${JSON.stringify(evt)}`);
337
+ }
338
+ } else {
339
+ if (stringControl === EventType.NodeChanged) {
340
+ this.logger.info(`Received Node Change Event: ${JSON.stringify(evt)}. These are currently unsupported.`);
341
+ }
342
+ this.logger.debug(`${EventType[Number(stringControl)]} Event: ${JSON.stringify(evt)}`);
343
+ }
344
+
345
+ break;
346
+ }
347
+ });
348
+ }
349
+
350
+ public async initialize(): Promise<any> {
351
+ const that = this;
352
+ try {
353
+ await this.loadConfig();
354
+ await this.loadNodes();
355
+ await this.loadVariables(VariableType.Integer);
356
+ await this.loadVariables(VariableType.State);
357
+ await this.refreshStatuses();
358
+ await this.#finishInitialize(true);
359
+ } catch (e) {
360
+ this.handleInitializeError('initialize', e);
361
+ } finally {
362
+ if (this.nodesLoaded !== true) {
363
+ that.#finishInitialize(false);
364
+ }
365
+ }
366
+ return Promise.resolve(true);
367
+ }
368
+
369
+ public initializeWebSocket() {
370
+ const that = this;
371
+ const auth = `Basic ${Buffer.from(`${this.credentials.username}:${this.credentials.password}`).toString('base64')}`;
372
+ this.logger.info(`Opening webSocket: ${this.wsprotocol}://${this.address}/rest/subscribe`);
373
+ if (this.webSocket) {
374
+ try {
375
+ this.webSocket.close();
376
+ } catch (e) {
377
+ this.logger.warn(`Error closing existing websocket: ${e.message}`);
378
+ }
379
+ }
380
+ this.webSocket = new WebSocket.Client(`${this.wsprotocol}://${this.address}/rest/subscribe`, ['ISYSUB'], {
381
+ headers: {
382
+ Origin: 'com.universal-devices.websockets.isy',
383
+ Authorization: auth
384
+ },
385
+ ping: 10
386
+ });
387
+
388
+ this.lastActivity = new Date();
389
+
390
+ this.webSocket
391
+ .on('message', (event: any) => {
392
+ that.logger.silly(`Received message: ${JSON.stringify(event.data, null, 2)}`);
393
+ that.handleWebSocketMessage(event);
394
+ })
395
+ .on('error', (err: any, response: any) => {
396
+ that.logger.warn(`Websocket subscription error: ${JSON.stringify(err.message)}`);
397
+ /// throw new Error('Error calling ISY' + err);
398
+ })
399
+ .on('fail', (data: string, response: any) => {
400
+ that.logger.warn(`Websocket subscription failure: ${data}`);
401
+ throw new Error('Failed calling ISY');
402
+ })
403
+ .on('abort', () => {
404
+ that.logger.warn('Websocket subscription aborted.');
405
+ throw new Error('Call to ISY was aborted');
406
+ })
407
+ .on('timeout', (ms: string) => {
408
+ that.logger.warn(`Websocket subscription timed out after ${ms} milliseconds.`);
409
+ throw new Error('Timeout contacting ISY');
410
+ });
411
+ }
412
+
413
+ public async loadConfig(): Promise<any> {
414
+ try {
415
+ this.logger.info('Loading ISY Config');
416
+ const configuration = (await this.sendRequest('config')).configuration;
417
+ if (this.isDebugEnabled) {
418
+ writeFile(this.storagePath + '/ISYConfigDump.json', JSON.stringify(configuration), this.logger.error);
419
+ }
420
+
421
+ const controls = configuration.controls;
422
+ this.model = configuration.deviceSpecs.model;
423
+ this.serverVersion = configuration.app_version;
424
+ this.vendorName = configuration.deviceSpecs.make;
425
+ this.productId = configuration.product.id;
426
+ this.productName = configuration.product.desc;
427
+ this.id = configuration.root.id;
428
+ // TODO: Check Installed Features
429
+ // this.logger.info(result.configuration);
430
+ if (controls !== undefined) {
431
+ // this.logger.info(controls.control);
432
+ // var arr = new Array(controls.control);
433
+ for (const ctl of controls.control) {
434
+ // this.logger.info(ctl);
435
+ Controls[ctl.name] = ctl;
436
+ }
437
+ }
438
+ return configuration;
439
+ } catch (e) {
440
+ this.handleInitializeError('config', e);
441
+ throw Error(`Error Loading Config: ${(e as Error).message}`);
442
+ }
443
+ }
444
+
445
+ public async loadNodes(): Promise<any> {
446
+ try {
447
+ const result = await this.sendRequest('nodes');
448
+ if (this.isDebugEnabled) writeFile(this.storagePath + '/ISYNodesDump.json', JSON.stringify(result), this.logger.error);
449
+
450
+ await this.#readFolderNodes(result).catch((p) => this.logger.error('Error Loading Folders', p));
451
+ await this.#readDeviceNodes(result).catch((p) => this.logger.error('Error Loading Devices', p));
452
+ await this.#readSceneNodes(result).catch((p) => this.logger.error('Error Loading Scenes', p));
453
+ return result;
454
+ } catch (e) {
455
+ throw new Error(`Error loading nodes: ${(e as Error).message}`);
456
+ }
457
+ }
458
+
459
+ public async loadVariables(type: VariableType): Promise<any> {
460
+ const that = this;
461
+ return this.sendRequest(`vars/definitions/${type}`)
462
+ .then((result) => that.#createVariables(type, result))
463
+ .then(() => that.sendRequest(`vars/get/${type}`))
464
+ .then(that.#setVariableValues.bind(that));
465
+ }
466
+
467
+ public nodeChangedHandler(node: ELKAlarmPanelDevice | ElkAlarmSensorDevice, propertyName = null) {
468
+ const that = this;
469
+ if (this.nodesLoaded) {
470
+ // this.logger.info(`Node: ${node.address} changed`);
471
+ // if (this.changeCallback !== undefined && this.changeCallback !== null) {
472
+ // t//his.changeCallback(that, node, propertyName);
473
+ // }
474
+ }
475
+ }
476
+
477
+ public override on(event: 'InitializeCompleted' | 'NodeAdded' | 'NodeRemoved' | 'NodeChanged', listener: (node?: ISYNode<any, any, any, any>) => void): this {
478
+ return super.on(event, listener);
479
+ }
480
+
481
+ public async refreshStatuses() {
482
+ try {
483
+ const that = this;
484
+ const result = await that.sendRequest('status');
485
+ if (that.isDebugEnabled) {
486
+ writeFile(that.storagePath + '/ISYStatusDump.json', JSON.stringify(result), this.logger.error);
487
+ }
488
+ this.logger.debug(result);
489
+ for (const node of result.nodes.node) {
490
+ this.logger.debug(node);
491
+ let device = that.getDevice(node.id) as unknown as ISYNode<any, any, any, any>;
492
+ if (device !== null && device !== undefined) {
493
+ // let child = device.children.find((p) => p.address === node.id);
494
+ // if (child) {
495
+ // //Case FanLinc where we treat the light as a child of the fan.
496
+ // device = child;
497
+ // }
498
+ device.parseResult(node.property);
499
+
500
+ // if (Array.isArray(node.property)) {
501
+ // for (let prop of node.property) {
502
+ // device.applyStatus(prop)
503
+ // // device.local[prop.id] = device.convertFrom(prop.value, prop.uom);
504
+ // // device.formatted[prop.id] = prop.formatted;
505
+ // // device.uom[prop.id] = prop.uom;
506
+ // // device.logger(
507
+ // // `Property ${Controls[prop.id].label} (${prop.id}) initialized to: ${device.local[prop.id]} (${
508
+ // // device.formatted[prop.id]
509
+ // // })`
510
+ // // );
511
+ // }
512
+ // } else if (node.property) {
513
+ // device.local[node.property.id] = device.convertFrom(node.property.value, node.property.uom);
514
+ // device.formatted[node.property.id] = node.property.formatted;
515
+ // device.uom[node.property.id] = node.property.uom;
516
+ // device.logger(
517
+ // `Property ${Controls[node.property.id].label} (${node.property.id}) initialized to: ${
518
+ // device.local[node.property.id]
519
+ // } (${device.formatted[node.property.id]})`
520
+ // );
521
+ // }
522
+ }
523
+ }
524
+ } catch (e) {
525
+ throw new Error(`Error refreshing statuses: ${JSON.stringify(e.message)}`);
526
+ }
527
+ }
528
+
529
+ public async sendGetVariable(id: any, type: any, handleResult: (arg0: number, arg1: number) => void) {
530
+ const uriToUse = `${this.protocol}://${this.address}/rest/vars/get/${type}/${id}`;
531
+ this.logger.info(`Sending ISY command...${uriToUse}`);
532
+
533
+ return this.sendRequest(uriToUse).then((p) => handleResult(p.val, p.init));
534
+ }
535
+
536
+ public async sendISYCommand(path: string): Promise<any> {
537
+ // const uriToUse = `${this.protocol}://${this.address}/rest/${path}`;
538
+ this.logger.info(`Sending command...${path}`);
539
+
540
+ return this.sendRequest(path);
541
+ }
542
+
543
+ public async sendNodeCommand<P extends string | symbol, N extends ISYNode<any, any, any, any>>(node: N, command: string, parameters?: Record<P, string | number> | string | number): Promise<any> {
544
+ let uriToUse = `nodes/${node.address}/cmd/${command}`;
545
+ if (parameters !== null && parameters !== undefined) {
546
+ if (typeof parameters == 'object') {
547
+ var q = parameters as Record<P, string | number>;
548
+ for (const paramName of Object.getOwnPropertyNames(q)) {
549
+ uriToUse += `/${paramName}/${q[paramName]}`;
550
+ }
551
+
552
+ //uriToUse += `/${q[((p : Record<string,number|number>) => `${p[]}/${p.paramValue}` ).join('/')}`;
553
+ } else if (typeof parameters == 'number' || typeof parameters == 'string') {
554
+ uriToUse += `/${parameters}`;
555
+ }
556
+ }
557
+ this.logger.info(`${node.name}: sending ${command} command: ${uriToUse}`);
558
+ return this.sendRequest(uriToUse);
559
+ }
560
+
561
+ public async sendRequest(
562
+ url: string,
563
+ options: {
564
+ parserOptions?: ParserOptions;
565
+ trailingSlash: boolean;
566
+ requestLogLevel?: any;
567
+ responseLogLevel?: any;
568
+ } = { trailingSlash: true }
569
+ ): Promise<any> {
570
+ const requestLogLevel = options.requestLogLevel ?? winston.config.cli.levels.debug;
571
+ const responseLogLevel = options.responseLogLevel ?? winston.config.cli.levels.silly;
572
+ url = `${this.protocol}://${this.address}/rest/${url}${options.trailingSlash ? '/' : ''}`;
573
+ this.logger.log(`Sending request: ${url}`, requestLogLevel);
574
+ try {
575
+ const response = await axios.get(url, {
576
+ auth: { username: this.credentials.username, password: this.credentials.password }
577
+ });
578
+ if (response.data) {
579
+ if (response.headers['content-type'].toString().includes('xml')) {
580
+ let curParser = parser;
581
+ if (options.parserOptions) curParser = new Parser({ ...defaultParserOptions, ...options.parserOptions });
582
+ var altParser = new XMLParser(defaultXMLParserOptions);
583
+ var s = altParser.parse(response.data);
584
+ this.logger.log(`Response: ${JSON.stringify(s)}`, requestLogLevel ?? 'debug');
585
+ return s;
586
+ } else if (response.headers['content-type'].toString().includes('json')) {
587
+ this.logger.log(`Response: ${JSON.stringify(response.data)}`, requestLogLevel ?? 'debug');
588
+ return JSON.parse(response.data);
589
+ } else {
590
+ this.logger.log(`Response Header: ${JSON.stringify(response.headers)} Response: ${JSON.stringify(response.data)}`, responseLogLevel ?? 'debug');
591
+ return response.data;
592
+ }
593
+ }
594
+ } catch (error) {
595
+ this.logger.error(`Error sending request to ISY: ${error?.message}`);
596
+ throw new Error(`Error sending request to ISY: ${JSON.stringify(error)}`);
597
+ }
598
+ }
599
+
600
+ public async sendSetVariable(id: any, type: any, value: any, handleResult: { (success: any): void; (arg0: boolean): void; (arg0: boolean): void }) {
601
+ const uriToUse = `/rest/vars/set/${type}/${id}/${value}`;
602
+ this.logger.info(`Sending ISY command...${uriToUse}`);
603
+
604
+ return this.sendRequest(uriToUse);
605
+ }
606
+
607
+ public variableChangedHandler(variable: { id: string; type: string }) {
608
+ this.logger.info(`Variable: ${variable.id} (${variable.type}) changed`);
609
+ }
610
+
611
+ // #endregion Public Methods (22)
612
+
613
+ // #region Private Methods (11)
614
+
615
+ #checkForFailure(response: any): boolean {
616
+ return response === null || response instanceof Error || (response.RestResponse !== undefined && response.RestResponse.status !== 200);
617
+ }
618
+
619
+ #createVariableKey(type: VariableType, id: number) {
620
+ return `${type}:${id}`;
621
+ }
622
+
623
+ #createVariables(type: VariableType, result: any) {
624
+ for (const variable of result.CList.e) {
625
+ const id = Number(variable.id);
626
+ const name = variable.name;
627
+ const newVariable = new ISYVariable(this, id, name, type);
628
+ this.variableList.set(this.#createVariableKey(type, id), newVariable);
629
+ }
630
+ }
631
+
632
+ #finishInitialize(success: boolean) {
633
+ if (!this.nodesLoaded) {
634
+ this.nodesLoaded = true;
635
+ //initializeCompleted();
636
+ if (success) {
637
+ // if (this.elkEnabled) {
638
+ // this.deviceList[this.elkAlarmPanel.address] = this.elkAlarmPanel;
639
+ // }
640
+ if (this.enableWebSocket) {
641
+ this.guardianTimer = setInterval(this.#guardian.bind(this), 60000);
642
+ this.initializeWebSocket();
643
+ }
644
+ }
645
+ }
646
+ }
647
+
648
+ #guardian() {
649
+ const timeNow = new Date();
650
+
651
+ if (Number(timeNow) - Number(this.lastActivity) > 60000) {
652
+ this.logger.info('Guardian: Detected no activity in more then 60 seconds. Reinitializing web sockets');
653
+ this.initializeWebSocket();
654
+ }
655
+ }
656
+
657
+ #loadElkInitialStatus(result: any) {
658
+ const p = new Parser({
659
+ explicitArray: false,
660
+ mergeAttrs: true
661
+ });
662
+ p.parseString(result, (err: any, res: { ae: any; ze: any }) => {
663
+ if (err) {
664
+ throw err;
665
+ }
666
+
667
+ for (const nodes of res.ae) {
668
+ this.elkAlarmPanel.setFromAreaUpdate(nodes);
669
+ }
670
+ for (const nodes of res.ze) {
671
+ const id = nodes.zone;
672
+ const zoneDevice = this.zoneMap[id];
673
+ if (zoneDevice !== null) {
674
+ zoneDevice.setFromZoneUpdate(nodes);
675
+ if (this.deviceList[zoneDevice.address] === null && zoneDevice.isPresent()) {
676
+ this.deviceList[zoneDevice.address] = zoneDevice;
677
+ // this.deviceIndex[zoneDevice.address] = zoneDevice;
678
+ }
679
+ }
680
+ }
681
+ });
682
+ }
683
+
684
+ #loadElkNodes(result: any) {
685
+ const document = new XmlDocument(result);
686
+ const nodes = document.childNamed('areas').childNamed('area').childrenNamed('zone');
687
+ for (let index = 0; index < nodes.length; index++) {
688
+ const id = nodes[index].attr.id;
689
+ const name = nodes[index].attr.name;
690
+ const alarmDef = nodes[index].attr.alarmDef;
691
+ const newDevice = new ElkAlarmSensorDevice(this, name, 1, id /*TODO: Handle CO Sensor vs. Door/Window Sensor */);
692
+ this.zoneMap[newDevice.zone] = newDevice;
693
+ }
694
+ }
695
+
696
+ async #readDeviceNodes(obj: { nodes: { node: NodeInfo[] } }) {
697
+ this.logger.info('Loading Device Nodes');
698
+
699
+ for (const nodeInfo of obj.nodes.node) {
700
+ try {
701
+ this.logger.debug(`Loading Device Node: ${JSON.stringify(nodeInfo)}`);
702
+ if (!this.deviceMap.has(nodeInfo.pnode)) {
703
+ const address = nodeInfo.address;
704
+ this.deviceMap[nodeInfo.pnode] = {
705
+ address
706
+ };
707
+ } else {
708
+ this.deviceMap[nodeInfo.pnode].push(nodeInfo.address);
709
+ }
710
+ let newDevice: ISYDeviceNode<any, any, any, any> = null;
711
+
712
+ // let deviceTypeInfo = this.isyTypeToTypeName(device.type, device.address);
713
+ // this.logger.info(JSON.stringify(deviceTypeInfo));
714
+
715
+ const enabled = nodeInfo.enabled ?? true;
716
+ const d = await NodeFactory.get(nodeInfo);
717
+ const m = DeviceFactory.getDeviceDetails(nodeInfo);
718
+
719
+ if (d) {
720
+ newDevice = new d(this, nodeInfo) as ISYDeviceNode<any, any, any, any>;
721
+ }
722
+ if (m) {
723
+ newDevice = newDevice ?? (new m.class(this, nodeInfo) as unknown as ISYDeviceNode<any, any, any, any>);
724
+ newDevice.productName = m.name;
725
+ newDevice.model = `(${m.modelNumber}) ${m.name} v.${m.version}`;
726
+ newDevice.modelNumber = m.modelNumber;
727
+ newDevice.version = m.version;
728
+ }
729
+ if (enabled) {
730
+ if (newDevice === null) {
731
+ this.logger.warn(`Device type resolution failed for ${nodeInfo.name} with type: ${nodeInfo.type} and nodedef: ${nodeInfo.nodeDefId}`);
732
+ newDevice = new ISYDeviceNode<any, any, any, any>(this, nodeInfo);
733
+ } else if (newDevice !== null) {
734
+ if (m.unsupported) {
735
+ this.logger.warn('Device not currently supported: ' + JSON.stringify(nodeInfo) + ' /n It has been mapped to: ' + d.name);
736
+ }
737
+ try {
738
+ await newDevice.refreshNotes();
739
+ } catch (e) {
740
+ this.logger.debug('No notes found.');
741
+ }
742
+
743
+ // if (!newDevice.hidden) {
744
+ // }
745
+
746
+ // this.deviceList.push(newDevice);
747
+ } else {
748
+ }
749
+ this.nodeMap.set(newDevice.address, newDevice);
750
+ } else {
751
+ this.logger.info(`Ignoring disabled device: ${nodeInfo.name}`);
752
+ }
753
+ } catch (e) {
754
+ this.logger.error(`Error loading device node: ${e.message}`);
755
+ }
756
+ }
757
+
758
+ this.logger.info(`${this.nodeMap.size} devices added.`);
759
+ }
760
+
761
+ async #readFolderNodes(result: { nodes: { folder: any } }) {
762
+ this.logger.info('Loading Folder Nodes');
763
+ if (result?.nodes?.folder) {
764
+ for (const folder of result.nodes.folder) {
765
+ this.logger.info(`Loading Folder Node: ${JSON.stringify(folder)}`);
766
+ this.folderMap.set(folder.address, folder.name);
767
+ }
768
+ }
769
+ }
770
+
771
+ async #readSceneNodes(result: { nodes: { group: any } }) {
772
+ this.logger.info('Loading Scene Nodes');
773
+ for (const scene of result.nodes?.group) {
774
+ if (scene.name === 'ISY' || scene.name === 'IoX' || scene.name === 'Auto DR') {
775
+ continue;
776
+ } // Skip ISY & Auto DR Scenes
777
+
778
+ const newScene = new ISYScene(this, scene);
779
+ try {
780
+ await newScene.refreshNotes();
781
+ } catch (e) {
782
+ this.logger.debug('No notes found.');
783
+ }
784
+ this.sceneList.set(newScene.address, newScene);
785
+ }
786
+ }
787
+
788
+ #setVariableValues(result: any) {
789
+ for (const vals of result.vars.var) {
790
+ const type = Number(vals.type) as VariableType;
791
+ const id = Number(vals.id);
792
+ const variable = this.getVariable(type, id);
793
+ if (variable) {
794
+ variable.init = vals.init;
795
+ variable.value = vals.val;
796
+ variable.lastChanged = new Date(vals.ts);
797
+ }
798
+ }
799
+ }
800
+
801
+ [Symbol.dispose](): void {
802
+ try {
803
+ this.webSocket.close();
804
+ } catch (e) {
805
+ this.logger.error(`Error closing websocket: ${e.message}`);
806
+ }
807
+ }
808
+
809
+ // #endregion Private Methods (11)
840
810
  }