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