homebridge-adt-pulse 3.0.0-beta.4 → 3.0.0-beta.5
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/build/config.schema.json +237 -0
- package/build/src/index.js +6 -0
- package/build/src/index.js.map +1 -0
- package/build/src/lib/accessory.js +216 -0
- package/build/src/lib/accessory.js.map +1 -0
- package/build/src/lib/api.js +1876 -0
- package/build/src/lib/api.js.map +1 -0
- package/build/src/lib/detect.js +443 -0
- package/build/src/lib/detect.js.map +1 -0
- package/build/src/lib/platform.js +419 -0
- package/build/src/lib/platform.js.map +1 -0
- package/{src/lib/regex.ts → build/src/lib/regex.js} +1 -143
- package/build/src/lib/regex.js.map +1 -0
- package/build/src/lib/schema.js +29 -0
- package/build/src/lib/schema.js.map +1 -0
- package/build/src/lib/utility.js +434 -0
- package/build/src/lib/utility.js.map +1 -0
- package/build/src/scripts/repl.js +173 -0
- package/build/src/scripts/repl.js.map +1 -0
- package/build/src/scripts/test-api.js +171 -0
- package/build/src/scripts/test-api.js.map +1 -0
- package/package.json +5 -6
- package/src/index.ts +0 -18
- package/src/lib/accessory.ts +0 -405
- package/src/lib/api.ts +0 -3483
- package/src/lib/detect.ts +0 -728
- package/src/lib/platform.ts +0 -890
- package/src/lib/schema.ts +0 -34
- package/src/lib/utility.ts +0 -933
- package/src/scripts/repl.ts +0 -300
- package/src/scripts/test-api.ts +0 -278
- package/src/types/constant.d.ts +0 -308
- package/src/types/index.d.ts +0 -1472
- package/src/types/shared.d.ts +0 -517
- package/tsconfig.json +0 -32
package/src/lib/platform.ts
DELETED
|
@@ -1,890 +0,0 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
|
-
import {
|
|
3
|
-
arch,
|
|
4
|
-
argv,
|
|
5
|
-
env,
|
|
6
|
-
platform,
|
|
7
|
-
versions,
|
|
8
|
-
} from 'node:process';
|
|
9
|
-
import { serializeError } from 'serialize-error';
|
|
10
|
-
|
|
11
|
-
import { ADTPulseAccessory } from '@/lib/accessory.js';
|
|
12
|
-
import { ADTPulse } from '@/lib/api.js';
|
|
13
|
-
import { platformConfig } from '@/lib/schema.js';
|
|
14
|
-
import {
|
|
15
|
-
condenseSensorType,
|
|
16
|
-
findIndexWithValue,
|
|
17
|
-
getAccessoryCategory,
|
|
18
|
-
getPluralForm,
|
|
19
|
-
sleep,
|
|
20
|
-
stackTracer,
|
|
21
|
-
} from '@/lib/utility.js';
|
|
22
|
-
import type {
|
|
23
|
-
ADTPulsePlatformAccessories,
|
|
24
|
-
ADTPulsePlatformAddAccessoryDevice,
|
|
25
|
-
ADTPulsePlatformAddAccessoryReturns,
|
|
26
|
-
ADTPulsePlatformAddAccessoryTypedNewAccessory,
|
|
27
|
-
ADTPulsePlatformApi,
|
|
28
|
-
ADTPulsePlatformCharacteristic,
|
|
29
|
-
ADTPulsePlatformConfig,
|
|
30
|
-
ADTPulsePlatformConfigureAccessoryAccessory,
|
|
31
|
-
ADTPulsePlatformConfigureAccessoryReturns,
|
|
32
|
-
ADTPulsePlatformConstants,
|
|
33
|
-
ADTPulsePlatformConstructorApi,
|
|
34
|
-
ADTPulsePlatformConstructorConfig,
|
|
35
|
-
ADTPulsePlatformConstructorLog,
|
|
36
|
-
ADTPulsePlatformDebugMode,
|
|
37
|
-
ADTPulsePlatformFetchUpdatedInformationReturns,
|
|
38
|
-
ADTPulsePlatformHandlers,
|
|
39
|
-
ADTPulsePlatformInstance,
|
|
40
|
-
ADTPulsePlatformLog,
|
|
41
|
-
ADTPulsePlatformPlugin,
|
|
42
|
-
ADTPulsePlatformPollAccessoriesDevices,
|
|
43
|
-
ADTPulsePlatformPollAccessoriesReturns,
|
|
44
|
-
ADTPulsePlatformPrintSystemInformationReturns,
|
|
45
|
-
ADTPulsePlatformRemoveAccessoryAccessory,
|
|
46
|
-
ADTPulsePlatformRemoveAccessoryReturns,
|
|
47
|
-
ADTPulsePlatformService,
|
|
48
|
-
ADTPulsePlatformState,
|
|
49
|
-
ADTPulsePlatformSynchronizeKeepAliveReturns,
|
|
50
|
-
ADTPulsePlatformSynchronizeReturns,
|
|
51
|
-
ADTPulsePlatformSynchronizeSyncCheckReturns,
|
|
52
|
-
ADTPulsePlatformUnifyDevicesDevices,
|
|
53
|
-
ADTPulsePlatformUnifyDevicesId,
|
|
54
|
-
ADTPulsePlatformUnifyDevicesReturns,
|
|
55
|
-
ADTPulsePlatformUpdateAccessoryDevice,
|
|
56
|
-
ADTPulsePlatformUpdateAccessoryReturns,
|
|
57
|
-
} from '@/types/index.d.ts';
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* ADT Pulse Platform.
|
|
61
|
-
*
|
|
62
|
-
* @since 1.0.0
|
|
63
|
-
*/
|
|
64
|
-
export class ADTPulsePlatform implements ADTPulsePlatformPlugin {
|
|
65
|
-
/**
|
|
66
|
-
* ADT Pulse Platform - Accessories.
|
|
67
|
-
*
|
|
68
|
-
* @private
|
|
69
|
-
*
|
|
70
|
-
* @since 1.0.0
|
|
71
|
-
*/
|
|
72
|
-
accessories: ADTPulsePlatformAccessories;
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* ADT Pulse Platform - Api.
|
|
76
|
-
*
|
|
77
|
-
* @private
|
|
78
|
-
*
|
|
79
|
-
* @since 1.0.0
|
|
80
|
-
*/
|
|
81
|
-
readonly #api: ADTPulsePlatformApi;
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* ADT Pulse Platform - Characteristic.
|
|
85
|
-
*
|
|
86
|
-
* @private
|
|
87
|
-
*
|
|
88
|
-
* @since 1.0.0
|
|
89
|
-
*/
|
|
90
|
-
readonly #characteristic: ADTPulsePlatformCharacteristic;
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* ADT Pulse Platform - Config.
|
|
94
|
-
*
|
|
95
|
-
* @private
|
|
96
|
-
*
|
|
97
|
-
* @since 1.0.0
|
|
98
|
-
*/
|
|
99
|
-
#config: ADTPulsePlatformConfig;
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* ADT Pulse Platform - Constants.
|
|
103
|
-
*
|
|
104
|
-
* @private
|
|
105
|
-
*
|
|
106
|
-
* @since 1.0.0
|
|
107
|
-
*/
|
|
108
|
-
#constants: ADTPulsePlatformConstants;
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* ADT Pulse Platform - Debug mode.
|
|
112
|
-
*
|
|
113
|
-
* @private
|
|
114
|
-
*
|
|
115
|
-
* @since 1.0.0
|
|
116
|
-
*/
|
|
117
|
-
readonly #debugMode: ADTPulsePlatformDebugMode;
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* ADT Pulse Platform - Handlers.
|
|
121
|
-
*
|
|
122
|
-
* @private
|
|
123
|
-
*
|
|
124
|
-
* @since 1.0.0
|
|
125
|
-
*/
|
|
126
|
-
readonly #handlers: ADTPulsePlatformHandlers;
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* ADT Pulse Platform - Instance.
|
|
130
|
-
*
|
|
131
|
-
* @private
|
|
132
|
-
*
|
|
133
|
-
* @since 1.0.0
|
|
134
|
-
*/
|
|
135
|
-
#instance: ADTPulsePlatformInstance;
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* ADT Pulse Platform - Log.
|
|
139
|
-
*
|
|
140
|
-
* @private
|
|
141
|
-
*
|
|
142
|
-
* @since 1.0.0
|
|
143
|
-
*/
|
|
144
|
-
readonly #log: ADTPulsePlatformLog;
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* ADT Pulse Platform - Service.
|
|
148
|
-
*
|
|
149
|
-
* @private
|
|
150
|
-
*
|
|
151
|
-
* @since 1.0.0
|
|
152
|
-
*/
|
|
153
|
-
readonly #service: ADTPulsePlatformService;
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* ADT Pulse Platform - State.
|
|
157
|
-
*
|
|
158
|
-
* @private
|
|
159
|
-
*
|
|
160
|
-
* @since 1.0.0
|
|
161
|
-
*/
|
|
162
|
-
readonly #state: ADTPulsePlatformState;
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* ADT Pulse Platform - Constructor.
|
|
166
|
-
*
|
|
167
|
-
* @param {ADTPulsePlatformConstructorLog} log - Log.
|
|
168
|
-
* @param {ADTPulsePlatformConstructorConfig} config - Config.
|
|
169
|
-
* @param {ADTPulsePlatformConstructorApi} api - Api.
|
|
170
|
-
*
|
|
171
|
-
* @since 1.0.0
|
|
172
|
-
*/
|
|
173
|
-
constructor(log: ADTPulsePlatformConstructorLog, config: ADTPulsePlatformConstructorConfig, api: ADTPulsePlatformConstructorApi) {
|
|
174
|
-
this.accessories = [];
|
|
175
|
-
this.#api = api;
|
|
176
|
-
this.#characteristic = api.hap.Characteristic;
|
|
177
|
-
this.#config = null;
|
|
178
|
-
this.#constants = {
|
|
179
|
-
intervalTimestamps: {
|
|
180
|
-
adtKeepAlive: 538000, // 8.9666666667 minutes.
|
|
181
|
-
adtSyncCheck: 3000, // 3 seconds.
|
|
182
|
-
suspendSyncing: 1800000, // 30 minutes.
|
|
183
|
-
synchronize: 1000, // 1 second.
|
|
184
|
-
},
|
|
185
|
-
maxLoginRetries: 3,
|
|
186
|
-
};
|
|
187
|
-
this.#debugMode = argv.includes('-D') || argv.includes('--debug');
|
|
188
|
-
this.#handlers = {};
|
|
189
|
-
this.#instance = null;
|
|
190
|
-
this.#log = log;
|
|
191
|
-
this.#service = api.hap.Service;
|
|
192
|
-
this.#state = {
|
|
193
|
-
activity: {
|
|
194
|
-
isAdtKeepingAlive: false,
|
|
195
|
-
isAdtSyncChecking: false,
|
|
196
|
-
isLoggingIn: false,
|
|
197
|
-
isSyncing: false,
|
|
198
|
-
},
|
|
199
|
-
data: {
|
|
200
|
-
gatewayInfo: null,
|
|
201
|
-
panelInfo: null,
|
|
202
|
-
panelStatus: null,
|
|
203
|
-
sensorsInfo: [],
|
|
204
|
-
sensorsStatus: [],
|
|
205
|
-
syncCode: '1-0-0',
|
|
206
|
-
},
|
|
207
|
-
eventCounters: {
|
|
208
|
-
failedLogins: 0,
|
|
209
|
-
},
|
|
210
|
-
intervals: {
|
|
211
|
-
synchronize: undefined,
|
|
212
|
-
},
|
|
213
|
-
lastRunOn: {
|
|
214
|
-
adtKeepAlive: 0, // January 1, 1970, at 00:00:00 UTC.
|
|
215
|
-
adtSyncCheck: 0, // January 1, 1970, at 00:00:00 UTC.
|
|
216
|
-
},
|
|
217
|
-
};
|
|
218
|
-
|
|
219
|
-
// Parsed Homebridge platform configuration.
|
|
220
|
-
const parsedConfig = platformConfig.safeParse(config);
|
|
221
|
-
|
|
222
|
-
// Check for a valid platform configuration before initializing.
|
|
223
|
-
if (!parsedConfig.success) {
|
|
224
|
-
this.#log.error('Plugin is unable to initialize due to an invalid platform configuration.');
|
|
225
|
-
stackTracer('zod-error', parsedConfig.error.errors);
|
|
226
|
-
|
|
227
|
-
return;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
// Start plugin here after Homebridge has restored all cached accessories from disk.
|
|
231
|
-
api.on('didFinishLaunching', async () => {
|
|
232
|
-
// Assign the parsed config.
|
|
233
|
-
this.#config = parsedConfig.data;
|
|
234
|
-
|
|
235
|
-
// Initialize the API instance.
|
|
236
|
-
this.#instance = new ADTPulse(
|
|
237
|
-
this.#config,
|
|
238
|
-
{
|
|
239
|
-
// If Homebridge debug mode, set "this instance" to debug mode as well.
|
|
240
|
-
debug: this.#debugMode === true,
|
|
241
|
-
logger: this.#log,
|
|
242
|
-
},
|
|
243
|
-
);
|
|
244
|
-
|
|
245
|
-
// Print the system information into logs.
|
|
246
|
-
this.printSystemInformation();
|
|
247
|
-
|
|
248
|
-
// Give notice to users this plugin is being anonymously tracked.
|
|
249
|
-
this.#log.info(`${chalk.bold.yellowBright('NOTICE')}: The API gathers anonymous analytics to detect potential bugs or issues. All personally identifiable information redacted. You will see exactly what will be sent out.`);
|
|
250
|
-
|
|
251
|
-
// If the config specifies that plugin should be paused.
|
|
252
|
-
if (this.#config.pause === true) {
|
|
253
|
-
this.#log.warn('Plugin is now paused and all related accessories will no longer respond.');
|
|
254
|
-
|
|
255
|
-
return;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
// If the config specifies that plugin should be reset.
|
|
259
|
-
if (this.#config.reset === true) {
|
|
260
|
-
this.#log.warn('Plugin is now removing all related accessories from Homebridge ...');
|
|
261
|
-
|
|
262
|
-
// Remove all related accessories from Homebridge.
|
|
263
|
-
for (let i = this.accessories.length - 1; i >= 0; i -= 1) {
|
|
264
|
-
this.removeAccessory(this.accessories[i]);
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
return;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
// Start synchronization with the portal.
|
|
271
|
-
this.synchronize();
|
|
272
|
-
});
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
/**
|
|
276
|
-
* ADT Pulse Platform - Configure accessory.
|
|
277
|
-
*
|
|
278
|
-
* @param {ADTPulsePlatformConfigureAccessoryAccessory} accessory - Accessory.
|
|
279
|
-
*
|
|
280
|
-
* @returns {ADTPulsePlatformConfigureAccessoryReturns}
|
|
281
|
-
*
|
|
282
|
-
* @since 1.0.0
|
|
283
|
-
*/
|
|
284
|
-
configureAccessory(accessory: ADTPulsePlatformConfigureAccessoryAccessory): ADTPulsePlatformConfigureAccessoryReturns {
|
|
285
|
-
this.#log.info(`Configuring cached accessory for ${chalk.underline(accessory.context.name)} (id: ${accessory.context.id}, uuid: ${accessory.context.uuid}) ...`);
|
|
286
|
-
|
|
287
|
-
// Add the restored accessory to the accessories cache.
|
|
288
|
-
this.accessories.push(accessory);
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
/**
|
|
292
|
-
* ADT Pulse Platform - Add accessory.
|
|
293
|
-
*
|
|
294
|
-
* @param {ADTPulsePlatformAddAccessoryDevice} device - Device.
|
|
295
|
-
*
|
|
296
|
-
* @returns {ADTPulsePlatformAddAccessoryReturns}
|
|
297
|
-
*
|
|
298
|
-
* @since 1.0.0
|
|
299
|
-
*/
|
|
300
|
-
addAccessory(device: ADTPulsePlatformAddAccessoryDevice): ADTPulsePlatformAddAccessoryReturns {
|
|
301
|
-
const accessoryIndex = this.accessories.findIndex((accessory) => device.uuid === accessory.context.uuid);
|
|
302
|
-
|
|
303
|
-
// Prevent adding duplicate accessory.
|
|
304
|
-
if (accessoryIndex >= 0) {
|
|
305
|
-
this.#log.error(`Cannot add ${chalk.underline(device.name)} (id: ${device.id}, uuid: ${device.uuid}) accessory that already exists.`);
|
|
306
|
-
|
|
307
|
-
return;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
// If API instance is not available.
|
|
311
|
-
if (this.#instance === null) {
|
|
312
|
-
this.#log.error(`Attempted to add ${chalk.underline(device.name)} (id: ${device.id}, uuid: ${device.uuid}) accessory but API instance is not available.`);
|
|
313
|
-
|
|
314
|
-
return;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
// Create the new accessory without context.
|
|
318
|
-
const newAccessory = new this.#api.platformAccessory(
|
|
319
|
-
device.name,
|
|
320
|
-
device.uuid,
|
|
321
|
-
getAccessoryCategory(device.category),
|
|
322
|
-
);
|
|
323
|
-
|
|
324
|
-
// Set the context into the new accessory.
|
|
325
|
-
newAccessory.context = device;
|
|
326
|
-
|
|
327
|
-
// Let TypeScript know that the context now exists. This creates additional runtime.
|
|
328
|
-
const typedAccessory = newAccessory as ADTPulsePlatformAddAccessoryTypedNewAccessory;
|
|
329
|
-
|
|
330
|
-
this.#log.info(`Adding ${chalk.underline(typedAccessory.context.name)} (id: ${typedAccessory.context.id}, uuid: ${typedAccessory.context.uuid}) accessory ...`);
|
|
331
|
-
|
|
332
|
-
// Create the handler for the new accessory if it does not exist.
|
|
333
|
-
if (this.#handlers[device.id] === undefined) {
|
|
334
|
-
// All arguments are passed by reference.
|
|
335
|
-
this.#handlers[device.id] = new ADTPulseAccessory(typedAccessory, this.#state, this.#instance, this.#service, this.#characteristic, this.#api, this.#log);
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
// Save the new accessory into the accessories cache.
|
|
339
|
-
this.accessories.push(typedAccessory);
|
|
340
|
-
|
|
341
|
-
this.#api.registerPlatformAccessories(
|
|
342
|
-
'homebridge-adt-pulse',
|
|
343
|
-
'ADTPulse',
|
|
344
|
-
[typedAccessory],
|
|
345
|
-
);
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
/**
|
|
349
|
-
* ADT Pulse Platform - Update accessory.
|
|
350
|
-
*
|
|
351
|
-
* @param {ADTPulsePlatformUpdateAccessoryDevice} device - Device.
|
|
352
|
-
*
|
|
353
|
-
* @returns {ADTPulsePlatformUpdateAccessoryReturns}
|
|
354
|
-
*
|
|
355
|
-
* @since 1.0.0
|
|
356
|
-
*/
|
|
357
|
-
updateAccessory(device: ADTPulsePlatformUpdateAccessoryDevice): ADTPulsePlatformUpdateAccessoryReturns {
|
|
358
|
-
const { index, value } = findIndexWithValue(
|
|
359
|
-
this.accessories,
|
|
360
|
-
(accessory) => device.uuid === accessory.context.uuid,
|
|
361
|
-
);
|
|
362
|
-
|
|
363
|
-
if (index < 0 || value === undefined) {
|
|
364
|
-
this.#log.warn(`Attempted to update ${chalk.underline(device.name)} (id: ${device.id}, uuid: ${device.uuid}) accessory that does not exist.`);
|
|
365
|
-
|
|
366
|
-
return;
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
// If API instance is not available.
|
|
370
|
-
if (this.#instance === null) {
|
|
371
|
-
this.#log.error(`Attempted to updated ${chalk.underline(device.name)} (id: ${device.id}, uuid: ${device.uuid}) accessory but API instance is not available.`);
|
|
372
|
-
|
|
373
|
-
return;
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
this.#log.debug(`Updating ${chalk.underline(value.context.name)} (id: ${value.context.id}, uuid: ${value.context.uuid}) accessory ...`);
|
|
377
|
-
|
|
378
|
-
// Set the context into the existing accessory.
|
|
379
|
-
value.context = device;
|
|
380
|
-
|
|
381
|
-
// Update the display name.
|
|
382
|
-
value.displayName = device.name;
|
|
383
|
-
|
|
384
|
-
// Create the handler for the existing accessory if it does not exist.
|
|
385
|
-
if (this.#handlers[device.id] === undefined) {
|
|
386
|
-
// All arguments are passed by reference.
|
|
387
|
-
this.#handlers[device.id] = new ADTPulseAccessory(value, this.#state, this.#instance, this.#service, this.#characteristic, this.#api, this.#log);
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
// Update the existing accessory in the accessories cache.
|
|
391
|
-
this.accessories[index] = value;
|
|
392
|
-
|
|
393
|
-
this.#api.updatePlatformAccessories(
|
|
394
|
-
[value],
|
|
395
|
-
);
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
/**
|
|
399
|
-
* ADT Pulse Platform - Remove accessory.
|
|
400
|
-
*
|
|
401
|
-
* @param {ADTPulsePlatformRemoveAccessoryAccessory} accessory - Accessory.
|
|
402
|
-
*
|
|
403
|
-
* @returns {ADTPulsePlatformRemoveAccessoryReturns}
|
|
404
|
-
*
|
|
405
|
-
* @since 1.0.0
|
|
406
|
-
*/
|
|
407
|
-
removeAccessory(accessory: ADTPulsePlatformRemoveAccessoryAccessory): ADTPulsePlatformRemoveAccessoryReturns {
|
|
408
|
-
this.#log.info(`Removing ${chalk.underline(accessory.context.name)} (id: ${accessory.context.id}, uuid: ${accessory.context.uuid}) accessory ...`);
|
|
409
|
-
|
|
410
|
-
// Keep only the accessories in the cache that is not the accessory being removed.
|
|
411
|
-
this.accessories = this.accessories.filter((existingAccessory) => existingAccessory.context.uuid !== accessory.context.uuid);
|
|
412
|
-
|
|
413
|
-
this.#api.unregisterPlatformAccessories(
|
|
414
|
-
'homebridge-adt-pulse',
|
|
415
|
-
'ADTPulse',
|
|
416
|
-
[accessory],
|
|
417
|
-
);
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
/**
|
|
421
|
-
* ADT Pulse Platform - Print system information.
|
|
422
|
-
*
|
|
423
|
-
* @private
|
|
424
|
-
*
|
|
425
|
-
* @returns {ADTPulsePlatformPrintSystemInformationReturns}
|
|
426
|
-
*
|
|
427
|
-
* @since 1.0.0
|
|
428
|
-
*/
|
|
429
|
-
private printSystemInformation(): ADTPulsePlatformPrintSystemInformationReturns {
|
|
430
|
-
const homebridgeVersion = chalk.yellowBright(`v${this.#api.serverVersion}`);
|
|
431
|
-
const nodeVersion = chalk.blueBright(`v${versions.node}`);
|
|
432
|
-
const opensslVersion = chalk.magentaBright(`v${versions.openssl}`);
|
|
433
|
-
const packageVersion = chalk.greenBright(`v${env.npm_package_version ?? '0.1.0'}`);
|
|
434
|
-
const platformPlusArch = chalk.redBright(`${platform} (${arch})`);
|
|
435
|
-
|
|
436
|
-
this.#log.info([
|
|
437
|
-
`running on ${platformPlusArch}`,
|
|
438
|
-
`homebridge-adt-pulse ${packageVersion}`,
|
|
439
|
-
`homebridge ${homebridgeVersion}`,
|
|
440
|
-
`node ${nodeVersion}`,
|
|
441
|
-
`openssl ${opensslVersion}`,
|
|
442
|
-
].join(chalk.gray(' // ')));
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
/**
|
|
446
|
-
* ADT Pulse Platform - Synchronize.
|
|
447
|
-
*
|
|
448
|
-
* @private
|
|
449
|
-
*
|
|
450
|
-
* @returns {ADTPulsePlatformSynchronizeReturns}
|
|
451
|
-
*
|
|
452
|
-
* @since 1.0.0
|
|
453
|
-
*/
|
|
454
|
-
private synchronize(): ADTPulsePlatformSynchronizeReturns {
|
|
455
|
-
this.#state.intervals.synchronize = setInterval(async () => {
|
|
456
|
-
// If currently syncing.
|
|
457
|
-
if (this.#state.activity.isSyncing) {
|
|
458
|
-
return;
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
// Checks for a "null" instance. Just in case it happens.
|
|
462
|
-
if (this.#instance === null) {
|
|
463
|
-
this.#log.warn('synchronize() was called but API is not available.');
|
|
464
|
-
|
|
465
|
-
return;
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
// Attempt to synchronize.
|
|
469
|
-
try {
|
|
470
|
-
// ACTIVITY: Start sync.
|
|
471
|
-
this.#state.activity.isSyncing = true;
|
|
472
|
-
|
|
473
|
-
// Perform login action if "this instance" is not authenticated.
|
|
474
|
-
if (!this.#instance.isAuthenticated()) {
|
|
475
|
-
// Attempt to log in if "this instance" is not currently logging in.
|
|
476
|
-
if (!this.#state.activity.isLoggingIn) {
|
|
477
|
-
// ACTIVITY: Start login.
|
|
478
|
-
this.#state.activity.isLoggingIn = true;
|
|
479
|
-
|
|
480
|
-
const login = await this.#instance.login();
|
|
481
|
-
|
|
482
|
-
// If login was successful.
|
|
483
|
-
if (login.success) {
|
|
484
|
-
const currentTimestamp = Date.now();
|
|
485
|
-
|
|
486
|
-
// Update timing for the sync protocols, so they can pace themselves.
|
|
487
|
-
this.#state.lastRunOn.adtKeepAlive = currentTimestamp;
|
|
488
|
-
this.#state.lastRunOn.adtSyncCheck = currentTimestamp;
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
// If login was not successful.
|
|
492
|
-
if (!login.success) {
|
|
493
|
-
this.#state.eventCounters.failedLogins += 1;
|
|
494
|
-
|
|
495
|
-
const attemptsLeft = this.#constants.maxLoginRetries - this.#state.eventCounters.failedLogins;
|
|
496
|
-
|
|
497
|
-
if (attemptsLeft > 0) {
|
|
498
|
-
this.#log.error(`Login attempt has failed. Trying ${attemptsLeft} more ${getPluralForm(attemptsLeft, 'time', 'times')} ...`);
|
|
499
|
-
} else {
|
|
500
|
-
const suspendMinutes = this.#constants.intervalTimestamps.suspendSyncing / 1000 / 60;
|
|
501
|
-
|
|
502
|
-
this.#log.error(`Login attempt has failed for ${this.#constants.maxLoginRetries} ${getPluralForm(this.#constants.maxLoginRetries, 'time', 'times')}. Sleeping for ${suspendMinutes} ${getPluralForm(suspendMinutes, 'minute', 'minutes')} before resuming ...`);
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
stackTracer('api-response', login);
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
// ACTIVITY: Finish login.
|
|
509
|
-
this.#state.activity.isLoggingIn = false;
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
// If failed logins have reached the max login retries.
|
|
513
|
-
if (this.#state.eventCounters.failedLogins >= this.#constants.maxLoginRetries) {
|
|
514
|
-
await sleep(this.#constants.intervalTimestamps.suspendSyncing);
|
|
515
|
-
|
|
516
|
-
// After sleeping, reset the failed login count.
|
|
517
|
-
this.#state.eventCounters.failedLogins = 0;
|
|
518
|
-
|
|
519
|
-
return;
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
// Make sure the rest of the code does not run if user is not authenticated.
|
|
523
|
-
if (!this.#instance.isAuthenticated()) {
|
|
524
|
-
return;
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
// Get the current timestamp.
|
|
529
|
-
const currentTimestamp = Date.now();
|
|
530
|
-
|
|
531
|
-
// Run the keep alive request if time has reached. Do not await, they shall run at their own pace.
|
|
532
|
-
if (currentTimestamp - this.#state.lastRunOn.adtKeepAlive >= this.#constants.intervalTimestamps.adtKeepAlive) {
|
|
533
|
-
this.synchronizeKeepAlive();
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
// Run the sync check request if time has reached. Do not await, they shall run at their own pace.
|
|
537
|
-
if (currentTimestamp - this.#state.lastRunOn.adtSyncCheck >= this.#constants.intervalTimestamps.adtSyncCheck) {
|
|
538
|
-
this.synchronizeSyncCheck();
|
|
539
|
-
}
|
|
540
|
-
} catch (error) {
|
|
541
|
-
this.#log.error('synchronize() has unexpectedly thrown an error, will continue to sync.');
|
|
542
|
-
stackTracer('serialize-error', serializeError(error));
|
|
543
|
-
} finally {
|
|
544
|
-
// ACTIVITY: Finish sync.
|
|
545
|
-
this.#state.activity.isSyncing = false;
|
|
546
|
-
}
|
|
547
|
-
}, this.#constants.intervalTimestamps.synchronize);
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
/**
|
|
551
|
-
* ADT Pulse Platform - Synchronize keep alive.
|
|
552
|
-
*
|
|
553
|
-
* @private
|
|
554
|
-
*
|
|
555
|
-
* @returns {ADTPulsePlatformSynchronizeKeepAliveReturns}
|
|
556
|
-
*
|
|
557
|
-
* @since 1.0.0
|
|
558
|
-
*/
|
|
559
|
-
private synchronizeKeepAlive(): ADTPulsePlatformSynchronizeKeepAliveReturns {
|
|
560
|
-
// Running an IIFE, to internalize async context.
|
|
561
|
-
(async () => {
|
|
562
|
-
// If currently keeping alive.
|
|
563
|
-
if (this.#state.activity.isAdtKeepingAlive) {
|
|
564
|
-
return;
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
// Checks for a "null" instance. Just in case it happens.
|
|
568
|
-
if (this.#instance === null) {
|
|
569
|
-
this.#log.warn('synchronizeKeepAlive() was called but API instance is not available.');
|
|
570
|
-
|
|
571
|
-
return;
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
// Attempt to keep alive.
|
|
575
|
-
try {
|
|
576
|
-
// ACTIVITY: Start keeping alive.
|
|
577
|
-
this.#state.activity.isAdtKeepingAlive = true;
|
|
578
|
-
|
|
579
|
-
const keepAlive = await this.#instance.performKeepAlive();
|
|
580
|
-
|
|
581
|
-
// If keeping alive was successful.
|
|
582
|
-
if (keepAlive.success) {
|
|
583
|
-
this.#log.debug('Keep alive request was successful. The login session should now be extended.');
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
// If keeping alive was not successful.
|
|
587
|
-
if (!keepAlive.success) {
|
|
588
|
-
this.#log.error('Keeping alive attempt has failed. Trying again later.');
|
|
589
|
-
stackTracer('api-response', keepAlive);
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
// Update timestamp for keep alive request, even if request failed.
|
|
593
|
-
this.#state.lastRunOn.adtKeepAlive = Date.now();
|
|
594
|
-
} catch (error) {
|
|
595
|
-
this.#log.error('synchronizeKeepAlive() has unexpectedly thrown an error, will continue to keep alive.');
|
|
596
|
-
stackTracer('serialize-error', serializeError(error));
|
|
597
|
-
} finally {
|
|
598
|
-
// ACTIVITY: Finish keeping alive.
|
|
599
|
-
this.#state.activity.isAdtKeepingAlive = false;
|
|
600
|
-
}
|
|
601
|
-
})();
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
/**
|
|
605
|
-
* ADT Pulse Platform - Synchronize sync check.
|
|
606
|
-
*
|
|
607
|
-
* @private
|
|
608
|
-
*
|
|
609
|
-
* @returns {ADTPulsePlatformSynchronizeSyncCheckReturns}
|
|
610
|
-
*
|
|
611
|
-
* @since 1.0.0
|
|
612
|
-
*/
|
|
613
|
-
private synchronizeSyncCheck(): ADTPulsePlatformSynchronizeSyncCheckReturns {
|
|
614
|
-
// Running an IIFE, to internalize async context.
|
|
615
|
-
(async () => {
|
|
616
|
-
// If currently sync checking.
|
|
617
|
-
if (this.#state.activity.isAdtSyncChecking) {
|
|
618
|
-
return;
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
// Checks for a "null" instance. Just in case it happens.
|
|
622
|
-
if (this.#instance === null) {
|
|
623
|
-
this.#log.warn('synchronizeSyncCheck() was called but API instance is not available.');
|
|
624
|
-
|
|
625
|
-
return;
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
// Attempt to sync check.
|
|
629
|
-
try {
|
|
630
|
-
// ACTIVITY: Start sync checking.
|
|
631
|
-
this.#state.activity.isAdtSyncChecking = true;
|
|
632
|
-
|
|
633
|
-
const syncCheck = await this.#instance.performSyncCheck();
|
|
634
|
-
|
|
635
|
-
// If sync checking was successful.
|
|
636
|
-
if (syncCheck.success) {
|
|
637
|
-
this.#log.debug('Sync check request was successful. Determining if panel and sensor data is outdated ...');
|
|
638
|
-
|
|
639
|
-
// If new sync code is different from the cached sync code.
|
|
640
|
-
if (syncCheck.info.syncCode !== this.#state.data.syncCode) {
|
|
641
|
-
this.#log.debug(`Panel and sensor data is outdated (old: ${this.#state.data.syncCode}, new: ${syncCheck.info.syncCode}). Preparing to retrieve the latest panel and sensor data ...`);
|
|
642
|
-
|
|
643
|
-
// Cache the sync code.
|
|
644
|
-
this.#state.data.syncCode = syncCheck.info.syncCode;
|
|
645
|
-
|
|
646
|
-
// Request new data from the portal. Should be awaited.
|
|
647
|
-
await this.fetchUpdatedInformation();
|
|
648
|
-
}
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
// If sync checking was not successful.
|
|
652
|
-
if (!syncCheck.success) {
|
|
653
|
-
const { error } = syncCheck.info;
|
|
654
|
-
const { message } = error ?? {};
|
|
655
|
-
|
|
656
|
-
// Determine if the message is related to a minor connection issue.
|
|
657
|
-
if (message !== undefined) {
|
|
658
|
-
switch (true) {
|
|
659
|
-
case message.includes('ECONNABORTED'):
|
|
660
|
-
this.#log.debug('Sync checking attempt has failed because the connection timed out. Trying again later.');
|
|
661
|
-
break;
|
|
662
|
-
case message.includes('ECONNRESET'):
|
|
663
|
-
this.#log.debug('Sync checking attempt has failed because the connection was reset. Trying again later.');
|
|
664
|
-
break;
|
|
665
|
-
default:
|
|
666
|
-
break;
|
|
667
|
-
}
|
|
668
|
-
} else {
|
|
669
|
-
this.#log.error('Sync checking attempt has failed. Trying again later.');
|
|
670
|
-
stackTracer('api-response', syncCheck);
|
|
671
|
-
}
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
// Update timestamp for sync check request, even if request failed.
|
|
675
|
-
this.#state.lastRunOn.adtSyncCheck = Date.now();
|
|
676
|
-
} catch (error) {
|
|
677
|
-
this.#log.error('synchronizeSyncCheck() has unexpectedly thrown an error, will continue to sync check.');
|
|
678
|
-
stackTracer('serialize-error', serializeError(error));
|
|
679
|
-
} finally {
|
|
680
|
-
// ACTIVITY: Finish sync checking.
|
|
681
|
-
this.#state.activity.isAdtSyncChecking = false;
|
|
682
|
-
}
|
|
683
|
-
})();
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
/**
|
|
687
|
-
* ADT Pulse Platform - Fetch updated information.
|
|
688
|
-
*
|
|
689
|
-
* @private
|
|
690
|
-
*
|
|
691
|
-
* @returns {ADTPulsePlatformFetchUpdatedInformationReturns}
|
|
692
|
-
*
|
|
693
|
-
* @since 1.0.0
|
|
694
|
-
*/
|
|
695
|
-
private async fetchUpdatedInformation(): ADTPulsePlatformFetchUpdatedInformationReturns {
|
|
696
|
-
// Checks for a "null" instance. Just in case it happens.
|
|
697
|
-
if (this.#instance === null) {
|
|
698
|
-
this.#log.warn('fetchUpdatedInformation() was called but API instance is not available.');
|
|
699
|
-
|
|
700
|
-
return;
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
try {
|
|
704
|
-
// Fetch all the panel and sensor information.
|
|
705
|
-
const requests = await Promise.all([
|
|
706
|
-
this.#instance.getGatewayInformation(),
|
|
707
|
-
this.#instance.getPanelInformation(),
|
|
708
|
-
this.#instance.getPanelStatus(),
|
|
709
|
-
this.#instance.getSensorsInformation(),
|
|
710
|
-
this.#instance.getSensorsStatus(),
|
|
711
|
-
]);
|
|
712
|
-
|
|
713
|
-
// Update gateway information.
|
|
714
|
-
if (requests[0].success) {
|
|
715
|
-
const { info } = requests[0];
|
|
716
|
-
|
|
717
|
-
// Set gateway information into memory.
|
|
718
|
-
this.#state.data.gatewayInfo = info;
|
|
719
|
-
}
|
|
720
|
-
|
|
721
|
-
// Update panel information.
|
|
722
|
-
if (requests[1].success) {
|
|
723
|
-
const { info } = requests[1];
|
|
724
|
-
|
|
725
|
-
// Set panel information into memory.
|
|
726
|
-
this.#state.data.panelInfo = info;
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
// Update panel status.
|
|
730
|
-
if (requests[2].success) {
|
|
731
|
-
const { info } = requests[2];
|
|
732
|
-
|
|
733
|
-
// Set panel status into memory.
|
|
734
|
-
this.#state.data.panelStatus = info;
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
// Update sensors information.
|
|
738
|
-
if (requests[3].success) {
|
|
739
|
-
const { sensors } = requests[3].info;
|
|
740
|
-
|
|
741
|
-
// Set sensors information into memory.
|
|
742
|
-
this.#state.data.sensorsInfo = sensors;
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
// Update sensors status.
|
|
746
|
-
if (requests[4].success) {
|
|
747
|
-
const { sensors } = requests[4].info;
|
|
748
|
-
|
|
749
|
-
// Set sensors status into memory.
|
|
750
|
-
this.#state.data.sensorsStatus = sensors;
|
|
751
|
-
}
|
|
752
|
-
|
|
753
|
-
// Consolidate devices first, then update them all.
|
|
754
|
-
await this.unifyDevices();
|
|
755
|
-
} catch (error) {
|
|
756
|
-
this.#log.error('fetchUpdatedInformation() has unexpectedly thrown an error, will continue to fetch.');
|
|
757
|
-
stackTracer('serialize-error', serializeError(error));
|
|
758
|
-
}
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
/**
|
|
762
|
-
* ADT Pulse Platform - Unify devices.
|
|
763
|
-
*
|
|
764
|
-
* @private
|
|
765
|
-
*
|
|
766
|
-
* @returns {ADTPulsePlatformUnifyDevicesReturns}
|
|
767
|
-
*
|
|
768
|
-
* @since 1.0.0
|
|
769
|
-
*/
|
|
770
|
-
private async unifyDevices(): ADTPulsePlatformUnifyDevicesReturns {
|
|
771
|
-
const { gatewayInfo, panelInfo, sensorsInfo } = this.#state.data;
|
|
772
|
-
|
|
773
|
-
const devices: ADTPulsePlatformUnifyDevicesDevices = [];
|
|
774
|
-
|
|
775
|
-
// Add gateway as an accessory.
|
|
776
|
-
if (gatewayInfo !== null) {
|
|
777
|
-
const id = 'adt-device-0';
|
|
778
|
-
|
|
779
|
-
devices.push({
|
|
780
|
-
id,
|
|
781
|
-
name: 'ADT Pulse Gateway',
|
|
782
|
-
type: 'gateway',
|
|
783
|
-
zone: null,
|
|
784
|
-
category: 'OTHER',
|
|
785
|
-
manufacturer: gatewayInfo.manufacturer,
|
|
786
|
-
model: gatewayInfo.model,
|
|
787
|
-
serial: gatewayInfo.serialNumber,
|
|
788
|
-
firmware: gatewayInfo.versions.firmware,
|
|
789
|
-
hardware: gatewayInfo.versions.hardware,
|
|
790
|
-
software: env.npm_package_version ?? '0.1.0',
|
|
791
|
-
uuid: this.#api.hap.uuid.generate(id),
|
|
792
|
-
});
|
|
793
|
-
}
|
|
794
|
-
|
|
795
|
-
// Add security panel as an accessory.
|
|
796
|
-
if (panelInfo !== null) {
|
|
797
|
-
const id = 'adt-device-1';
|
|
798
|
-
|
|
799
|
-
devices.push({
|
|
800
|
-
id,
|
|
801
|
-
name: 'Security Panel',
|
|
802
|
-
type: 'panel',
|
|
803
|
-
zone: null,
|
|
804
|
-
category: 'SECURITY_SYSTEM',
|
|
805
|
-
manufacturer: panelInfo.manufacturer,
|
|
806
|
-
model: panelInfo.model,
|
|
807
|
-
serial: 'N/A',
|
|
808
|
-
firmware: null,
|
|
809
|
-
hardware: null,
|
|
810
|
-
software: env.npm_package_version ?? '0.1.0',
|
|
811
|
-
uuid: this.#api.hap.uuid.generate(id),
|
|
812
|
-
});
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
// Add sensors as an accessory.
|
|
816
|
-
if (this.#config !== null && sensorsInfo !== null) {
|
|
817
|
-
for (let i = 0; i < this.#config.sensors.length; i += 1) {
|
|
818
|
-
const {
|
|
819
|
-
adtName,
|
|
820
|
-
adtType,
|
|
821
|
-
adtZone,
|
|
822
|
-
name,
|
|
823
|
-
} = this.#config.sensors[i];
|
|
824
|
-
|
|
825
|
-
const sensor = sensorsInfo.find((sensorInfo) => {
|
|
826
|
-
const sensorInfoName = sensorInfo.name;
|
|
827
|
-
const sensorInfoType = condenseSensorType(sensorInfo.deviceType);
|
|
828
|
-
const sensorInfoZone = sensorInfo.zone;
|
|
829
|
-
|
|
830
|
-
return (
|
|
831
|
-
adtName === sensorInfoName
|
|
832
|
-
&& adtType === sensorInfoType
|
|
833
|
-
&& adtZone === sensorInfoZone
|
|
834
|
-
);
|
|
835
|
-
});
|
|
836
|
-
|
|
837
|
-
// If sensor was not found, it could be that the config was wrong.
|
|
838
|
-
if (sensor === undefined) {
|
|
839
|
-
this.#log.warn(`Attempted to add or update ${adtName} (zone: ${adtZone}) accessory that does not exist on the portal.`);
|
|
840
|
-
|
|
841
|
-
continue;
|
|
842
|
-
}
|
|
843
|
-
|
|
844
|
-
const id = `adt-device-${sensor.deviceId}` as ADTPulsePlatformUnifyDevicesId;
|
|
845
|
-
|
|
846
|
-
devices.push({
|
|
847
|
-
id,
|
|
848
|
-
name: name ?? adtName,
|
|
849
|
-
type: adtType,
|
|
850
|
-
zone: adtZone,
|
|
851
|
-
category: 'SENSOR',
|
|
852
|
-
manufacturer: 'ADT',
|
|
853
|
-
model: sensor.deviceType,
|
|
854
|
-
serial: null,
|
|
855
|
-
firmware: null,
|
|
856
|
-
hardware: null,
|
|
857
|
-
software: env.npm_package_version ?? '0.1.0',
|
|
858
|
-
uuid: this.#api.hap.uuid.generate(id),
|
|
859
|
-
});
|
|
860
|
-
}
|
|
861
|
-
}
|
|
862
|
-
|
|
863
|
-
// Now poll the accessories using the generated devices.
|
|
864
|
-
await this.pollAccessories(devices);
|
|
865
|
-
}
|
|
866
|
-
|
|
867
|
-
/**
|
|
868
|
-
* ADT Pulse Platform - Poll accessories.
|
|
869
|
-
*
|
|
870
|
-
* @param {ADTPulsePlatformPollAccessoriesDevices} devices - Devices.
|
|
871
|
-
*
|
|
872
|
-
* @private
|
|
873
|
-
*
|
|
874
|
-
* @returns {ADTPulsePlatformPollAccessoriesReturns}
|
|
875
|
-
*
|
|
876
|
-
* @since 1.0.0
|
|
877
|
-
*/
|
|
878
|
-
private async pollAccessories(devices: ADTPulsePlatformPollAccessoriesDevices): ADTPulsePlatformPollAccessoriesReturns {
|
|
879
|
-
for (let i = 0; i < devices.length; i += 1) {
|
|
880
|
-
const accessoryIndex = this.accessories.findIndex((accessory) => devices[i].uuid === accessory.context.uuid);
|
|
881
|
-
|
|
882
|
-
// Update the device if accessory is cached, otherwise add it as a new device.
|
|
883
|
-
if (accessoryIndex >= 0) {
|
|
884
|
-
this.updateAccessory(devices[i]);
|
|
885
|
-
} else {
|
|
886
|
-
this.addAccessory(devices[i]);
|
|
887
|
-
}
|
|
888
|
-
}
|
|
889
|
-
}
|
|
890
|
-
}
|