homebridge-adt-pulse 2.2.0 → 3.0.0-beta.2
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/LICENSE +1 -1
- package/README.md +13 -13
- package/package.json +38 -17
- package/src/index.ts +18 -0
- package/src/lib/accessory.ts +405 -0
- package/src/lib/api.ts +3483 -0
- package/src/lib/detect.ts +728 -0
- package/src/lib/platform.ts +890 -0
- package/src/lib/regex.ts +167 -0
- package/src/lib/schema.ts +34 -0
- package/src/lib/utility.ts +933 -0
- package/src/scripts/repl.ts +300 -0
- package/src/scripts/test-api.ts +278 -0
- package/src/types/constant.d.ts +308 -0
- package/src/types/index.d.ts +1472 -0
- package/src/types/shared.d.ts +517 -0
- package/api-test.js +0 -280
- package/api.js +0 -878
- package/index.js +0 -1312
package/index.js
DELETED
|
@@ -1,1312 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ADT Pulse Homebridge Plugin.
|
|
3
|
-
*
|
|
4
|
-
* @since 1.0.0
|
|
5
|
-
*/
|
|
6
|
-
const _ = require('lodash');
|
|
7
|
-
|
|
8
|
-
const packageJson = require('./package.json');
|
|
9
|
-
const Pulse = require('./api');
|
|
10
|
-
|
|
11
|
-
let Accessory;
|
|
12
|
-
let Service;
|
|
13
|
-
let Characteristic;
|
|
14
|
-
let UUIDGen;
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Platform constructor.
|
|
18
|
-
*
|
|
19
|
-
* @param {Logger} log - Homebridge log function.
|
|
20
|
-
* @param {object} config - Platform plugin configuration from "config.json".
|
|
21
|
-
* @param {object} api - Homebridge API. Null for older versions.
|
|
22
|
-
*
|
|
23
|
-
* @constructor
|
|
24
|
-
*
|
|
25
|
-
* @since 1.0.0
|
|
26
|
-
*/
|
|
27
|
-
function ADTPulsePlatform(log, config, api) {
|
|
28
|
-
this.log = log;
|
|
29
|
-
this.config = config;
|
|
30
|
-
|
|
31
|
-
// Where the security panel and sensors are held.
|
|
32
|
-
this.accessories = [];
|
|
33
|
-
this.deviceStatus = {};
|
|
34
|
-
this.zoneStatus = {};
|
|
35
|
-
|
|
36
|
-
// Keeps track of failed times.
|
|
37
|
-
this.failedLoginTimes = 0;
|
|
38
|
-
this.stalledSyncTimes = 0;
|
|
39
|
-
|
|
40
|
-
// Keeps track of device updates.
|
|
41
|
-
this.lastSyncCode = '1-0-0';
|
|
42
|
-
this.portalSyncSession = {};
|
|
43
|
-
this.isSyncing = false;
|
|
44
|
-
|
|
45
|
-
// Session data.
|
|
46
|
-
this.sessionVersion = '';
|
|
47
|
-
|
|
48
|
-
// These variables could be undefined.
|
|
49
|
-
this.username = _.get(this.config, 'username');
|
|
50
|
-
this.password = _.get(this.config, 'password');
|
|
51
|
-
this.fingerprint = _.get(this.config, 'fingerprint');
|
|
52
|
-
this.overrideSensors = _.get(this.config, 'overrideSensors');
|
|
53
|
-
this.country = _.get(this.config, 'country');
|
|
54
|
-
this.logLevel = _.get(this.config, 'logLevel');
|
|
55
|
-
this.logActivity = _.get(this.config, 'logActivity');
|
|
56
|
-
this.removeObsoleteZones = _.get(this.config, 'removeObsoleteZones');
|
|
57
|
-
this.pausePlugin = _.get(this.config, 'pausePlugin');
|
|
58
|
-
this.resetAll = _.get(this.config, 'resetAll');
|
|
59
|
-
|
|
60
|
-
// Timers.
|
|
61
|
-
this.syncInterval = 3; // 3 seconds.
|
|
62
|
-
this.syncIntervalDelay = 600; // 10 minutes.
|
|
63
|
-
this.setDeviceTimeout = 6; // 6 seconds.
|
|
64
|
-
|
|
65
|
-
// Tested builds.
|
|
66
|
-
this.testedBuilds = ['25.0.0-21', '26.0.0-32'];
|
|
67
|
-
|
|
68
|
-
// Setup logging function.
|
|
69
|
-
if (typeof this.logLevel !== 'number' || ![10, 20, 30, 40, 50].includes(this.logLevel)) {
|
|
70
|
-
if (this.logLevel !== undefined) {
|
|
71
|
-
this.log.warn('"logLevel" should be a specific number (10, 20, 30, 40, or 50). Defaulting to 30.');
|
|
72
|
-
}
|
|
73
|
-
this.logLevel = 30;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// Check for credentials.
|
|
77
|
-
if (!this.username || !this.password) {
|
|
78
|
-
this.logMessage('Missing required username or password in configuration.', 10);
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Check if override sensors is configured incorrectly.
|
|
83
|
-
if (
|
|
84
|
-
!_.isArray(this.overrideSensors)
|
|
85
|
-
|| !_.every(this.overrideSensors, (sensor) => _.isString(_.get(sensor, 'name')) && _.isString(_.get(sensor, 'type')))
|
|
86
|
-
) {
|
|
87
|
-
if (this.overrideSensors !== undefined) {
|
|
88
|
-
this.logMessage('"overrideSensors" setting is incorrectly defined. Defaulting to [].', 20);
|
|
89
|
-
}
|
|
90
|
-
this.overrideSensors = [];
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Setup country configuration.
|
|
94
|
-
if (!['us', 'ca'].includes(this.country)) {
|
|
95
|
-
if (this.country !== undefined) {
|
|
96
|
-
this.logMessage('"country" setting should be "us" or "ca". Defaulting to "us".', 20);
|
|
97
|
-
}
|
|
98
|
-
this.country = 'us';
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// Check if log activity is configured.
|
|
102
|
-
if (typeof this.logActivity !== 'boolean') {
|
|
103
|
-
if (this.logActivity !== undefined) {
|
|
104
|
-
this.logMessage('"logActivity" setting should be true or false. Defaulting to true.', 20);
|
|
105
|
-
}
|
|
106
|
-
this.logActivity = true;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// Check if obsolete zone removal is configured.
|
|
110
|
-
if (typeof this.removeObsoleteZones !== 'boolean') {
|
|
111
|
-
if (this.removeObsoleteZones !== undefined) {
|
|
112
|
-
this.logMessage('"removeObsoleteZones" setting should be true or false. Defaulting to true.', 20);
|
|
113
|
-
}
|
|
114
|
-
this.removeObsoleteZones = true;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// Prevent accidental pausing.
|
|
118
|
-
if (typeof this.pausePlugin !== 'boolean') {
|
|
119
|
-
if (this.pausePlugin !== undefined) {
|
|
120
|
-
this.logMessage('"pausePlugin" setting should be true or false. Defaulting to false.', 20);
|
|
121
|
-
}
|
|
122
|
-
this.pausePlugin = false;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// Prevent accidental reset.
|
|
126
|
-
if (typeof this.resetAll !== 'boolean') {
|
|
127
|
-
if (this.resetAll !== undefined) {
|
|
128
|
-
this.logMessage('"resetAll" setting should be true or false. Defaulting to false.', 20);
|
|
129
|
-
}
|
|
130
|
-
this.resetAll = false;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// Initialize main script.
|
|
134
|
-
this.pulse = new Pulse({
|
|
135
|
-
username: this.username,
|
|
136
|
-
password: this.password,
|
|
137
|
-
fingerprint: this.fingerprint,
|
|
138
|
-
overrideSensors: this.overrideSensors,
|
|
139
|
-
country: this.country,
|
|
140
|
-
debug: (this.logLevel >= 40),
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
if (api) {
|
|
144
|
-
// Register new accessories via this object.
|
|
145
|
-
this.api = api;
|
|
146
|
-
|
|
147
|
-
this.api.on('didFinishLaunching', () => {
|
|
148
|
-
this.logSystemInformation({
|
|
149
|
-
platform: _.get(process, 'platform'),
|
|
150
|
-
arch: _.get(process, 'arch'),
|
|
151
|
-
pluginVer: _.get(packageJson, 'version'),
|
|
152
|
-
nodeVer: _.get(process, 'versions.node'),
|
|
153
|
-
homebridgeVer: _.get(api, 'serverVersion'),
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
if (this.pausePlugin) {
|
|
157
|
-
this.logMessage('ADT Pulse plugin is now paused...', 20);
|
|
158
|
-
} else if (this.resetAll) {
|
|
159
|
-
this.logMessage('Removing all ADT Pulse accessories from Homebridge...', 20);
|
|
160
|
-
|
|
161
|
-
_.forEachRight(this.accessories, (accessory) => {
|
|
162
|
-
this.removeAccessory(accessory);
|
|
163
|
-
});
|
|
164
|
-
} else {
|
|
165
|
-
this.portalSync();
|
|
166
|
-
}
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* Restore cached accessories.
|
|
173
|
-
*
|
|
174
|
-
* @param {object} accessory - The accessory.
|
|
175
|
-
*
|
|
176
|
-
* @since 1.0.0
|
|
177
|
-
*/
|
|
178
|
-
ADTPulsePlatform.prototype.configureAccessory = function configureAccessory(accessory) {
|
|
179
|
-
const that = this;
|
|
180
|
-
|
|
181
|
-
const id = _.get(accessory, 'context.id');
|
|
182
|
-
const name = _.get(accessory, 'displayName');
|
|
183
|
-
const type = _.get(accessory, 'context.type');
|
|
184
|
-
|
|
185
|
-
this.logMessage(`Configuring cached accessory... ${name} (${id})`, 30);
|
|
186
|
-
this.logMessage(accessory, 40);
|
|
187
|
-
|
|
188
|
-
// When "Identify Accessory" is tapped.
|
|
189
|
-
accessory.on('identify', (paired, callback) => {
|
|
190
|
-
this.logMessage(`Identifying cached accessory... ${name} (${id})`, 30);
|
|
191
|
-
callback(null, paired);
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
switch (type) {
|
|
195
|
-
case 'system':
|
|
196
|
-
accessory
|
|
197
|
-
.getService(Service.SecuritySystem)
|
|
198
|
-
.getCharacteristic(Characteristic.SecuritySystemTargetState)
|
|
199
|
-
.on('get', (callback) => this.getDeviceAccessory('target', id, name, callback))
|
|
200
|
-
.on('set', (state, callback) => this.setDeviceAccessory(id, name, state, callback));
|
|
201
|
-
|
|
202
|
-
accessory
|
|
203
|
-
.getService(Service.SecuritySystem)
|
|
204
|
-
.getCharacteristic(Characteristic.SecuritySystemCurrentState)
|
|
205
|
-
.on('get', (callback) => this.getDeviceAccessory('current', id, name, callback));
|
|
206
|
-
break;
|
|
207
|
-
case 'doorWindow':
|
|
208
|
-
accessory
|
|
209
|
-
.getService(Service.ContactSensor)
|
|
210
|
-
.getCharacteristic(Characteristic.ContactSensorState)
|
|
211
|
-
.on('get', (callback) => this.getZoneAccessory(type, id, name, callback));
|
|
212
|
-
break;
|
|
213
|
-
case 'glass':
|
|
214
|
-
accessory
|
|
215
|
-
.getService(Service.OccupancySensor)
|
|
216
|
-
.getCharacteristic(Characteristic.OccupancyDetected)
|
|
217
|
-
.on('get', (callback) => this.getZoneAccessory(type, id, name, callback));
|
|
218
|
-
break;
|
|
219
|
-
case 'motion':
|
|
220
|
-
accessory
|
|
221
|
-
.getService(Service.MotionSensor)
|
|
222
|
-
.getCharacteristic(Characteristic.MotionDetected)
|
|
223
|
-
.on('get', (callback) => this.getZoneAccessory(type, id, name, callback));
|
|
224
|
-
break;
|
|
225
|
-
case 'co':
|
|
226
|
-
accessory
|
|
227
|
-
.getService(Service.CarbonMonoxideSensor)
|
|
228
|
-
.getCharacteristic(Characteristic.CarbonMonoxideDetected)
|
|
229
|
-
.on('get', (callback) => this.getZoneAccessory(type, id, name, callback));
|
|
230
|
-
break;
|
|
231
|
-
case 'fire':
|
|
232
|
-
accessory
|
|
233
|
-
.getService(Service.SmokeSensor)
|
|
234
|
-
.getCharacteristic(Characteristic.SmokeDetected)
|
|
235
|
-
.on('get', (callback) => this.getZoneAccessory(type, id, name, callback));
|
|
236
|
-
break;
|
|
237
|
-
default:
|
|
238
|
-
this.logMessage(`Failed to configure invalid or unsupported accessory... ${type}`, 10);
|
|
239
|
-
break;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
that.accessories.push(accessory);
|
|
243
|
-
};
|
|
244
|
-
|
|
245
|
-
/**
|
|
246
|
-
* Add accessory.
|
|
247
|
-
*
|
|
248
|
-
* @param {string} type - Can be "system", "doorWindow", "glass", "motion", "co", or "fire".
|
|
249
|
-
* @param {string} id - The accessory unique identification code.
|
|
250
|
-
* @param {string} name - The name of the accessory.
|
|
251
|
-
* @param {string} make - The manufacturer of the accessory.
|
|
252
|
-
* @param {string} model - The model of the accessory.
|
|
253
|
-
*
|
|
254
|
-
* @since 1.0.0
|
|
255
|
-
*/
|
|
256
|
-
ADTPulsePlatform.prototype.addAccessory = function addAccessory(type, id, name, make, model) {
|
|
257
|
-
const that = this;
|
|
258
|
-
|
|
259
|
-
const uuid = UUIDGen.generate(id);
|
|
260
|
-
const accessory = new Accessory(name, uuid);
|
|
261
|
-
const accessoryLoaded = _.find(that.accessories, ['UUID', uuid]);
|
|
262
|
-
|
|
263
|
-
// Add new accessories only.
|
|
264
|
-
if (accessoryLoaded === undefined) {
|
|
265
|
-
this.logMessage(`Adding accessory... ${name} (${id})`, 30);
|
|
266
|
-
|
|
267
|
-
let validAccessory = true;
|
|
268
|
-
|
|
269
|
-
switch (type) {
|
|
270
|
-
case 'system':
|
|
271
|
-
accessory.addService(Service.SecuritySystem, name);
|
|
272
|
-
|
|
273
|
-
accessory
|
|
274
|
-
.getService(Service.SecuritySystem)
|
|
275
|
-
.getCharacteristic(Characteristic.SecuritySystemTargetState)
|
|
276
|
-
.on('get', (callback) => this.getDeviceAccessory('target', id, name, callback))
|
|
277
|
-
.on('set', (state, callback) => this.setDeviceAccessory(id, name, state, callback));
|
|
278
|
-
|
|
279
|
-
accessory
|
|
280
|
-
.getService(Service.SecuritySystem)
|
|
281
|
-
.getCharacteristic(Characteristic.SecuritySystemCurrentState)
|
|
282
|
-
.on('get', (callback) => this.getDeviceAccessory('current', id, name, callback));
|
|
283
|
-
break;
|
|
284
|
-
case 'doorWindow':
|
|
285
|
-
accessory
|
|
286
|
-
.addService(Service.ContactSensor, name)
|
|
287
|
-
.getCharacteristic(Characteristic.ContactSensorState)
|
|
288
|
-
.on('get', (callback) => this.getZoneAccessory(type, id, name, callback));
|
|
289
|
-
break;
|
|
290
|
-
case 'glass':
|
|
291
|
-
accessory
|
|
292
|
-
.addService(Service.OccupancySensor, name)
|
|
293
|
-
.getCharacteristic(Characteristic.OccupancyDetected)
|
|
294
|
-
.on('get', (callback) => this.getZoneAccessory(type, id, name, callback));
|
|
295
|
-
break;
|
|
296
|
-
case 'motion':
|
|
297
|
-
accessory
|
|
298
|
-
.addService(Service.MotionSensor, name)
|
|
299
|
-
.getCharacteristic(Characteristic.MotionDetected)
|
|
300
|
-
.on('get', (callback) => this.getZoneAccessory(type, id, name, callback));
|
|
301
|
-
break;
|
|
302
|
-
case 'co':
|
|
303
|
-
accessory
|
|
304
|
-
.addService(Service.CarbonMonoxideSensor, name)
|
|
305
|
-
.getCharacteristic(Characteristic.CarbonMonoxideDetected)
|
|
306
|
-
.on('get', (callback) => this.getZoneAccessory(type, id, name, callback));
|
|
307
|
-
break;
|
|
308
|
-
case 'fire':
|
|
309
|
-
accessory
|
|
310
|
-
.addService(Service.SmokeSensor, name)
|
|
311
|
-
.getCharacteristic(Characteristic.SmokeDetected)
|
|
312
|
-
.on('get', (callback) => this.getZoneAccessory(type, id, name, callback));
|
|
313
|
-
break;
|
|
314
|
-
default:
|
|
315
|
-
validAccessory = false;
|
|
316
|
-
break;
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
if (validAccessory) {
|
|
320
|
-
// Set accessory context.
|
|
321
|
-
_.set(accessory, 'context.id', id);
|
|
322
|
-
_.set(accessory, 'context.type', type);
|
|
323
|
-
|
|
324
|
-
// Set accessory information.
|
|
325
|
-
accessory
|
|
326
|
-
.getService(Service.AccessoryInformation)
|
|
327
|
-
.setCharacteristic(Characteristic.Manufacturer, make)
|
|
328
|
-
.setCharacteristic(Characteristic.SerialNumber, id)
|
|
329
|
-
.setCharacteristic(Characteristic.Model, model)
|
|
330
|
-
.setCharacteristic(Characteristic.FirmwareRevision, '1.0');
|
|
331
|
-
|
|
332
|
-
// When "Identify Accessory" is tapped.
|
|
333
|
-
accessory.on('identify', (paired, callback) => {
|
|
334
|
-
this.logMessage(`Identifying new accessory... ${accessory.displayName} (${id})`, 30);
|
|
335
|
-
callback(null, paired);
|
|
336
|
-
});
|
|
337
|
-
|
|
338
|
-
// Make accessory active.
|
|
339
|
-
that.accessories.push(accessory);
|
|
340
|
-
|
|
341
|
-
// Save accessory to database.
|
|
342
|
-
that.api.registerPlatformAccessories(
|
|
343
|
-
'homebridge-adt-pulse',
|
|
344
|
-
'ADTPulse',
|
|
345
|
-
[accessory],
|
|
346
|
-
);
|
|
347
|
-
} else {
|
|
348
|
-
this.logMessage(`Failed to register invalid or unsupported accessory... ${type}`, 10);
|
|
349
|
-
}
|
|
350
|
-
} else {
|
|
351
|
-
this.logMessage(`Skipping duplicate accessory... ${uuid}`, 20);
|
|
352
|
-
}
|
|
353
|
-
};
|
|
354
|
-
|
|
355
|
-
/**
|
|
356
|
-
* Prepare to add accessory.
|
|
357
|
-
*
|
|
358
|
-
* @param {string} type - Could either be "device" or "zone".
|
|
359
|
-
* @param {object} accessory - The accessory.
|
|
360
|
-
*
|
|
361
|
-
* @since 1.0.0
|
|
362
|
-
*/
|
|
363
|
-
ADTPulsePlatform.prototype.prepareAddAccessory = function prepareAddAccessory(type, accessory) {
|
|
364
|
-
const that = this;
|
|
365
|
-
|
|
366
|
-
if (type === 'device') {
|
|
367
|
-
const deviceName = _.get(accessory, 'name', '').replace(/[()]/gi, '');
|
|
368
|
-
const deviceMake = _.get(accessory, 'make');
|
|
369
|
-
const deviceType = _.get(accessory, 'type');
|
|
370
|
-
|
|
371
|
-
const deviceKind = 'system';
|
|
372
|
-
const deviceModel = deviceType.substr(deviceType.indexOf('-') + 2);
|
|
373
|
-
|
|
374
|
-
const deviceId = 'system-1';
|
|
375
|
-
const deviceUUID = UUIDGen.generate(deviceId);
|
|
376
|
-
const deviceLoaded = _.find(that.accessories, ['UUID', deviceUUID]);
|
|
377
|
-
|
|
378
|
-
this.logMessage(`Preparing to add device (${deviceId}) accessory...`, 30);
|
|
379
|
-
this.logMessage(accessory, 40);
|
|
380
|
-
|
|
381
|
-
if (deviceLoaded === undefined) {
|
|
382
|
-
this.addAccessory(
|
|
383
|
-
deviceKind,
|
|
384
|
-
deviceId,
|
|
385
|
-
deviceName,
|
|
386
|
-
deviceMake,
|
|
387
|
-
deviceModel,
|
|
388
|
-
);
|
|
389
|
-
}
|
|
390
|
-
} else if (type === 'zone') {
|
|
391
|
-
const zoneId = _.get(accessory, 'id');
|
|
392
|
-
const zoneName = _.get(accessory, 'name', '').replace(/[()]/gi, '');
|
|
393
|
-
const zoneTags = _.get(accessory, 'tags');
|
|
394
|
-
|
|
395
|
-
const zoneMake = 'ADT';
|
|
396
|
-
const zoneKind = zoneTags.substr(zoneTags.indexOf(',') + 1);
|
|
397
|
-
|
|
398
|
-
const zoneUUID = UUIDGen.generate(zoneId);
|
|
399
|
-
const zoneLoaded = _.find(that.accessories, ['UUID', zoneUUID]);
|
|
400
|
-
|
|
401
|
-
let zoneModel;
|
|
402
|
-
|
|
403
|
-
switch (zoneKind) {
|
|
404
|
-
case 'doorWindow':
|
|
405
|
-
zoneModel = 'Door/Window Sensor';
|
|
406
|
-
break;
|
|
407
|
-
case 'glass':
|
|
408
|
-
zoneModel = 'Glass Break Detector';
|
|
409
|
-
break;
|
|
410
|
-
case 'motion':
|
|
411
|
-
zoneModel = 'Motion Sensor';
|
|
412
|
-
break;
|
|
413
|
-
case 'co':
|
|
414
|
-
zoneModel = 'Carbon Monoxide Detector';
|
|
415
|
-
break;
|
|
416
|
-
case 'fire':
|
|
417
|
-
zoneModel = 'Fire (Smoke/Heat) Detector';
|
|
418
|
-
break;
|
|
419
|
-
default:
|
|
420
|
-
zoneModel = 'Unknown';
|
|
421
|
-
break;
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
this.logMessage(`Preparing to add zone (${zoneId}) accessory...`, 30);
|
|
425
|
-
this.logMessage(accessory, 40);
|
|
426
|
-
|
|
427
|
-
if (zoneLoaded === undefined) {
|
|
428
|
-
this.addAccessory(
|
|
429
|
-
zoneKind,
|
|
430
|
-
zoneId,
|
|
431
|
-
zoneName,
|
|
432
|
-
zoneMake,
|
|
433
|
-
zoneModel,
|
|
434
|
-
);
|
|
435
|
-
}
|
|
436
|
-
} else {
|
|
437
|
-
this.logMessage(`Skipping unknown accessory... ${type}`, 10);
|
|
438
|
-
this.logMessage(accessory, 40);
|
|
439
|
-
}
|
|
440
|
-
};
|
|
441
|
-
|
|
442
|
-
/**
|
|
443
|
-
* Remove accessory.
|
|
444
|
-
*
|
|
445
|
-
* @param {object} accessory - The accessory.
|
|
446
|
-
*
|
|
447
|
-
* @since 1.0.0
|
|
448
|
-
*/
|
|
449
|
-
ADTPulsePlatform.prototype.removeAccessory = function removeAccessory(accessory) {
|
|
450
|
-
const that = this;
|
|
451
|
-
|
|
452
|
-
if (_.get(accessory, 'UUID') === undefined) {
|
|
453
|
-
this.logMessage(`Failed to remove invalid accessory... ${accessory}`, 10);
|
|
454
|
-
return;
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
const id = _.get(accessory, 'context.id');
|
|
458
|
-
const name = _.get(accessory, 'displayName');
|
|
459
|
-
|
|
460
|
-
this.logMessage(`Removing accessory... ${name} (${id})`, 30);
|
|
461
|
-
this.logMessage(accessory, 40);
|
|
462
|
-
|
|
463
|
-
// Remove from accessory array.
|
|
464
|
-
_.remove(that.accessories, (thatAccessory) => thatAccessory.UUID === accessory.UUID);
|
|
465
|
-
|
|
466
|
-
that.api.unregisterPlatformAccessories(
|
|
467
|
-
'homebridge-adt-pulse',
|
|
468
|
-
'ADTPulse',
|
|
469
|
-
[accessory],
|
|
470
|
-
);
|
|
471
|
-
};
|
|
472
|
-
|
|
473
|
-
/**
|
|
474
|
-
* Get device accessory.
|
|
475
|
-
*
|
|
476
|
-
* @param {string} type - Can be "target" or "current".
|
|
477
|
-
* @param {string} id - The accessory unique identification code.
|
|
478
|
-
* @param {string} name - The name of the accessory.
|
|
479
|
-
* @param {function} callback - Homebridge callback function.
|
|
480
|
-
*
|
|
481
|
-
* @since 1.0.0
|
|
482
|
-
*/
|
|
483
|
-
ADTPulsePlatform.prototype.getDeviceAccessory = function getDeviceAccessory(type, id, name, callback) {
|
|
484
|
-
const status = this.getDeviceStatus(type, true);
|
|
485
|
-
|
|
486
|
-
let error = false;
|
|
487
|
-
|
|
488
|
-
if (status === undefined) {
|
|
489
|
-
error = true;
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
this.logMessage(`Getting ${name} (${id}) ${type} status... ${status}`, 50);
|
|
493
|
-
|
|
494
|
-
callback(error, status);
|
|
495
|
-
};
|
|
496
|
-
|
|
497
|
-
/**
|
|
498
|
-
* Set device accessory.
|
|
499
|
-
*
|
|
500
|
-
* @param {string} id - The accessory unique identification code.
|
|
501
|
-
* @param {string} name - The name of the accessory.
|
|
502
|
-
* @param {number} state - The state that accessory is being set to.
|
|
503
|
-
* @param {function} callback - Homebridge callback function.
|
|
504
|
-
*
|
|
505
|
-
* @since 1.0.0
|
|
506
|
-
*/
|
|
507
|
-
ADTPulsePlatform.prototype.setDeviceAccessory = function setDeviceAccessory(id, name, state, callback) {
|
|
508
|
-
const that = this;
|
|
509
|
-
|
|
510
|
-
this.setDeviceStatus(id, name, state);
|
|
511
|
-
|
|
512
|
-
setTimeout(() => {
|
|
513
|
-
callback(null);
|
|
514
|
-
}, that.setDeviceTimeout * 1000);
|
|
515
|
-
};
|
|
516
|
-
|
|
517
|
-
/**
|
|
518
|
-
* Get zone accessory.
|
|
519
|
-
*
|
|
520
|
-
* @param {string} type - Can be "doorWindow", "glass", "motion", "co", or "fire".
|
|
521
|
-
* @param {string} id - The accessory unique identification code.
|
|
522
|
-
* @param {string} name - The name of the accessory.
|
|
523
|
-
* @param {function} callback - Homebridge callback function.
|
|
524
|
-
*
|
|
525
|
-
* @since 1.0.0
|
|
526
|
-
*/
|
|
527
|
-
ADTPulsePlatform.prototype.getZoneAccessory = function getZoneAccessory(type, id, name, callback) {
|
|
528
|
-
const status = this.getZoneStatus(type, id, true);
|
|
529
|
-
|
|
530
|
-
let error = false;
|
|
531
|
-
|
|
532
|
-
if (status === undefined) {
|
|
533
|
-
error = true;
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
this.logMessage(`Getting ${name} (${id}) status... ${status}`, 50);
|
|
537
|
-
|
|
538
|
-
callback(error, status);
|
|
539
|
-
};
|
|
540
|
-
|
|
541
|
-
/**
|
|
542
|
-
* Get device status.
|
|
543
|
-
*
|
|
544
|
-
* Returns the latest device state and status from "this.deviceStatus" array.
|
|
545
|
-
*
|
|
546
|
-
* @param {string} type - Can be "target" or "current".
|
|
547
|
-
* @param {boolean} format - Format device status to Homebridge.
|
|
548
|
-
*
|
|
549
|
-
* @returns {(undefined|string|number)}
|
|
550
|
-
*
|
|
551
|
-
* @since 1.0.0
|
|
552
|
-
*/
|
|
553
|
-
ADTPulsePlatform.prototype.getDeviceStatus = function getDeviceStatus(type, format) {
|
|
554
|
-
const device = this.deviceStatus;
|
|
555
|
-
const summary = _.get(device, 'summary');
|
|
556
|
-
const state = _.get(device, 'state');
|
|
557
|
-
const status = _.get(device, 'status');
|
|
558
|
-
|
|
559
|
-
if (typeof summary === 'string') {
|
|
560
|
-
if (format) {
|
|
561
|
-
return this.formatGetDeviceStatus(type, summary);
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
return `${state} / ${status}`.toLowerCase();
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
return undefined;
|
|
568
|
-
};
|
|
569
|
-
|
|
570
|
-
/**
|
|
571
|
-
* Format get device status.
|
|
572
|
-
*
|
|
573
|
-
* Converts the device state and status from ADT Pulse to Homebridge compatible.
|
|
574
|
-
*
|
|
575
|
-
* @param {string} type - Can be "target" or "current".
|
|
576
|
-
* @param {string} summary - The last known summary of the accessory.
|
|
577
|
-
*
|
|
578
|
-
* @returns {(undefined|number)}
|
|
579
|
-
*
|
|
580
|
-
* @since 1.0.0
|
|
581
|
-
*/
|
|
582
|
-
ADTPulsePlatform.prototype.formatGetDeviceStatus = function formatGetDeviceStatus(type, summary) {
|
|
583
|
-
const lowerCaseSummary = summary.toLowerCase();
|
|
584
|
-
const alarm = lowerCaseSummary.includes('alarm');
|
|
585
|
-
const unclearedAlarm = lowerCaseSummary.includes('uncleared alarm');
|
|
586
|
-
const disarmed = lowerCaseSummary.includes('disarmed');
|
|
587
|
-
const armAway = lowerCaseSummary.includes('armed away');
|
|
588
|
-
const armStay = lowerCaseSummary.includes('armed stay');
|
|
589
|
-
const armNight = lowerCaseSummary.includes('armed night');
|
|
590
|
-
|
|
591
|
-
let status;
|
|
592
|
-
|
|
593
|
-
if (alarm && !unclearedAlarm) {
|
|
594
|
-
if (type === 'current') {
|
|
595
|
-
status = Characteristic.SecuritySystemCurrentState.ALARM_TRIGGERED;
|
|
596
|
-
}
|
|
597
|
-
} else if (unclearedAlarm || disarmed) {
|
|
598
|
-
if (type === 'current') {
|
|
599
|
-
status = Characteristic.SecuritySystemCurrentState.DISARMED;
|
|
600
|
-
} else if (type === 'target') {
|
|
601
|
-
status = Characteristic.SecuritySystemTargetState.DISARM;
|
|
602
|
-
}
|
|
603
|
-
} else if (armAway) {
|
|
604
|
-
if (type === 'current') {
|
|
605
|
-
status = Characteristic.SecuritySystemCurrentState.AWAY_ARM;
|
|
606
|
-
} else if (type === 'target') {
|
|
607
|
-
status = Characteristic.SecuritySystemTargetState.AWAY_ARM;
|
|
608
|
-
}
|
|
609
|
-
} else if (armStay) {
|
|
610
|
-
if (type === 'current') {
|
|
611
|
-
status = Characteristic.SecuritySystemCurrentState.STAY_ARM;
|
|
612
|
-
} else if (type === 'target') {
|
|
613
|
-
status = Characteristic.SecuritySystemTargetState.STAY_ARM;
|
|
614
|
-
}
|
|
615
|
-
} else if (armNight) {
|
|
616
|
-
if (type === 'current') {
|
|
617
|
-
status = Characteristic.SecuritySystemCurrentState.NIGHT_ARM;
|
|
618
|
-
} else if (type === 'target') {
|
|
619
|
-
status = Characteristic.SecuritySystemTargetState.NIGHT_ARM;
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
return status;
|
|
624
|
-
};
|
|
625
|
-
|
|
626
|
-
/**
|
|
627
|
-
* Set device status.
|
|
628
|
-
*
|
|
629
|
-
* @param {string} id - The accessory unique identification code.
|
|
630
|
-
* @param {string} name - The name of the accessory.
|
|
631
|
-
* @param {number} arm - Defined status to change device to.
|
|
632
|
-
*
|
|
633
|
-
* @since 1.0.0
|
|
634
|
-
*/
|
|
635
|
-
ADTPulsePlatform.prototype.setDeviceStatus = function setDeviceStatus(id, name, arm) {
|
|
636
|
-
const that = this;
|
|
637
|
-
|
|
638
|
-
const latestState = this.getDeviceStatus('current', false);
|
|
639
|
-
const newArmState = this.formatSetDeviceStatus(arm, 'arm');
|
|
640
|
-
|
|
641
|
-
let oldArmState = this.formatSetDeviceStatus(latestState, 'armState');
|
|
642
|
-
|
|
643
|
-
this.logMessage(`Setting ${name} (${id}) status from ${oldArmState} to ${newArmState}...`, 30);
|
|
644
|
-
this.logMessage(`Latest state for ${name} (${id}) is ${latestState}...`, 40);
|
|
645
|
-
|
|
646
|
-
if (typeof latestState === 'string' && latestState.includes('status unavailable')) {
|
|
647
|
-
this.logMessage(`Unable to set ${name} (${id}) status. The ADT Pulse Gateway is offline.`, 10);
|
|
648
|
-
return;
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
if (!oldArmState) {
|
|
652
|
-
this.logMessage(`Unknown latestState context... ${latestState}`, 10);
|
|
653
|
-
return;
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
that.pulse
|
|
657
|
-
.login()
|
|
658
|
-
.then(async () => {
|
|
659
|
-
// Attempt to clear the alarms first.
|
|
660
|
-
if (latestState.includes('alarm')) {
|
|
661
|
-
if (['carbon monoxide', 'fire', 'burglary'].includes(latestState)) {
|
|
662
|
-
this.logMessage(`Alarm is active! Disarming the ${name} (${id})...`, 20);
|
|
663
|
-
|
|
664
|
-
await that.pulse.setDeviceStatus(oldArmState, 'off')
|
|
665
|
-
.then((response) => this.thenResponse(response))
|
|
666
|
-
.catch((error) => this.catchErrors(error));
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
this.logMessage(`Alarm is inactive. Clearing the ${name} (${id}) alarm...`, 20);
|
|
670
|
-
|
|
671
|
-
// Clear the uncleared alarm.
|
|
672
|
-
await that.pulse.setDeviceStatus('disarmed+with+alarm', 'off')
|
|
673
|
-
.then((response) => this.thenResponse(response))
|
|
674
|
-
.catch((error) => this.catchErrors(error));
|
|
675
|
-
|
|
676
|
-
// Make sure oldArmState is manually reset.
|
|
677
|
-
oldArmState = 'disarmed';
|
|
678
|
-
|
|
679
|
-
// If disarm, job is finished.
|
|
680
|
-
if (newArmState === 'off') {
|
|
681
|
-
return;
|
|
682
|
-
}
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
// Set the device status.
|
|
686
|
-
if (oldArmState === newArmState || (oldArmState === 'disarmed' && newArmState === 'off')) {
|
|
687
|
-
this.logMessage(`Already set to ${newArmState}. Cannot set from ${oldArmState} to ${newArmState}.`, 20);
|
|
688
|
-
} else {
|
|
689
|
-
const armStay = Characteristic.SecuritySystemTargetState.STAY_ARM;
|
|
690
|
-
const armAway = Characteristic.SecuritySystemTargetState.AWAY_ARM;
|
|
691
|
-
const armNight = Characteristic.SecuritySystemTargetState.NIGHT_ARM;
|
|
692
|
-
|
|
693
|
-
// If device is not disarmed, and you are attempting to arm.
|
|
694
|
-
if (oldArmState !== 'disarmed' && [armStay, armAway, armNight].includes(arm)) {
|
|
695
|
-
this.logMessage(`Switching arm modes. Disarming ${name} (${id}) first...`, 30);
|
|
696
|
-
|
|
697
|
-
await that.pulse.setDeviceStatus(oldArmState, 'off')
|
|
698
|
-
.then((response) => this.thenResponse(response))
|
|
699
|
-
.catch((error) => this.catchErrors(error));
|
|
700
|
-
|
|
701
|
-
// Make sure oldArmState is manually reset.
|
|
702
|
-
oldArmState = 'disarmed';
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
await that.pulse.setDeviceStatus(oldArmState, newArmState)
|
|
706
|
-
.then((response) => this.thenResponse(response))
|
|
707
|
-
.catch((error) => this.catchErrors(error));
|
|
708
|
-
}
|
|
709
|
-
})
|
|
710
|
-
.catch((error) => this.catchErrors(error));
|
|
711
|
-
};
|
|
712
|
-
|
|
713
|
-
/**
|
|
714
|
-
* Format set device status.
|
|
715
|
-
*
|
|
716
|
-
* @param {(string|number)} status - The device status.
|
|
717
|
-
* @param {string} type - Can be "armState" or "arm".
|
|
718
|
-
*
|
|
719
|
-
* @returns {(undefined|string)}
|
|
720
|
-
*
|
|
721
|
-
* @since 1.0.0
|
|
722
|
-
*/
|
|
723
|
-
ADTPulsePlatform.prototype.formatSetDeviceStatus = function formatSetDeviceStatus(status, type) {
|
|
724
|
-
let string;
|
|
725
|
-
|
|
726
|
-
if (type === 'armState' && typeof status === 'string') {
|
|
727
|
-
if (status.includes('disarmed')) {
|
|
728
|
-
string = 'disarmed';
|
|
729
|
-
} else if (status.includes('armed away')) {
|
|
730
|
-
string = 'away';
|
|
731
|
-
} else if (status.includes('armed stay')) {
|
|
732
|
-
string = 'stay';
|
|
733
|
-
} else if (status.includes('armed night')) {
|
|
734
|
-
string = 'night';
|
|
735
|
-
}
|
|
736
|
-
} else if (type === 'arm' && typeof status === 'number') {
|
|
737
|
-
if (status === 0) {
|
|
738
|
-
string = 'stay';
|
|
739
|
-
} else if (status === 1) {
|
|
740
|
-
string = 'away';
|
|
741
|
-
} else if (status === 2) {
|
|
742
|
-
string = 'night';
|
|
743
|
-
} else if (status === 3) {
|
|
744
|
-
string = 'off';
|
|
745
|
-
}
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
return string;
|
|
749
|
-
};
|
|
750
|
-
|
|
751
|
-
/**
|
|
752
|
-
* Get zone status.
|
|
753
|
-
*
|
|
754
|
-
* Returns the latest zone state from "this.zoneStatus" array.
|
|
755
|
-
*
|
|
756
|
-
* @param {string} type - Can be "system", "doorWindow", "glass", "motion", "co", or "fire".
|
|
757
|
-
* @param {string} id - The accessory unique identification code.
|
|
758
|
-
* @param {boolean} format - Format device status to Homebridge.
|
|
759
|
-
*
|
|
760
|
-
* @returns {(undefined|string|number|boolean)}
|
|
761
|
-
*
|
|
762
|
-
* @since 1.0.0
|
|
763
|
-
*/
|
|
764
|
-
ADTPulsePlatform.prototype.getZoneStatus = function getZoneStatus(type, id, format) {
|
|
765
|
-
const zone = _.find(this.zoneStatus, ['id', id]);
|
|
766
|
-
const state = _.get(zone, 'state');
|
|
767
|
-
|
|
768
|
-
if (typeof state === 'string') {
|
|
769
|
-
if (format) {
|
|
770
|
-
return this.formatGetZoneStatus(type, state);
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
return state;
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
return undefined;
|
|
777
|
-
};
|
|
778
|
-
|
|
779
|
-
/**
|
|
780
|
-
* Format get zone status.
|
|
781
|
-
*
|
|
782
|
-
* Converts the zone state from ADT Pulse "devStat" icon classes to Homebridge compatible.
|
|
783
|
-
*
|
|
784
|
-
* @param {string} type - Can be "doorWindow", "glass", "motion", "co", or "fire".
|
|
785
|
-
* @param {string} state - Can be "devStatOK", "devStatLowBatt", "devStatOpen", "devStatMotion", "devStatTamper", or "devStatAlarm".
|
|
786
|
-
*
|
|
787
|
-
* @returns {(undefined|number|boolean)}
|
|
788
|
-
*
|
|
789
|
-
* @since 1.0.0
|
|
790
|
-
*/
|
|
791
|
-
ADTPulsePlatform.prototype.formatGetZoneStatus = function formatGetZoneStatus(type, state) {
|
|
792
|
-
let status;
|
|
793
|
-
|
|
794
|
-
switch (type) {
|
|
795
|
-
case 'doorWindow':
|
|
796
|
-
if (state === 'devStatOK' || state === 'devStatLowBatt') {
|
|
797
|
-
status = Characteristic.ContactSensorState.CONTACT_DETECTED;
|
|
798
|
-
} else if (state === 'devStatOpen' || state === 'devStatTamper') {
|
|
799
|
-
status = Characteristic.ContactSensorState.CONTACT_NOT_DETECTED;
|
|
800
|
-
}
|
|
801
|
-
break;
|
|
802
|
-
case 'glass':
|
|
803
|
-
if (state === 'devStatOK' || state === 'devStatLowBatt') {
|
|
804
|
-
status = Characteristic.OccupancyDetected.OCCUPANCY_NOT_DETECTED;
|
|
805
|
-
} else if (state === 'devStatTamper') {
|
|
806
|
-
status = Characteristic.OccupancyDetected.OCCUPANCY_DETECTED;
|
|
807
|
-
}
|
|
808
|
-
break;
|
|
809
|
-
case 'motion':
|
|
810
|
-
if (state === 'devStatOK' || state === 'devStatLowBatt') {
|
|
811
|
-
status = false;
|
|
812
|
-
} else if (state === 'devStatMotion' || state === 'devStatTamper') {
|
|
813
|
-
status = true;
|
|
814
|
-
}
|
|
815
|
-
break;
|
|
816
|
-
case 'co':
|
|
817
|
-
if (state === 'devStatOK' || state === 'devStatLowBatt') {
|
|
818
|
-
status = Characteristic.CarbonMonoxideDetected.CO_LEVELS_NORMAL;
|
|
819
|
-
} else if (state === 'devStatAlarm' || state === 'devStatTamper') {
|
|
820
|
-
status = Characteristic.CarbonMonoxideDetected.CO_LEVELS_ABNORMAL;
|
|
821
|
-
}
|
|
822
|
-
break;
|
|
823
|
-
case 'fire':
|
|
824
|
-
if (state === 'devStatOK' || state === 'devStatLowBatt') {
|
|
825
|
-
status = Characteristic.SmokeDetected.SMOKE_NOT_DETECTED;
|
|
826
|
-
} else if (state === 'devStatAlarm' || state === 'devStatTamper') {
|
|
827
|
-
status = Characteristic.SmokeDetected.SMOKE_DETECTED;
|
|
828
|
-
}
|
|
829
|
-
break;
|
|
830
|
-
default:
|
|
831
|
-
this.logMessage(`Unknown type with state... ${type} (${state})`, 10);
|
|
832
|
-
break;
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
return status;
|
|
836
|
-
};
|
|
837
|
-
|
|
838
|
-
/**
|
|
839
|
-
* Sync with web portal.
|
|
840
|
-
*
|
|
841
|
-
* Retrieve latest status, add/remove accessories.
|
|
842
|
-
*
|
|
843
|
-
* @since 1.0.0
|
|
844
|
-
*/
|
|
845
|
-
ADTPulsePlatform.prototype.portalSync = function portalSync() {
|
|
846
|
-
const that = this;
|
|
847
|
-
|
|
848
|
-
clearTimeout(this.portalSyncSession.timer);
|
|
849
|
-
|
|
850
|
-
// Begin portal sync.
|
|
851
|
-
if (this.isSyncing !== true) {
|
|
852
|
-
this.isSyncing = true;
|
|
853
|
-
|
|
854
|
-
// Reset stalled sync increments.
|
|
855
|
-
this.stalledSyncTimes = 0;
|
|
856
|
-
|
|
857
|
-
this.logMessage('Synchronizing with ADT Pulse Web Portal...', 40);
|
|
858
|
-
|
|
859
|
-
// Store in session, so it's easy to wipe out later.
|
|
860
|
-
this.portalSyncSession.function = that.pulse
|
|
861
|
-
.login()
|
|
862
|
-
.then((response) => {
|
|
863
|
-
const version = _.get(response, 'info.version');
|
|
864
|
-
const supportedVersions = that.testedBuilds;
|
|
865
|
-
|
|
866
|
-
if (version !== undefined && !supportedVersions.includes(version) && version !== this.sessionVersion) {
|
|
867
|
-
this.logMessage(`Web Portal version ${version} detected. Test plugin to ensure system compatibility...`, 20);
|
|
868
|
-
}
|
|
869
|
-
|
|
870
|
-
// Bind version to session so message does not bomb logs.
|
|
871
|
-
this.sessionVersion = version;
|
|
872
|
-
})
|
|
873
|
-
.then(() => that.pulse.performPortalSync())
|
|
874
|
-
.then(async (syncCode) => {
|
|
875
|
-
const theSyncCode = _.get(syncCode, 'info.syncCode');
|
|
876
|
-
|
|
877
|
-
// Runs if status changes.
|
|
878
|
-
if (theSyncCode !== this.lastSyncCode || theSyncCode === '1-0-0') {
|
|
879
|
-
this.logMessage(`New sync code detected... ${theSyncCode}`, 40);
|
|
880
|
-
|
|
881
|
-
// Add or update accessories.
|
|
882
|
-
await that.pulse
|
|
883
|
-
.getDeviceStatus()
|
|
884
|
-
.then(async (device) => {
|
|
885
|
-
const deviceStatus = _.get(device, 'info');
|
|
886
|
-
|
|
887
|
-
const deviceUUID = UUIDGen.generate('system-1');
|
|
888
|
-
const deviceLoaded = _.find(that.accessories, ['UUID', deviceUUID]);
|
|
889
|
-
|
|
890
|
-
if (that.logActivity) {
|
|
891
|
-
const deviceId = 'system-1';
|
|
892
|
-
const deviceName = 'Security Panel';
|
|
893
|
-
|
|
894
|
-
const oldState = _.get(this.deviceStatus, 'state');
|
|
895
|
-
const oldStatus = _.get(this.deviceStatus, 'status');
|
|
896
|
-
const newState = _.get(deviceStatus, 'state');
|
|
897
|
-
const newStatus = _.get(deviceStatus, 'status');
|
|
898
|
-
|
|
899
|
-
const oldSummary = `${oldState} / ${oldStatus}`.toLowerCase();
|
|
900
|
-
const newSummary = `${newState} / ${newStatus}`.toLowerCase();
|
|
901
|
-
|
|
902
|
-
if (!oldSummary.includes('undefined') && oldSummary !== newSummary) {
|
|
903
|
-
this.logMessage(`${deviceName} (${deviceId}) changed from "${oldSummary}" to "${newSummary}".`, 30);
|
|
904
|
-
}
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
// Set latest status into instance.
|
|
908
|
-
this.deviceStatus = deviceStatus;
|
|
909
|
-
|
|
910
|
-
// Add or update device.
|
|
911
|
-
if (deviceLoaded === undefined) {
|
|
912
|
-
try {
|
|
913
|
-
const getDeviceInfo = await that.pulse.getDeviceInformation();
|
|
914
|
-
const deviceInfo = _.get(getDeviceInfo, 'info');
|
|
915
|
-
const deviceInfoStatus = _.merge(deviceInfo, deviceStatus);
|
|
916
|
-
|
|
917
|
-
this.prepareAddAccessory('device', deviceInfoStatus);
|
|
918
|
-
} catch (error) {
|
|
919
|
-
this.catchErrors(error);
|
|
920
|
-
}
|
|
921
|
-
}
|
|
922
|
-
|
|
923
|
-
this.devicePolling('system', 'system-1');
|
|
924
|
-
})
|
|
925
|
-
.then(() => that.pulse.getZoneStatus())
|
|
926
|
-
.then((zones) => {
|
|
927
|
-
const zoneStatus = _.get(zones, 'info');
|
|
928
|
-
|
|
929
|
-
if (that.logActivity) {
|
|
930
|
-
_.forEach(zoneStatus, (zone) => {
|
|
931
|
-
const zoneId = _.get(zone, 'id');
|
|
932
|
-
const zoneName = _.get(zone, 'name');
|
|
933
|
-
const zoneTags = _.get(zone, 'tags');
|
|
934
|
-
|
|
935
|
-
const matchStatus = _.find(this.zoneStatus, {
|
|
936
|
-
id: zoneId,
|
|
937
|
-
tags: zoneTags,
|
|
938
|
-
});
|
|
939
|
-
|
|
940
|
-
const oldStatus = _.get(matchStatus, 'state');
|
|
941
|
-
const newStatus = _.get(zone, 'state');
|
|
942
|
-
|
|
943
|
-
if (oldStatus !== undefined && oldStatus !== newStatus) {
|
|
944
|
-
const convertOld = this.convertZoneStatus(oldStatus, zoneTags);
|
|
945
|
-
const convertNew = this.convertZoneStatus(newStatus, zoneTags);
|
|
946
|
-
|
|
947
|
-
this.logMessage(`${zoneName} (${zoneId}) changed from "${convertOld}" to "${convertNew}".`, 30);
|
|
948
|
-
}
|
|
949
|
-
});
|
|
950
|
-
}
|
|
951
|
-
|
|
952
|
-
// Set latest status into instance.
|
|
953
|
-
this.zoneStatus = zoneStatus;
|
|
954
|
-
|
|
955
|
-
_.forEach(zoneStatus, (zone) => {
|
|
956
|
-
const zoneId = _.get(zone, 'id');
|
|
957
|
-
const zoneTags = _.get(zone, 'tags');
|
|
958
|
-
|
|
959
|
-
const zoneType = zoneTags.substr(zoneTags.indexOf(',') + 1);
|
|
960
|
-
|
|
961
|
-
const deviceUUID = UUIDGen.generate(zoneId);
|
|
962
|
-
const deviceLoaded = _.find(that.accessories, ['UUID', deviceUUID]);
|
|
963
|
-
|
|
964
|
-
// Do not poll or add unknown sensor type.
|
|
965
|
-
if (zoneTags === 'sensor') {
|
|
966
|
-
return;
|
|
967
|
-
}
|
|
968
|
-
|
|
969
|
-
// Add or update zone.
|
|
970
|
-
if (deviceLoaded === undefined) {
|
|
971
|
-
this.prepareAddAccessory('zone', zone);
|
|
972
|
-
}
|
|
973
|
-
|
|
974
|
-
this.devicePolling(zoneType, zoneId);
|
|
975
|
-
});
|
|
976
|
-
})
|
|
977
|
-
.catch((error) => this.catchErrors(error));
|
|
978
|
-
|
|
979
|
-
// Remove obsolete zones.
|
|
980
|
-
_.forEachRight(that.accessories, (accessory) => {
|
|
981
|
-
const id = _.get(accessory, 'context.id');
|
|
982
|
-
const type = _.get(accessory, 'context.type');
|
|
983
|
-
const zone = _.find(this.zoneStatus, { id });
|
|
984
|
-
|
|
985
|
-
// Do not remove security panel(s).
|
|
986
|
-
if (zone === undefined && type !== 'system') {
|
|
987
|
-
if (that.removeObsoleteZones) {
|
|
988
|
-
this.logMessage(`Preparing to remove zone (${id}) accessory...`, 30);
|
|
989
|
-
this.removeAccessory(accessory);
|
|
990
|
-
} else {
|
|
991
|
-
this.logMessage(`Preparing to remove zone (${id}) accessory, but "removeObsoleteZones" is disabled...`, 20);
|
|
992
|
-
}
|
|
993
|
-
}
|
|
994
|
-
});
|
|
995
|
-
|
|
996
|
-
// Update sync code.
|
|
997
|
-
this.lastSyncCode = theSyncCode;
|
|
998
|
-
}
|
|
999
|
-
})
|
|
1000
|
-
.then(() => {
|
|
1001
|
-
this.isSyncing = false;
|
|
1002
|
-
})
|
|
1003
|
-
.catch((error) => {
|
|
1004
|
-
this.isSyncing = false;
|
|
1005
|
-
|
|
1006
|
-
this.catchErrors(error);
|
|
1007
|
-
});
|
|
1008
|
-
} else if (this.stalledSyncTimes >= 5) {
|
|
1009
|
-
this.isSyncing = false;
|
|
1010
|
-
|
|
1011
|
-
// Reset sync session.
|
|
1012
|
-
this.portalSyncSession = {};
|
|
1013
|
-
|
|
1014
|
-
this.logMessage('Portal sync stalled. Cleaning up and resetting...', 20);
|
|
1015
|
-
} else {
|
|
1016
|
-
this.stalledSyncTimes += 1;
|
|
1017
|
-
|
|
1018
|
-
this.logMessage('Portal sync is already in progress...', 40);
|
|
1019
|
-
}
|
|
1020
|
-
|
|
1021
|
-
// Refresh portal sync session.
|
|
1022
|
-
this.portalSyncSession.timer = setTimeout(
|
|
1023
|
-
() => {
|
|
1024
|
-
if (this.failedLoginTimes > 2) {
|
|
1025
|
-
this.failedLoginTimes = 0;
|
|
1026
|
-
}
|
|
1027
|
-
|
|
1028
|
-
this.portalSync();
|
|
1029
|
-
},
|
|
1030
|
-
// If login failed more than 2 times.
|
|
1031
|
-
(this.failedLoginTimes >= 2)
|
|
1032
|
-
? that.syncIntervalDelay * 1000
|
|
1033
|
-
: that.syncInterval * 1000,
|
|
1034
|
-
);
|
|
1035
|
-
};
|
|
1036
|
-
|
|
1037
|
-
/**
|
|
1038
|
-
* Convert "devStat" zone statuses to human readable format.
|
|
1039
|
-
*
|
|
1040
|
-
* @param {string} status - The raw "devStat" zone status.
|
|
1041
|
-
* @param {string} type - Can be "sensor,doorWindow", "sensor,glass", "sensor,motion", "sensor,co", or "sensor,fire".
|
|
1042
|
-
*
|
|
1043
|
-
* @returns {(undefined|string)}
|
|
1044
|
-
*
|
|
1045
|
-
* @since 1.0.0
|
|
1046
|
-
*/
|
|
1047
|
-
ADTPulsePlatform.prototype.convertZoneStatus = function convertZoneStatus(status, type) {
|
|
1048
|
-
let newStatus;
|
|
1049
|
-
|
|
1050
|
-
if (typeof status === 'string') {
|
|
1051
|
-
const processedStatus = status.replace('devStat', '').toLowerCase();
|
|
1052
|
-
|
|
1053
|
-
if (processedStatus === 'ok') {
|
|
1054
|
-
switch (type) {
|
|
1055
|
-
case 'sensor,doorWindow':
|
|
1056
|
-
newStatus = 'closed';
|
|
1057
|
-
break;
|
|
1058
|
-
case 'sensor,glass':
|
|
1059
|
-
newStatus = 'not tripped';
|
|
1060
|
-
break;
|
|
1061
|
-
case 'sensor,motion':
|
|
1062
|
-
newStatus = 'no motion';
|
|
1063
|
-
break;
|
|
1064
|
-
case 'sensor,co':
|
|
1065
|
-
case 'sensor,fire':
|
|
1066
|
-
newStatus = 'no alarm';
|
|
1067
|
-
break;
|
|
1068
|
-
default:
|
|
1069
|
-
break;
|
|
1070
|
-
}
|
|
1071
|
-
} else {
|
|
1072
|
-
newStatus = processedStatus;
|
|
1073
|
-
}
|
|
1074
|
-
}
|
|
1075
|
-
|
|
1076
|
-
return newStatus || status;
|
|
1077
|
-
};
|
|
1078
|
-
|
|
1079
|
-
/**
|
|
1080
|
-
* Force accessories to update.
|
|
1081
|
-
*
|
|
1082
|
-
* @param {string} type - Can be "system", "doorWindow", "glass", "motion", "co", or "fire".
|
|
1083
|
-
* @param {string} id - The accessory unique identification code.
|
|
1084
|
-
*
|
|
1085
|
-
* @since 1.0.0
|
|
1086
|
-
*/
|
|
1087
|
-
ADTPulsePlatform.prototype.devicePolling = function devicePolling(type, id) {
|
|
1088
|
-
const that = this;
|
|
1089
|
-
|
|
1090
|
-
const uuid = UUIDGen.generate(id);
|
|
1091
|
-
const accessory = _.find(that.accessories, ['UUID', uuid]);
|
|
1092
|
-
const name = _.get(accessory, 'displayName');
|
|
1093
|
-
|
|
1094
|
-
if (accessory !== undefined) {
|
|
1095
|
-
this.logMessage(`Polling device status for ${name} (${id})...`, 50);
|
|
1096
|
-
|
|
1097
|
-
switch (type) {
|
|
1098
|
-
case 'system':
|
|
1099
|
-
accessory
|
|
1100
|
-
.getService(Service.SecuritySystem)
|
|
1101
|
-
.getCharacteristic(Characteristic.SecuritySystemTargetState)
|
|
1102
|
-
.getValue();
|
|
1103
|
-
|
|
1104
|
-
accessory
|
|
1105
|
-
.getService(Service.SecuritySystem)
|
|
1106
|
-
.getCharacteristic(Characteristic.SecuritySystemCurrentState)
|
|
1107
|
-
.getValue();
|
|
1108
|
-
break;
|
|
1109
|
-
case 'doorWindow':
|
|
1110
|
-
accessory
|
|
1111
|
-
.getService(Service.ContactSensor)
|
|
1112
|
-
.getCharacteristic(Characteristic.ContactSensorState)
|
|
1113
|
-
.getValue();
|
|
1114
|
-
break;
|
|
1115
|
-
case 'glass':
|
|
1116
|
-
accessory
|
|
1117
|
-
.getService(Service.OccupancySensor)
|
|
1118
|
-
.getCharacteristic(Characteristic.OccupancyDetected)
|
|
1119
|
-
.getValue();
|
|
1120
|
-
break;
|
|
1121
|
-
case 'motion':
|
|
1122
|
-
accessory
|
|
1123
|
-
.getService(Service.MotionSensor)
|
|
1124
|
-
.getCharacteristic(Characteristic.MotionDetected)
|
|
1125
|
-
.getValue();
|
|
1126
|
-
break;
|
|
1127
|
-
case 'co':
|
|
1128
|
-
accessory
|
|
1129
|
-
.getService(Service.CarbonMonoxideSensor)
|
|
1130
|
-
.getCharacteristic(Characteristic.CarbonMonoxideDetected)
|
|
1131
|
-
.getValue();
|
|
1132
|
-
break;
|
|
1133
|
-
case 'fire':
|
|
1134
|
-
accessory
|
|
1135
|
-
.getService(Service.SmokeSensor)
|
|
1136
|
-
.getCharacteristic(Characteristic.SmokeDetected)
|
|
1137
|
-
.getValue();
|
|
1138
|
-
break;
|
|
1139
|
-
default:
|
|
1140
|
-
this.logMessage(`Failed to poll invalid or unsupported accessory... ${type}`, 10);
|
|
1141
|
-
break;
|
|
1142
|
-
}
|
|
1143
|
-
}
|
|
1144
|
-
};
|
|
1145
|
-
|
|
1146
|
-
/**
|
|
1147
|
-
* Then response (for setDeviceStatus only).
|
|
1148
|
-
*
|
|
1149
|
-
* @param {object} response - The response object from setDeviceStatus.
|
|
1150
|
-
*
|
|
1151
|
-
* @since 1.0.0
|
|
1152
|
-
*/
|
|
1153
|
-
ADTPulsePlatform.prototype.thenResponse = function thenResponse(response) {
|
|
1154
|
-
const forceArm = _.get(response, 'info.forceArm');
|
|
1155
|
-
|
|
1156
|
-
if (forceArm) {
|
|
1157
|
-
this.logMessage('Sensor(s) were bypassed when arming. Check the ADT Pulse website or app for more details.', 20);
|
|
1158
|
-
}
|
|
1159
|
-
|
|
1160
|
-
if (response) {
|
|
1161
|
-
this.logMessage(response, 40);
|
|
1162
|
-
}
|
|
1163
|
-
};
|
|
1164
|
-
|
|
1165
|
-
/**
|
|
1166
|
-
* Catch errors.
|
|
1167
|
-
*
|
|
1168
|
-
* @param {object} error - The error response object.
|
|
1169
|
-
*
|
|
1170
|
-
* @since 1.0.0
|
|
1171
|
-
*/
|
|
1172
|
-
ADTPulsePlatform.prototype.catchErrors = function catchErrors(error) {
|
|
1173
|
-
const action = _.get(error, 'action');
|
|
1174
|
-
const infoError = _.get(error, 'info.error');
|
|
1175
|
-
const infoMessage = _.get(error, 'info.message', '');
|
|
1176
|
-
|
|
1177
|
-
let priority;
|
|
1178
|
-
|
|
1179
|
-
switch (action) {
|
|
1180
|
-
case 'LOGIN':
|
|
1181
|
-
if (infoMessage.match(/(Sign In unsuccessful\.)/g)) {
|
|
1182
|
-
this.failedLoginTimes += 1;
|
|
1183
|
-
}
|
|
1184
|
-
|
|
1185
|
-
// If login fails more than 2 times.
|
|
1186
|
-
if (this.failedLoginTimes > 2) {
|
|
1187
|
-
this.logMessage('Login failed more than 2 times. Portal sync restarting in 10 minutes...', priority = 10);
|
|
1188
|
-
} else {
|
|
1189
|
-
this.logMessage('Login failed. Trying again...', priority = 20);
|
|
1190
|
-
}
|
|
1191
|
-
break;
|
|
1192
|
-
case 'SYNC':
|
|
1193
|
-
this.logMessage('Portal sync failed. Attempting to fix connection...', priority = 40);
|
|
1194
|
-
break;
|
|
1195
|
-
case 'GET_DEVICE_INFO':
|
|
1196
|
-
this.logMessage('Get device information failed.', priority = 10);
|
|
1197
|
-
break;
|
|
1198
|
-
case 'GET_DEVICE_STATUS':
|
|
1199
|
-
this.logMessage('Get device status failed.', priority = 10);
|
|
1200
|
-
break;
|
|
1201
|
-
case 'GET_ZONE_STATUS':
|
|
1202
|
-
this.logMessage('Get zone status failed.', priority = 10);
|
|
1203
|
-
break;
|
|
1204
|
-
case 'SET_DEVICE_STATUS':
|
|
1205
|
-
this.logMessage('Set device status failed.', priority = 10);
|
|
1206
|
-
break;
|
|
1207
|
-
case 'HOST_UNREACHABLE':
|
|
1208
|
-
this.logMessage('Internet disconnected or portal unreachable. Trying again...', priority = 10);
|
|
1209
|
-
break;
|
|
1210
|
-
default:
|
|
1211
|
-
this.logMessage('An unknown error occurred.', priority = 10);
|
|
1212
|
-
break;
|
|
1213
|
-
}
|
|
1214
|
-
|
|
1215
|
-
if (infoMessage) {
|
|
1216
|
-
this.logMessage(infoMessage, priority);
|
|
1217
|
-
}
|
|
1218
|
-
|
|
1219
|
-
if (infoError) {
|
|
1220
|
-
const message = _.get(infoError, 'message');
|
|
1221
|
-
|
|
1222
|
-
if (message) {
|
|
1223
|
-
this.logMessage(`Error: ${message}`, priority);
|
|
1224
|
-
} else {
|
|
1225
|
-
this.logMessage(infoError, priority);
|
|
1226
|
-
}
|
|
1227
|
-
}
|
|
1228
|
-
|
|
1229
|
-
this.logMessage(error, 40);
|
|
1230
|
-
};
|
|
1231
|
-
|
|
1232
|
-
/**
|
|
1233
|
-
* Log system information.
|
|
1234
|
-
*
|
|
1235
|
-
* @param {object} systemInfo - System information object.
|
|
1236
|
-
* @param {string} systemInfo.platform - The current platform.
|
|
1237
|
-
* @param {string} systemInfo.arch - The current architecture.
|
|
1238
|
-
* @param {string} systemInfo.pluginVer - The plugin version.
|
|
1239
|
-
* @param {string} systemInfo.nodeVer - The node version.
|
|
1240
|
-
* @param {string} systemInfo.homebridgeVer - The homebridge version.
|
|
1241
|
-
*
|
|
1242
|
-
* @since 1.0.0
|
|
1243
|
-
*/
|
|
1244
|
-
ADTPulsePlatform.prototype.logSystemInformation = function logSystemInformation(systemInfo) {
|
|
1245
|
-
this.logMessage(`running on ${systemInfo.platform} (${systemInfo.arch})`, 30);
|
|
1246
|
-
this.logMessage(`homebridge-adt-pulse v${systemInfo.pluginVer}`, 30);
|
|
1247
|
-
this.logMessage(`node v${systemInfo.nodeVer}`, 30);
|
|
1248
|
-
this.logMessage(`homebridge v${systemInfo.homebridgeVer}`, 30);
|
|
1249
|
-
};
|
|
1250
|
-
|
|
1251
|
-
/**
|
|
1252
|
-
* Log message.
|
|
1253
|
-
*
|
|
1254
|
-
* @param {(object|string)} content - The message or content being recorded into the logs.
|
|
1255
|
-
* @param {number} priority - Can be 10 (error), 20 (warn), 30 (info), 40 (debug), or 50 (verbose).
|
|
1256
|
-
*
|
|
1257
|
-
* @since 1.0.0
|
|
1258
|
-
*/
|
|
1259
|
-
ADTPulsePlatform.prototype.logMessage = function logMessage(content, priority) {
|
|
1260
|
-
const {
|
|
1261
|
-
log,
|
|
1262
|
-
logLevel,
|
|
1263
|
-
} = this;
|
|
1264
|
-
|
|
1265
|
-
if (logLevel >= priority) {
|
|
1266
|
-
/**
|
|
1267
|
-
* Messages will not be logged if priority is wrong.
|
|
1268
|
-
* Homebridge Debug Mode must be enabled for priorities 40 and 50.
|
|
1269
|
-
*/
|
|
1270
|
-
switch (priority) {
|
|
1271
|
-
case 10:
|
|
1272
|
-
log.error(content);
|
|
1273
|
-
break;
|
|
1274
|
-
case 20:
|
|
1275
|
-
log.warn(content);
|
|
1276
|
-
break;
|
|
1277
|
-
case 30:
|
|
1278
|
-
log.info(content);
|
|
1279
|
-
break;
|
|
1280
|
-
case 40:
|
|
1281
|
-
log.debug(content);
|
|
1282
|
-
break;
|
|
1283
|
-
case 50:
|
|
1284
|
-
log.debug(content);
|
|
1285
|
-
break;
|
|
1286
|
-
default:
|
|
1287
|
-
break;
|
|
1288
|
-
}
|
|
1289
|
-
}
|
|
1290
|
-
};
|
|
1291
|
-
|
|
1292
|
-
/**
|
|
1293
|
-
* Register the platform plugin.
|
|
1294
|
-
*
|
|
1295
|
-
* @param {object} homebridge - Homebridge API.
|
|
1296
|
-
*
|
|
1297
|
-
* @since 1.0.0
|
|
1298
|
-
*/
|
|
1299
|
-
module.exports = function plugin(homebridge) {
|
|
1300
|
-
Accessory = homebridge.platformAccessory;
|
|
1301
|
-
Service = homebridge.hap.Service;
|
|
1302
|
-
Characteristic = homebridge.hap.Characteristic;
|
|
1303
|
-
UUIDGen = homebridge.hap.uuid;
|
|
1304
|
-
|
|
1305
|
-
// Register the platform into Homebridge.
|
|
1306
|
-
homebridge.registerPlatform(
|
|
1307
|
-
'homebridge-adt-pulse',
|
|
1308
|
-
'ADTPulse',
|
|
1309
|
-
ADTPulsePlatform,
|
|
1310
|
-
true,
|
|
1311
|
-
);
|
|
1312
|
-
};
|