homebridge-securitysystem 7.2.1 → 7.4.0

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/src/index.js CHANGED
@@ -1,2396 +1,2418 @@
1
- const fs = require("fs");
2
- const path = require("path");
3
- const storage = require("node-persist");
4
- const { spawn } = require("child_process");
5
- const fetch = require("node-fetch");
6
- const express = require("express");
7
- const rateLimit = require("express-rate-limit");
8
-
9
- const packageJson = require("../package.json");
10
- const options = require("./utils/options.js");
11
-
12
- // HomeKit error
13
- const HK_NOT_ALLOWED_IN_CURRENT_STATE = -70412;
14
-
15
- const originTypes = {
16
- REGULAR_SWITCH: 0,
17
- SPECIAL_SWITCH: 1,
18
- INTERNAL: 3,
19
- EXTERNAL: 4,
20
- };
21
-
22
- const app = express();
23
- let Service, Characteristic, storagePath;
24
-
25
- module.exports = function (homebridge) {
26
- Service = homebridge.hap.Service;
27
- Characteristic = homebridge.hap.Characteristic;
28
- storagePath = homebridge.user.storagePath();
29
-
30
- homebridge.registerAccessory(
31
- "homebridge-securitysystem",
32
- "security-system",
33
- SecuritySystem
34
- );
35
- };
36
-
37
- function SecuritySystem(log, config) {
38
- this.log = log;
39
- options.init(log, config);
40
-
41
- this.defaultState = this.mode2State(options.defaultMode);
42
- this.currentState = this.defaultState;
43
- this.targetState = this.defaultState;
44
- this.availableTargetStates = null;
45
-
46
- this.isArming = false;
47
- this.isKnocked = false;
48
-
49
- this.invalidCodeCount = 0;
50
-
51
- this.pausedCurrentState = null;
52
- this.audioProcess = null;
53
-
54
- this.armTimeout = null;
55
- this.pauseTimeout = null;
56
- this.triggerTimeout = null;
57
- this.doubleKnockTimeout = null;
58
- this.resetTimeout = null;
59
-
60
- this.trippedMotionSensorInterval = null;
61
- this.triggeredMotionSensorInterval = null;
62
-
63
- // File logger
64
- if (options.isValueSet(options.logDirectory)) {
65
- const logInfo = this.log.info.bind(this.log);
66
- const logWarn = this.log.warn.bind(this.log);
67
- const logError = this.log.error.bind(this.log);
68
-
69
- this.log.info = (message) => {
70
- logInfo.apply(null, [message]);
71
- this.log.appendFile(message);
72
- };
73
-
74
- this.log.warn = (message) => {
75
- logWarn.apply(null, [message]);
76
- this.log.appendFile(message);
77
- };
78
-
79
- this.log.error = (message) => {
80
- logError.apply(null, [message]);
81
- this.log.appendFile(message);
82
- };
83
-
84
- this.log.appendFile = async (message) => {
85
- const date = new Date();
86
-
87
- try {
88
- const stats = await fs.promises.stat(
89
- `${options.logDirectory}/securitysystem.log`
90
- );
91
-
92
- if (
93
- stats.birthtime.toLocaleDateString() !== date.toLocaleDateString()
94
- ) {
95
- await fs.promises.rename(
96
- `${options.logDirectory}/securitysystem.log`,
97
- `${options.logDirectory}/securitysystem-${stats.birthtime
98
- .toLocaleDateString()
99
- .replaceAll("/", "-")}.log`
100
- );
101
- }
102
- } catch (error) {
103
- this.log.debug("Previous log file not found.");
104
- }
105
-
106
- try {
107
- await fs.promises.appendFile(
108
- `${options.logDirectory}/securitysystem.log`,
109
- `[${new Date().toLocaleString()}] ${message}\n`,
110
- { flag: "a" }
111
- );
112
- } catch (error) {
113
- logError("File logger (Error)");
114
- logError(error);
115
- }
116
- };
117
- }
118
-
119
- // Log
120
- if (options.testMode) {
121
- this.log.warn("Test Mode");
122
- }
123
-
124
- this.logMode("Default", this.defaultState);
125
- this.log.info(`Arm delay (${options.armSeconds} second/s)`);
126
- this.log.info(`Trigger delay (${options.triggerSeconds} second/s)`);
127
- this.log.info(`Audio (${options.audio ? "Enabled" : "Disabled"})`);
128
-
129
- if (options.proxyMode) {
130
- this.log.info("Proxy mode (Enabled)");
131
- }
132
-
133
- if (options.isValueSet(options.webhookUrl)) {
134
- this.log.info(`Webhook (${options.webhookUrl})`);
135
- }
136
-
137
- // Security system
138
- this.service = new Service.SecuritySystem(options.securitySystemName);
139
- this.availableTargetStates = this.getAvailableTargetStates();
140
-
141
- this.service.getCharacteristic(
142
- Characteristic.SecuritySystemTargetState
143
- ).value = this.targetState;
144
-
145
- this.service.addCharacteristic(Characteristic.ConfiguredName);
146
-
147
- this.service
148
- .setCharacteristic(
149
- Characteristic.ConfiguredName,
150
- options.securitySystemName
151
- )
152
- .getCharacteristic(Characteristic.SecuritySystemTargetState)
153
- .setProps({ validValues: this.availableTargetStates })
154
- .on("get", this.getTargetState.bind(this))
155
- .on("set", this.setTargetState.bind(this));
156
-
157
- this.service.getCharacteristic(
158
- Characteristic.SecuritySystemCurrentState
159
- ).value = this.currentState;
160
-
161
- this.service
162
- .getCharacteristic(Characteristic.SecuritySystemCurrentState)
163
- .on("get", this.getCurrentState.bind(this));
164
-
165
- // Trip switches
166
- this.tripSwitchService = new Service.Switch(
167
- options.tripSwitchName,
168
- "siren-switch"
169
- );
170
-
171
- this.tripSwitchService.addCharacteristic(Characteristic.ConfiguredName);
172
-
173
- this.tripSwitchService
174
- .setCharacteristic(Characteristic.ConfiguredName, options.tripSwitchName)
175
- .getCharacteristic(Characteristic.On)
176
- .on("get", this.getTripSwitch.bind(this))
177
- .on("set", this.setTripSwitch.bind(this));
178
-
179
- this.tripHomeSwitchService = new Service.Switch(
180
- options.tripHomeSwitchName,
181
- "siren-home"
182
- );
183
-
184
- this.tripHomeSwitchService.addCharacteristic(Characteristic.ConfiguredName);
185
-
186
- this.tripHomeSwitchService
187
- .setCharacteristic(
188
- Characteristic.ConfiguredName,
189
- options.tripHomeSwitchName
190
- )
191
- .getCharacteristic(Characteristic.On)
192
- .on("get", this.getTripHomeSwitch.bind(this))
193
- .on("set", this.setTripHomeSwitch.bind(this));
194
-
195
- this.tripAwaySwitchService = new Service.Switch(
196
- options.tripAwaySwitchName,
197
- "siren-away"
198
- );
199
-
200
- this.tripAwaySwitchService.addCharacteristic(Characteristic.ConfiguredName);
201
-
202
- this.tripAwaySwitchService
203
- .setCharacteristic(
204
- Characteristic.ConfiguredName,
205
- options.tripAwaySwitchName
206
- )
207
- .getCharacteristic(Characteristic.On)
208
- .on("get", this.getTripAwaySwitch.bind(this))
209
- .on("set", this.setTripAwaySwitch.bind(this));
210
-
211
- this.tripNightSwitchService = new Service.Switch(
212
- options.tripNightSwitchName,
213
- "siren-night"
214
- );
215
-
216
- this.tripNightSwitchService.addCharacteristic(Characteristic.ConfiguredName);
217
-
218
- this.tripNightSwitchService
219
- .setCharacteristic(
220
- Characteristic.ConfiguredName,
221
- options.tripNightSwitchName
222
- )
223
- .getCharacteristic(Characteristic.On)
224
- .on("get", this.getTripNightSwitch.bind(this))
225
- .on("set", this.setTripNightSwitch.bind(this));
226
-
227
- this.tripOverrideSwitchService = new Service.Switch(
228
- options.tripOverrideSwitchName,
229
- "pink-sheep"
230
- );
231
-
232
- this.tripOverrideSwitchService.addCharacteristic(
233
- Characteristic.ConfiguredName
234
- );
235
-
236
- this.tripOverrideSwitchService
237
- .setCharacteristic(
238
- Characteristic.ConfiguredName,
239
- options.tripOverrideSwitchName
240
- )
241
- .getCharacteristic(Characteristic.On)
242
- .on("get", this.getTripOverrideSwitch.bind(this))
243
- .on("set", this.setTripOverrideSwitch.bind(this));
244
-
245
- // Arming lock switches
246
- this.armingLockSwitchService = new Service.Switch(
247
- "Arming Lock",
248
- "arming-lock"
249
- );
250
-
251
- this.armingLockSwitchService.addCharacteristic(Characteristic.ConfiguredName);
252
-
253
- this.armingLockSwitchService
254
- .setCharacteristic(Characteristic.ConfiguredName, "Arming Lock")
255
- .getCharacteristic(Characteristic.On)
256
- .on("get", this.getArmingLockSwitch.bind(this))
257
- .on("set", this.setArmingLockSwitch.bind(this));
258
-
259
- this.armingLockHomeSwitchService = new Service.Switch(
260
- "Arming Lock Home",
261
- "arming-lock-home"
262
- );
263
-
264
- this.armingLockHomeSwitchService.addCharacteristic(
265
- Characteristic.ConfiguredName
266
- );
267
-
268
- this.armingLockHomeSwitchService
269
- .setCharacteristic(Characteristic.ConfiguredName, "Arming Lock Home")
270
- .getCharacteristic(Characteristic.On)
271
- .on("get", this.getArmingLockHomeSwitch.bind(this))
272
- .on("set", this.setArmingLockHomeSwitch.bind(this));
273
-
274
- this.armingLockAwaySwitchService = new Service.Switch(
275
- "Arming Lock Away",
276
- "arming-lock-away"
277
- );
278
-
279
- this.armingLockAwaySwitchService.addCharacteristic(
280
- Characteristic.ConfiguredName
281
- );
282
-
283
- this.armingLockAwaySwitchService
284
- .setCharacteristic(Characteristic.ConfiguredName, "Arming Lock Away")
285
- .getCharacteristic(Characteristic.On)
286
- .on("get", this.getArmingLockAwaySwitch.bind(this))
287
- .on("set", this.setArmingLockAwaySwitch.bind(this));
288
-
289
- this.armingLockNightSwitchService = new Service.Switch(
290
- "Arming Lock Night",
291
- "arming-lock-night"
292
- );
293
-
294
- this.armingLockNightSwitchService.addCharacteristic(
295
- Characteristic.ConfiguredName
296
- );
297
-
298
- this.armingLockNightSwitchService
299
- .setCharacteristic(Characteristic.ConfiguredName, "Arming Lock Night")
300
- .getCharacteristic(Characteristic.On)
301
- .on("get", this.getArmingLockNightSwitch.bind(this))
302
- .on("set", this.setArmingLockNightSwitch.bind(this));
303
-
304
- // Mode switches
305
- this.modeHomeSwitchService = new Service.Switch("Mode Home", "mode-home");
306
- this.modeHomeSwitchService.addCharacteristic(Characteristic.ConfiguredName);
307
-
308
- this.modeHomeSwitchService
309
- .setCharacteristic(Characteristic.ConfiguredName, "Mode Home")
310
- .getCharacteristic(Characteristic.On)
311
- .on("get", this.getModeHomeSwitch.bind(this))
312
- .on("set", this.setModeHomeSwitch.bind(this));
313
-
314
- this.modeAwaySwitchService = new Service.Switch("Mode Away", "mode-away");
315
- this.modeAwaySwitchService.addCharacteristic(Characteristic.ConfiguredName);
316
-
317
- this.modeAwaySwitchService
318
- .setCharacteristic(Characteristic.ConfiguredName, "Mode Away")
319
- .getCharacteristic(Characteristic.On)
320
- .on("get", this.getModeAwaySwitch.bind(this))
321
- .on("set", this.setModeAwaySwitch.bind(this));
322
-
323
- this.modeNightSwitchService = new Service.Switch("Mode Night", "mode-night");
324
- this.modeNightSwitchService.addCharacteristic(Characteristic.ConfiguredName);
325
-
326
- this.modeNightSwitchService
327
- .setCharacteristic(Characteristic.ConfiguredName, "Mode Night")
328
- .getCharacteristic(Characteristic.On)
329
- .on("get", this.getModeNightSwitch.bind(this))
330
- .on("set", this.setModeNightSwitch.bind(this));
331
-
332
- this.modeOffSwitchService = new Service.Switch("Mode Off", "mode-off");
333
- this.modeOffSwitchService.addCharacteristic(Characteristic.ConfiguredName);
334
-
335
- this.modeOffSwitchService
336
- .setCharacteristic(Characteristic.ConfiguredName, "Mode Off")
337
- .getCharacteristic(Characteristic.On)
338
- .on("get", this.getModeOffSwitch.bind(this))
339
- .on("set", this.setModeOffSwitch.bind(this));
340
-
341
- this.modeAwayExtendedSwitchService = new Service.Switch(
342
- "Mode Away Extended",
343
- "mode-away-extended"
344
- );
345
-
346
- this.modeAwayExtendedSwitchService.addCharacteristic(
347
- Characteristic.ConfiguredName
348
- );
349
-
350
- this.modeAwayExtendedSwitchService
351
- .setCharacteristic(Characteristic.ConfiguredName, "Mode Away Extended")
352
- .getCharacteristic(Characteristic.On)
353
- .on("get", this.getModeAwayExtendedSwitch.bind(this))
354
- .on("set", this.setModeAwayExtendedSwitch.bind(this));
355
-
356
- this.modePauseSwitchService = new Service.Switch("Mode Pause", "mode-pause");
357
- this.modePauseSwitchService.addCharacteristic(Characteristic.ConfiguredName);
358
-
359
- this.modePauseSwitchService
360
- .setCharacteristic(Characteristic.ConfiguredName, "Mode Pause")
361
- .getCharacteristic(Characteristic.On)
362
- .on("get", this.getModePauseSwitch.bind(this))
363
- .on("set", this.setModePauseSwitch.bind(this));
364
-
365
- // Audio switch
366
- this.audioSwitchService = new Service.Switch(
367
- "Audio",
368
- "kx82r64zN3txDXKFiX9JDi"
369
- );
370
-
371
- this.audioSwitchService.addCharacteristic(Characteristic.ConfiguredName);
372
-
373
- this.audioSwitchService
374
- .setCharacteristic(Characteristic.ConfiguredName, "Audio")
375
- .getCharacteristic(Characteristic.On)
376
- .on("get", this.getAudioSwitch.bind(this))
377
- .on("set", this.setAudioSwitch.bind(this));
378
-
379
- this.audioSwitchService.getCharacteristic(Characteristic.On).value = true;
380
-
381
- // Tripped / Triggered sensors
382
- this.trippedMotionSensorService = new Service.MotionSensor(
383
- "Tripped",
384
- "siren-tripped"
385
- );
386
-
387
- this.trippedMotionSensorService.addCharacteristic(
388
- Characteristic.ConfiguredName
389
- );
390
-
391
- this.trippedMotionSensorService
392
- .setCharacteristic(Characteristic.ConfiguredName, "Tripped")
393
- .getCharacteristic(Characteristic.MotionDetected)
394
- .on("get", this.getTrippedMotionDetected.bind(this));
395
-
396
- this.triggeredMotionSensorService = new Service.MotionSensor(
397
- "Triggered",
398
- "siren-triggered"
399
- );
400
-
401
- this.triggeredMotionSensorService.addCharacteristic(
402
- Characteristic.ConfiguredName
403
- );
404
-
405
- this.triggeredMotionSensorService
406
- .setCharacteristic(Characteristic.ConfiguredName, "Triggered")
407
- .getCharacteristic(Characteristic.MotionDetected)
408
- .on("get", this.getTriggeredMotionDetected.bind(this));
409
-
410
- this.triggeredResetMotionSensorService = new Service.MotionSensor(
411
- "Triggered Reset",
412
- "reset-event"
413
- );
414
-
415
- this.triggeredResetMotionSensorService.addOptionalCharacteristic(
416
- Characteristic.ConfiguredName
417
- );
418
-
419
- this.triggeredResetMotionSensorService
420
- .setCharacteristic(Characteristic.ConfiguredName, "Triggered Reset")
421
- .getCharacteristic(Characteristic.MotionDetected)
422
- .on("get", this.getTriggeredResetMotionDetected.bind(this));
423
-
424
- // Accessory information
425
- this.accessoryInformationService = new Service.AccessoryInformation();
426
-
427
- this.accessoryInformationService.setCharacteristic(
428
- Characteristic.Identify,
429
- true
430
- );
431
- this.accessoryInformationService.setCharacteristic(
432
- Characteristic.Manufacturer,
433
- "MiguelRipoll23"
434
- );
435
- this.accessoryInformationService.setCharacteristic(
436
- Characteristic.Model,
437
- "DIY"
438
- );
439
- this.accessoryInformationService.setCharacteristic(
440
- Characteristic.Name,
441
- "homebridge-securitysystem"
442
- );
443
- this.accessoryInformationService.setCharacteristic(
444
- Characteristic.SerialNumber,
445
- options.serialNumber
446
- );
447
- this.accessoryInformationService.setCharacteristic(
448
- Characteristic.FirmwareRevision,
449
- packageJson.version
450
- );
451
-
452
- // Services list
453
- this.services = [this.service, this.accessoryInformationService];
454
-
455
- if (options.trippedMotionSensor) {
456
- this.services.push(this.trippedMotionSensorService);
457
- }
458
-
459
- if (options.triggeredMotionSensor) {
460
- this.services.push(this.triggeredMotionSensorService);
461
- }
462
-
463
- if (options.resetSensor) {
464
- this.services.push(this.triggeredResetMotionSensorService);
465
- }
466
-
467
- if (options.armingLockSwitch) {
468
- this.services.push(this.armingLockSwitchService);
469
- }
470
-
471
- if (options.armingLockSwitches) {
472
- this.services.push(this.armingLockHomeSwitchService);
473
- this.services.push(this.armingLockAwaySwitchService);
474
- this.services.push(this.armingLockNightSwitchService);
475
- }
476
-
477
- if (options.tripSwitch) {
478
- this.services.push(this.tripSwitchService);
479
- }
480
-
481
- if (options.tripOverrideSwitch) {
482
- this.services.push(this.tripOverrideSwitchService);
483
- }
484
-
485
- if (
486
- this.availableTargetStates.includes(
487
- Characteristic.SecuritySystemTargetState.STAY_ARM
488
- )
489
- ) {
490
- if (options.modeSwitches) {
491
- this.services.push(this.modeHomeSwitchService);
492
- }
493
-
494
- if (options.tripModeSwitches) {
495
- this.services.push(this.tripHomeSwitchService);
496
- }
497
- }
498
-
499
- if (
500
- this.availableTargetStates.includes(
501
- Characteristic.SecuritySystemTargetState.AWAY_ARM
502
- )
503
- ) {
504
- if (options.modeSwitches) {
505
- this.services.push(this.modeAwaySwitchService);
506
- }
507
-
508
- if (options.tripModeSwitches) {
509
- this.services.push(this.tripAwaySwitchService);
510
- }
511
- }
512
-
513
- if (
514
- this.availableTargetStates.includes(
515
- Characteristic.SecuritySystemTargetState.NIGHT_ARM
516
- )
517
- ) {
518
- if (options.modeSwitches) {
519
- this.services.push(this.modeNightSwitchService);
520
- }
521
-
522
- if (options.tripModeSwitches) {
523
- this.services.push(this.tripNightSwitchService);
524
- }
525
- }
526
-
527
- if (options.modeSwitches && options.modeOffSwitch) {
528
- this.services.push(this.modeOffSwitchService);
529
- }
530
-
531
- if (options.modeAwayExtendedSwitch) {
532
- this.services.push(this.modeAwayExtendedSwitchService);
533
- }
534
-
535
- if (options.modePauseSwitch) {
536
- this.services.push(this.modePauseSwitchService);
537
- }
538
-
539
- if (options.audio && options.audioSwitch) {
540
- this.services.push(this.audioSwitchService);
541
- }
542
-
543
- // Storage
544
- if (options.saveState) {
545
- this.load();
546
- }
547
-
548
- // Audio
549
- if (options.isValueSet(options.audioPath)) {
550
- this.setupAudio();
551
- }
552
-
553
- // Server
554
- if (options.isValueSet(options.serverPort)) {
555
- this.startServer();
556
- }
557
- }
558
-
559
- SecuritySystem.prototype.getServices = function () {
560
- return this.services;
561
- };
562
-
563
- SecuritySystem.prototype.load = async function () {
564
- const storageOptions = {
565
- dir: path.join(storagePath, "homebridge-securitysystem"),
566
- };
567
-
568
- await storage
569
- .init(storageOptions)
570
- .then()
571
- .catch((error) => {
572
- this.log.error("Unable to load state.");
573
- this.log.error(error);
574
- });
575
-
576
- if (options.testMode) {
577
- await storage.clear();
578
- this.log.debug("Saved data from the plugin cleared.");
579
-
580
- return;
581
- }
582
-
583
- await storage
584
- .getItem("state")
585
- .then((state) => {
586
- if (state === undefined) {
587
- return;
588
- }
589
-
590
- this.log.debug("State (Loaded)", state);
591
- this.log.info("Saved state (Found)");
592
-
593
- const currentState = options.isValueSet(state.currentState)
594
- ? state.currentState
595
- : this.defaultState;
596
- const targetState = options.isValueSet(state.targetState)
597
- ? state.targetState
598
- : this.defaultState;
599
-
600
- // Change target state if triggered
601
- if (
602
- currentState ===
603
- Characteristic.SecuritySystemCurrentState.ALARM_TRIGGERED
604
- ) {
605
- this.targetState = targetState;
606
- } else {
607
- this.targetState = currentState;
608
- }
609
-
610
- this.currentState = currentState;
611
-
612
- // Update characteristics values
613
- this.service.updateCharacteristic(
614
- Characteristic.SecuritySystemTargetState,
615
- this.targetState
616
- );
617
- this.service.updateCharacteristic(
618
- Characteristic.SecuritySystemCurrentState,
619
- this.currentState
620
- );
621
- this.handleStateUpdate(false);
622
-
623
- // Log
624
- this.logMode("Current", this.currentState);
625
- })
626
- .catch((error) => {
627
- this.log.error("Saved state (Error)");
628
- this.log.error(error);
629
- });
630
- };
631
-
632
- SecuritySystem.prototype.save = async function () {
633
- // Check option
634
- if (options.saveState === false) {
635
- return;
636
- }
637
-
638
- if (storage.defaultInstance === undefined) {
639
- this.log.error("Unable to save state.");
640
- return;
641
- }
642
-
643
- const state = {
644
- currentState: this.currentState,
645
- targetState: this.targetState,
646
- };
647
-
648
- await storage
649
- .setItem("state", state)
650
- .then(() => {
651
- this.log.debug("State (Saved)", state);
652
- })
653
- .catch((error) => {
654
- this.log.error("Unable to save state.");
655
- this.log.error(error);
656
- });
657
- };
658
-
659
- SecuritySystem.prototype.identify = function (callback) {
660
- this.log.info("Identify");
661
- callback(null);
662
- };
663
-
664
- // Security system
665
- SecuritySystem.prototype.state2Mode = function (state) {
666
- switch (state) {
667
- case Characteristic.SecuritySystemCurrentState.ALARM_TRIGGERED:
668
- return "triggered";
669
-
670
- case Characteristic.SecuritySystemCurrentState.STAY_ARM:
671
- return "home";
672
-
673
- case Characteristic.SecuritySystemCurrentState.AWAY_ARM:
674
- return "away";
675
-
676
- case Characteristic.SecuritySystemCurrentState.NIGHT_ARM:
677
- return "night";
678
-
679
- case Characteristic.SecuritySystemCurrentState.DISARMED:
680
- return "off";
681
-
682
- // Custom
683
- case "lock":
684
- // Audio sound
685
- return state;
686
-
687
- case "warning":
688
- return state;
689
-
690
- default:
691
- this.log.error(`Unknown state (${state}).`);
692
- return "unknown";
693
- }
694
- };
695
-
696
- SecuritySystem.prototype.mode2State = function (mode) {
697
- switch (mode) {
698
- case "home":
699
- return Characteristic.SecuritySystemCurrentState.STAY_ARM;
700
-
701
- case "away":
702
- return Characteristic.SecuritySystemCurrentState.AWAY_ARM;
703
-
704
- case "night":
705
- return Characteristic.SecuritySystemCurrentState.NIGHT_ARM;
706
-
707
- case "off":
708
- return Characteristic.SecuritySystemCurrentState.DISARMED;
709
-
710
- default:
711
- this.log.error(`Unknown mode (${mode}).`);
712
- return -1;
713
- }
714
- };
715
-
716
- SecuritySystem.prototype.logMode = function (type, state) {
717
- let mode = this.state2Mode(state);
718
- mode = mode.charAt(0).toUpperCase() + mode.slice(1);
719
-
720
- this.log.info(`${type} mode (${mode})`);
721
- };
722
-
723
- SecuritySystem.prototype.getAvailableTargetStates = function () {
724
- const targetStateCharacteristic = this.service.getCharacteristic(
725
- Characteristic.SecuritySystemTargetState
726
- );
727
- const validValues = targetStateCharacteristic.props.validValues;
728
- const invalidValues = options.disabledModes.map((value) => {
729
- return this.mode2State(value.toLowerCase());
730
- });
731
-
732
- return validValues.filter((state) => invalidValues.includes(state) === false);
733
- };
734
-
735
- SecuritySystem.prototype.getCurrentState = function (callback) {
736
- callback(null, this.currentState);
737
- };
738
-
739
- SecuritySystem.prototype.setCurrentState = function (state, origin) {
740
- // Check if mode already set
741
- if (this.currentState === state) {
742
- return;
743
- }
744
-
745
- this.currentState = state;
746
- this.service.setCharacteristic(
747
- Characteristic.SecuritySystemCurrentState,
748
- state
749
- );
750
- this.logMode("Current", state);
751
-
752
- // Audio
753
- this.playAudio("current", state);
754
-
755
- // Commands
756
- this.executeCommand("current", state, origin);
757
-
758
- // Webhooks
759
- this.sendWebhookEvent("current", state, origin);
760
-
761
- if (state === Characteristic.SecuritySystemCurrentState.ALARM_TRIGGERED) {
762
- // Update triggered motion sensor
763
- this.triggeredMotionSensorInterval = setInterval(() => {
764
- this.updateTriggeredMotionDetected();
765
- }, options.triggeredMotionSensorSeconds * 1000);
766
-
767
- // Automatically arm the security system
768
- // when time runs out
769
- this.resetTimeout = setTimeout(() => {
770
- this.resetTimeout = null;
771
- this.log.info("Reset (Finished)");
772
-
773
- // Update triggered reset motion sensor
774
- this.triggeredResetMotionSensorService.updateCharacteristic(
775
- Characteristic.MotionDetected,
776
- true
777
- );
778
-
779
- setTimeout(() => {
780
- this.triggeredResetMotionSensorService.updateCharacteristic(
781
- Characteristic.MotionDetected,
782
- false
783
- );
784
- }, 750);
785
-
786
- // Alternative flow (Triggered -> Off -> Armed mode)
787
- if (options.resetOffFlow) {
788
- const originalTargetState = this.targetState;
789
- this.updateTargetState(
790
- Characteristic.SecuritySystemTargetState.DISARM,
791
- originTypes.INTERNAL,
792
- false,
793
- null
794
- );
795
-
796
- setTimeout(() => {
797
- this.updateTargetState(
798
- originalTargetState,
799
- originTypes.INTERNAL,
800
- true,
801
- null
802
- );
803
- }, 100);
804
-
805
- return;
806
- }
807
-
808
- // Normal flow
809
- this.handleStateUpdate(false);
810
- this.setCurrentState(this.targetState, false);
811
- }, options.resetMinutes * 60 * 1000);
812
- }
813
-
814
- this.save();
815
- };
816
-
817
- SecuritySystem.prototype.resetTimers = function () {
818
- // Clear trigger timeout
819
- if (this.triggerTimeout !== null) {
820
- clearTimeout(this.triggerTimeout);
821
-
822
- this.triggerTimeout = null;
823
- this.log.debug("Trigger timeout (Cleared)");
824
- }
825
-
826
- // Clear arming timeout
827
- if (this.armTimeout !== null) {
828
- clearTimeout(this.armTimeout);
829
-
830
- this.armTimeout = null;
831
- this.log.debug("Arming timeout (Cleared)");
832
- }
833
-
834
- // Clear triggered motion sensor
835
- if (this.triggeredMotionSensorInterval !== null) {
836
- clearInterval(this.triggeredMotionSensorInterval);
837
-
838
- this.triggeredMotionSensorInterval = null;
839
- this.log.debug("Triggered interval (Cleared)");
840
- }
841
-
842
- // Clear tripped sensor
843
- if (this.trippedMotionSensorInterval !== null) {
844
- clearInterval(this.trippedMotionSensorInterval);
845
-
846
- this.trippedMotionSensorInterval = null;
847
- this.log.debug("Tripped interval (Cleared)");
848
- }
849
-
850
- // Clear double-knock timeout
851
- if (this.doubleKnockTimeout !== null) {
852
- clearTimeout(this.doubleKnockTimeout);
853
- this.doubleKnockTimeout = null;
854
-
855
- this.log.debug("Double-knock timeout (Cleared)");
856
- }
857
-
858
- // Clear pause timeout
859
- if (this.pauseTimeout !== null) {
860
- clearTimeout(this.pauseTimeout);
861
- this.pauseTimeout = null;
862
-
863
- this.log.debug("Pause timeout (Cleared)");
864
- }
865
-
866
- // Clear security system reset timeout
867
- if (this.resetTimeout !== null) {
868
- clearTimeout(this.resetTimeout);
869
-
870
- this.resetTimeout = null;
871
- this.log.debug("Reset timeout (Cleared)");
872
- }
873
- };
874
-
875
- SecuritySystem.prototype.handleStateUpdate = function (alarmTriggered) {
876
- // Reset double-knock
877
- this.isKnocked = false;
878
-
879
- this.resetTimers();
880
- this.resetModeSwitches();
881
- this.updateModeSwitches();
882
-
883
- // Keep characteristic & switches on
884
- if (alarmTriggered) {
885
- return;
886
- }
887
-
888
- const trippedOnCharacteristic = this.tripSwitchService.getCharacteristic(
889
- Characteristic.On
890
- );
891
-
892
- if (trippedOnCharacteristic.value) {
893
- this.updateTripSwitch(false, originTypes.INTERNAL, true, null);
894
- }
895
-
896
- this.resetTripSwitches();
897
- };
898
-
899
- SecuritySystem.prototype.updateTargetState = function (
900
- state,
901
- origin,
902
- delay,
903
- callback
904
- ) {
905
- const isTargetStateAlreadySet = this.targetState === state;
906
- const isCurrentStateAlarmTriggered =
907
- this.currentState ===
908
- Characteristic.SecuritySystemCurrentState.ALARM_TRIGGERED;
909
- const isTargetStateDisarm =
910
- state === Characteristic.SecuritySystemTargetState.DISARM;
911
-
912
- // Check if target state is already set
913
- if (isTargetStateAlreadySet && isCurrentStateAlarmTriggered === false) {
914
- this.log.warn("Target mode (Already set)");
915
-
916
- if (callback !== null) {
917
- callback(null);
918
- }
919
-
920
- return false;
921
- }
922
-
923
- // Check if state is enabled
924
- if (this.availableTargetStates.includes(state) === false) {
925
- this.log.warn("Target mode (Disabled)");
926
-
927
- if (callback !== null) {
928
- // Tip: this will revert the original state
929
- // HomeKit error
930
- callback(Characteristic.SecuritySystemTargetState.DISARM);
931
- }
932
-
933
- return false;
934
- }
935
-
936
- // Check arming lock switches
937
- const isArmingLockEnabled =
938
- options.isValueSet(options.armingLockSwitch) ||
939
- options.isValueSet(options.armingLockSwitches);
940
-
941
- if (
942
- isTargetStateDisarm === false &&
943
- isArmingLockEnabled &&
944
- this.isArmingLocked(state)
945
- ) {
946
- this.log.warn("Arming lock (Not allowed)");
947
-
948
- if (callback !== null) {
949
- // Tip: this will revert the original state
950
- // HomeKit error
951
- callback(Characteristic.SecuritySystemTargetState.DISARM);
952
- }
953
-
954
- return false;
955
- }
956
-
957
- // Update target state
958
- this.targetState = state;
959
- this.logMode("Target", state);
960
-
961
- const isTargetStateHome =
962
- this.targetState === Characteristic.SecuritySystemTargetState.STAY_ARM;
963
- const isTargetStateAway =
964
- this.targetState === Characteristic.SecuritySystemTargetState.AWAY_ARM;
965
- const isTargetStateNight =
966
- this.targetState === Characteristic.SecuritySystemTargetState.NIGHT_ARM;
967
-
968
- // Update characteristic
969
- if (origin === originTypes.INTERNAL || origin === originTypes.EXTERNAL) {
970
- this.service.updateCharacteristic(
971
- Characteristic.SecuritySystemTargetState,
972
- this.targetState
973
- );
974
- }
975
-
976
- // Reset everything
977
- this.handleStateUpdate(false);
978
-
979
- // Commands
980
- this.executeCommand("target", state, origin);
981
-
982
- // Webhooks
983
- this.sendWebhookEvent("target", state, origin);
984
-
985
- // Check if current state is already set
986
- if (state === this.currentState) {
987
- this.log.warn("Current mode (Already set)");
988
-
989
- // Play audio
990
- this.playAudio("current", this.currentState);
991
-
992
- if (callback !== null) {
993
- callback(null);
994
- }
995
-
996
- return false;
997
- }
998
-
999
- // Set arming delay
1000
- let armSeconds = 0;
1001
-
1002
- if (delay) {
1003
- armSeconds = options.armSeconds;
1004
-
1005
- // No delay when triggered or set to Off
1006
- if (isCurrentStateAlarmTriggered || isTargetStateDisarm) {
1007
- armSeconds = 0;
1008
- }
1009
-
1010
- // Custom mode seconds
1011
- if (isTargetStateHome && options.isValueSet(options.homeArmSeconds)) {
1012
- armSeconds = options.homeArmSeconds;
1013
- } else if (
1014
- isTargetStateAway &&
1015
- options.isValueSet(options.awayArmSeconds)
1016
- ) {
1017
- armSeconds = options.awayArmSeconds;
1018
- } else if (
1019
- isTargetStateNight &&
1020
- options.isValueSet(options.nightArmSeconds)
1021
- ) {
1022
- armSeconds = options.nightArmSeconds;
1023
- }
1024
-
1025
- // Delay actions
1026
- if (armSeconds > 0) {
1027
- this.isArming = true;
1028
-
1029
- // Play sound
1030
- this.playAudio("target", state);
1031
-
1032
- // Log
1033
- this.log.info("Arm delay (" + armSeconds + " second/s)");
1034
- }
1035
- }
1036
-
1037
- // Arm the security system
1038
- this.armTimeout = setTimeout(() => {
1039
- this.armTimeout = null;
1040
- this.setCurrentState(state, origin);
1041
- this.isArming = false;
1042
- }, armSeconds * 1000);
1043
-
1044
- if (callback !== null) {
1045
- callback(null);
1046
- }
1047
-
1048
- return true;
1049
- };
1050
-
1051
- SecuritySystem.prototype.getTargetState = function (callback) {
1052
- callback(null, this.targetState);
1053
- };
1054
-
1055
- SecuritySystem.prototype.setTargetState = function (value, callback) {
1056
- this.updateTargetState(value, originTypes.REGULAR_SWITCH, true, callback);
1057
- };
1058
-
1059
- SecuritySystem.prototype.updateTripSwitch = function (
1060
- value,
1061
- origin,
1062
- stateChanged,
1063
- callback
1064
- ) {
1065
- const isCurrentStateAlarmTriggered =
1066
- this.currentState ===
1067
- Characteristic.SecuritySystemCurrentState.ALARM_TRIGGERED;
1068
- const isCurrentStateHome =
1069
- this.currentState === Characteristic.SecuritySystemCurrentState.STAY_ARM;
1070
- const isCurrentStateAway =
1071
- this.currentState === Characteristic.SecuritySystemCurrentState.AWAY_ARM;
1072
- const isCurrentStateNight =
1073
- this.currentState === Characteristic.SecuritySystemCurrentState.NIGHT_ARM;
1074
- const isCurrentStateDisarmed =
1075
- this.currentState === Characteristic.SecuritySystemCurrentState.DISARMED;
1076
-
1077
- // Check if the security system is disarmed
1078
- const isNotOverridingOff = options.overrideOff === false;
1079
- const isNotSpecialSwitch = origin !== originTypes.SPECIAL_SWITCH;
1080
-
1081
- if (isCurrentStateDisarmed && isNotOverridingOff && isNotSpecialSwitch) {
1082
- this.log.warn("Trip Switch (Not armed)");
1083
-
1084
- if (callback !== null) {
1085
- callback(HK_NOT_ALLOWED_IN_CURRENT_STATE, false);
1086
- }
1087
-
1088
- return false;
1089
- }
1090
-
1091
- // Check if arming
1092
- if (this.isArming) {
1093
- this.log.warn("Trip Switch (Still arming)");
1094
-
1095
- if (callback !== null) {
1096
- callback(HK_NOT_ALLOWED_IN_CURRENT_STATE, false);
1097
- }
1098
-
1099
- return false;
1100
- }
1101
-
1102
- // Check double knock
1103
- if (options.doubleKnock) {
1104
- const doubleKnockStates = options.doubleKnockModes.map((value) => {
1105
- return this.mode2State(value.toLowerCase());
1106
- });
1107
-
1108
- const isFirstKnock = this.isKnocked === false;
1109
- const isSpecialSwitch = origin === originTypes.SPECIAL_SWITCH;
1110
- const isStateKnockable = doubleKnockStates.includes(this.currentState);
1111
-
1112
- if (
1113
- value &&
1114
- isStateKnockable &&
1115
- isFirstKnock &&
1116
- isSpecialSwitch === false
1117
- ) {
1118
- this.log.warn("Trip Switch (Knock)");
1119
- this.isKnocked = true;
1120
-
1121
- // Custom mode seconds
1122
- let doubleKnockSeconds = options.doubleKnockSeconds;
1123
-
1124
- if (
1125
- isCurrentStateHome &&
1126
- options.isValueSet(options.homeDoubleKnockSeconds)
1127
- ) {
1128
- doubleKnockSeconds = options.homeDoubleKnockSeconds;
1129
- } else if (
1130
- isCurrentStateAway &&
1131
- options.isValueSet(options.awayDoubleKnockSeconds)
1132
- ) {
1133
- doubleKnockSeconds = options.awayDoubleKnockSeconds;
1134
- } else if (
1135
- isCurrentStateNight &&
1136
- options.isValueSet(options.nightDoubleKnockSeconds)
1137
- ) {
1138
- doubleKnockSeconds = options.nightDoubleKnockSeconds;
1139
- }
1140
-
1141
- this.doubleKnockTimeout = setTimeout(() => {
1142
- this.doubleKnockTimeout = null;
1143
- this.isKnocked = false;
1144
-
1145
- this.log.info("Trip Switch (Reset)");
1146
- }, doubleKnockSeconds * 1000);
1147
-
1148
- if (callback !== null) {
1149
- callback(HK_NOT_ALLOWED_IN_CURRENT_STATE, false);
1150
- }
1151
-
1152
- return false;
1153
- }
1154
- }
1155
-
1156
- // Clear double-knock timeout
1157
- if (this.doubleKnockTimeout !== null) {
1158
- clearTimeout(this.doubleKnockTimeout);
1159
- this.doubleKnockTimeout = null;
1160
-
1161
- this.log.debug("Double-knock timeout (Cleared)");
1162
- }
1163
-
1164
- if (origin === originTypes.INTERNAL || origin === originTypes.EXTERNAL) {
1165
- this.tripSwitchService.updateCharacteristic(Characteristic.On, value);
1166
- }
1167
-
1168
- if (value) {
1169
- // Already triggered
1170
- if (isCurrentStateAlarmTriggered) {
1171
- this.log.warn("Security System (Already triggered)");
1172
-
1173
- if (callback !== null) {
1174
- callback(HK_NOT_ALLOWED_IN_CURRENT_STATE, false);
1175
- }
1176
-
1177
- return false;
1178
- }
1179
-
1180
- // Already about to trigger
1181
- if (this.triggerTimeout !== null) {
1182
- this.log.warn("Security System (Already tripped)");
1183
-
1184
- if (callback !== null) {
1185
- callback(HK_NOT_ALLOWED_IN_CURRENT_STATE, false);
1186
- }
1187
-
1188
- return false;
1189
- }
1190
-
1191
- this.log.info("Security System (Tripped)");
1192
-
1193
- // Update tripped motion sensor
1194
- if (options.trippedMotionSensor) {
1195
- this.updateTrippedMotionDetected();
1196
-
1197
- this.trippedMotionSensorInterval = setInterval(() => {
1198
- this.updateTrippedMotionDetected();
1199
- }, options.trippedMotionSensorSeconds * 1000);
1200
- }
1201
-
1202
- const isCurrentStateHome =
1203
- this.currentState === Characteristic.SecuritySystemCurrentState.STAY_ARM;
1204
- const isCurrentStateAway =
1205
- this.currentState === Characteristic.SecuritySystemCurrentState.AWAY_ARM;
1206
- const isCurrentStateNight =
1207
- this.currentState === Characteristic.SecuritySystemCurrentState.NIGHT_ARM;
1208
-
1209
- // Set trigger delay
1210
- let triggerSeconds = options.triggerSeconds;
1211
-
1212
- // User options
1213
- if (isCurrentStateHome && options.isValueSet(options.homeTriggerSeconds)) {
1214
- triggerSeconds = options.homeTriggerSeconds;
1215
- }
1216
-
1217
- if (isCurrentStateAway) {
1218
- const modeAwayExtendedSwitchCharacteristicOn =
1219
- this.modeAwayExtendedSwitchService.getCharacteristic(Characteristic.On);
1220
- const modeAwayExtendedSwitchCharacteristicOnValue =
1221
- modeAwayExtendedSwitchCharacteristicOn.value;
1222
-
1223
- if (
1224
- options.isValueSet(options.awayExtendedTriggerSeconds) &&
1225
- modeAwayExtendedSwitchCharacteristicOnValue
1226
- ) {
1227
- triggerSeconds = options.awayExtendedTriggerSeconds;
1228
- } else if (options.isValueSet(options.awayTriggerSeconds)) {
1229
- triggerSeconds = options.awayTriggerSeconds;
1230
- }
1231
- }
1232
-
1233
- if (
1234
- isCurrentStateNight &&
1235
- options.isValueSet(options.nightTriggerSeconds)
1236
- ) {
1237
- triggerSeconds = options.nightTriggerSeconds;
1238
- }
1239
-
1240
- // Log
1241
- this.log.debug("Trigger delay (" + triggerSeconds + " second/s)");
1242
-
1243
- this.triggerTimeout = setTimeout(() => {
1244
- this.triggerTimeout = null;
1245
- this.setCurrentState(
1246
- Characteristic.SecuritySystemCurrentState.ALARM_TRIGGERED,
1247
- origin
1248
- );
1249
- }, triggerSeconds * 1000);
1250
-
1251
- // Audio
1252
- if (triggerSeconds > 0) {
1253
- this.playAudio("current", "warning");
1254
- }
1255
-
1256
- // Commands
1257
- this.executeCommand("current", "warning", origin);
1258
-
1259
- // Webhooks
1260
- this.sendWebhookEvent("current", "warning", origin);
1261
- } else {
1262
- // Off
1263
- this.log.info("Security System (Cancelled)");
1264
- this.stopAudio();
1265
-
1266
- if (isCurrentStateAlarmTriggered) {
1267
- if (stateChanged === false) {
1268
- this.updateTargetState(
1269
- Characteristic.SecuritySystemTargetState.DISARM,
1270
- originTypes.INTERNAL,
1271
- false,
1272
- null
1273
- );
1274
- }
1275
- } else {
1276
- this.resetTimers();
1277
- }
1278
-
1279
- // Update tripped motion sensor
1280
- if (options.trippedSensor) {
1281
- this.trippedMotionSensorService.updateCharacteristic(
1282
- Characteristic.MotionDetected,
1283
- false
1284
- );
1285
- }
1286
-
1287
- this.isKnocked = false;
1288
- }
1289
-
1290
- if (callback !== null) {
1291
- callback(null);
1292
- }
1293
-
1294
- return true;
1295
- };
1296
-
1297
- // Server
1298
- SecuritySystem.prototype.isAuthenticated = function (req, res) {
1299
- // Check if authentication is disabled
1300
- if (options.serverCode === null) {
1301
- return null;
1302
- }
1303
-
1304
- let code = req.query.code;
1305
-
1306
- // Check if code sent
1307
- if (code === undefined) {
1308
- this.sendCodeRequiredError(res);
1309
- return false;
1310
- }
1311
-
1312
- // Check brute force
1313
- if (this.invalidCodeCount >= 5) {
1314
- req.blocked = true;
1315
- this.sendCodeInvalidError(req, res);
1316
- return false;
1317
- }
1318
-
1319
- const userCode = parseInt(req.query.code);
1320
-
1321
- if (userCode !== options.serverCode) {
1322
- this.invalidCodeCount++;
1323
- this.sendCodeInvalidError(req, res);
1324
- return false;
1325
- }
1326
-
1327
- // Reset
1328
- this.invalidCodeCount = 0;
1329
-
1330
- return true;
1331
- };
1332
-
1333
- SecuritySystem.prototype.getDelayParameter = function (req) {
1334
- return req.query.delay === "true" ? true : false;
1335
- };
1336
-
1337
- SecuritySystem.prototype.sendCodeRequiredError = function (res) {
1338
- this.log.info("Code required (Server)");
1339
-
1340
- const response = {
1341
- error: true,
1342
- message: "Code required",
1343
- hint: "Add the 'code' URL parameter with your security code",
1344
- };
1345
-
1346
- res.status(401).json(response);
1347
- };
1348
-
1349
- SecuritySystem.prototype.sendCodeInvalidError = function (req, res) {
1350
- const response = { error: true };
1351
-
1352
- if (req.blocked) {
1353
- this.log.info("Code blocked (Server)");
1354
- response.message = "Code blocked";
1355
- } else {
1356
- this.log.info("Code invalid (Server)");
1357
- response.message = "Code invalid";
1358
- }
1359
-
1360
- res.status(403).json(response);
1361
- };
1362
-
1363
- SecuritySystem.prototype.sendResultResponse = function (res, success) {
1364
- const response = {
1365
- error: success ? false : true,
1366
- };
1367
-
1368
- res.json(response);
1369
- };
1370
-
1371
- SecuritySystem.prototype.startServer = async function () {
1372
- const apiLimiter = rateLimit({
1373
- windowMs: 1 * 60 * 1000,
1374
- max: 100,
1375
- standardHeaders: true,
1376
- legacyHeaders: false,
1377
- });
1378
-
1379
- app.use(apiLimiter);
1380
-
1381
- app.get("/", (req, res) => {
1382
- res.redirect(
1383
- "https://github.com/MiguelRipoll23/homebridge-securitysystem/wiki/Server"
1384
- );
1385
- });
1386
-
1387
- app.get("/status", (req, res) => {
1388
- if (this.isAuthenticated(req, res) === false) {
1389
- return;
1390
- }
1391
-
1392
- const response = {
1393
- arming: this.isArming,
1394
- current_mode: this.state2Mode(this.currentState),
1395
- target_mode: this.state2Mode(this.targetState),
1396
- tripped: this.triggerTimeout !== null,
1397
- };
1398
-
1399
- res.json(response);
1400
- });
1401
-
1402
- app.get("/triggered", (req, res) => {
1403
- if (this.isAuthenticated(req, res) === false) {
1404
- return;
1405
- }
1406
-
1407
- let result = true;
1408
-
1409
- if (this.getDelayParameter(req)) {
1410
- // Delay
1411
- result = this.updateTripSwitch(true, originTypes.EXTERNAL, false, null);
1412
- } else {
1413
- const isCurrentStateDisarmed =
1414
- this.currentState ===
1415
- Characteristic.SecuritySystemCurrentState.DISARMED;
1416
-
1417
- // Not armed
1418
- if (isCurrentStateDisarmed && options.overrideOff === false) {
1419
- this.sendResultResponse(res, false);
1420
- return;
1421
- }
1422
-
1423
- this.handleStateUpdate(true);
1424
- this.setCurrentState(
1425
- Characteristic.SecuritySystemCurrentState.ALARM_TRIGGERED,
1426
- true
1427
- );
1428
- }
1429
-
1430
- this.sendResultResponse(res, result);
1431
- });
1432
-
1433
- app.get("/home", (req, res) => {
1434
- if (this.isAuthenticated(req, res) === false) {
1435
- return;
1436
- }
1437
-
1438
- const state = Characteristic.SecuritySystemTargetState.STAY_ARM;
1439
- const delay = this.getDelayParameter(req);
1440
- const result = this.updateTargetState(
1441
- state,
1442
- originTypes.EXTERNAL,
1443
- delay,
1444
- null
1445
- );
1446
-
1447
- this.sendResultResponse(res, result);
1448
- });
1449
-
1450
- app.get("/away", (req, res) => {
1451
- if (this.isAuthenticated(req, res) === false) {
1452
- return;
1453
- }
1454
-
1455
- const state = Characteristic.SecuritySystemTargetState.AWAY_ARM;
1456
- const delay = this.getDelayParameter(req);
1457
- const result = this.updateTargetState(
1458
- state,
1459
- originTypes.EXTERNAL,
1460
- delay,
1461
- null
1462
- );
1463
-
1464
- this.sendResultResponse(res, result);
1465
- });
1466
-
1467
- app.get("/night", (req, res) => {
1468
- if (this.isAuthenticated(req, res) === false) {
1469
- return;
1470
- }
1471
-
1472
- const state = Characteristic.SecuritySystemTargetState.NIGHT_ARM;
1473
- const delay = this.getDelayParameter(req);
1474
- const result = this.updateTargetState(
1475
- state,
1476
- originTypes.EXTERNAL,
1477
- delay,
1478
- null
1479
- );
1480
-
1481
- this.sendResultResponse(res, result);
1482
- });
1483
-
1484
- app.get("/off", (req, res) => {
1485
- if (this.isAuthenticated(req, res) === false) {
1486
- return;
1487
- }
1488
-
1489
- const state = Characteristic.SecuritySystemTargetState.DISARM;
1490
- const delay = this.getDelayParameter(req);
1491
- const result = this.updateTargetState(
1492
- state,
1493
- originTypes.EXTERNAL,
1494
- delay,
1495
- null
1496
- );
1497
-
1498
- this.sendResultResponse(res, result);
1499
- });
1500
-
1501
- app.get("/arming-lock/:mode/:value", (req, res) => {
1502
- if (this.isAuthenticated(req, res) === false) {
1503
- return;
1504
- }
1505
-
1506
- const mode = req.params["mode"].toLowerCase();
1507
- const value = req.params["value"].includes("on");
1508
- const result = this.updateArmingLock(mode, value);
1509
-
1510
- this.sendResultResponse(res, result);
1511
- });
1512
-
1513
- // Listener
1514
- const server = app.listen(options.serverPort, (error) => {
1515
- if (error) {
1516
- this.log.error("Error while starting server.");
1517
- this.log.error(error);
1518
- return;
1519
- }
1520
-
1521
- this.log.info(`Server (${options.serverPort})`);
1522
- });
1523
-
1524
- server.on("error", (error) => {
1525
- this.log.error("Error while starting server.");
1526
- this.log.error(error);
1527
- });
1528
- };
1529
-
1530
- // Audio
1531
- SecuritySystem.prototype.playAudio = async function (type, state) {
1532
- // Check option
1533
- if (options.audio === false) {
1534
- return;
1535
- }
1536
-
1537
- const mode = this.state2Mode(state);
1538
-
1539
- // Close previous player
1540
- this.stopAudio();
1541
-
1542
- // Ignore 'Current Off' event
1543
- if (mode === "off") {
1544
- if (type === "target") {
1545
- return;
1546
- }
1547
- }
1548
-
1549
- // Check audio switch except for triggered
1550
- const audioSwitchOnCharacteristic = this.audioSwitchService.getCharacteristic(
1551
- Characteristic.On
1552
- );
1553
- const isAudioDisabledBySwitch = audioSwitchOnCharacteristic.value === false;
1554
-
1555
- if (mode !== "triggered" && isAudioDisabledBySwitch) {
1556
- return;
1557
- }
1558
-
1559
- // Directory
1560
- let directory = `${__dirname}/../sounds`;
1561
-
1562
- if (options.isValueSet(options.audioPath)) {
1563
- directory = options.audioPath;
1564
-
1565
- if (directory[directory.length] === "/") {
1566
- directory = directory.substring(0, directory.length - 1);
1567
- }
1568
- }
1569
-
1570
- // Check if file exists
1571
- const filename = `${type}-${mode}.mp3`;
1572
- const filePath = `${directory}/${options.audioLanguage}/${filename}`;
1573
-
1574
- try {
1575
- await fs.promises.access(filePath);
1576
- } catch (error) {
1577
- this.log.debug(`Sound file not found (${filePath})`);
1578
- return;
1579
- }
1580
-
1581
- // Arguments
1582
- let commandArguments = ["-loglevel", "error", "-nodisp", "-i", `${filePath}`];
1583
-
1584
- if (mode === "triggered") {
1585
- commandArguments.push("-loop");
1586
- commandArguments.push("-1");
1587
- } else if (
1588
- (mode === "home" || mode === "night" || mode === "away") &&
1589
- type === "target" &&
1590
- options.audioArmingLooped
1591
- ) {
1592
- commandArguments.push("-loop");
1593
- commandArguments.push("-1");
1594
- } else if (mode === "warning" && options.audioAlertLooped) {
1595
- commandArguments.push("-loop");
1596
- commandArguments.push("-1");
1597
- } else {
1598
- commandArguments.push("-autoexit");
1599
- }
1600
-
1601
- if (options.isValueSet(options.audioVolume)) {
1602
- commandArguments.push("-volume");
1603
- commandArguments.push(options.audioVolume);
1604
- }
1605
-
1606
- // Process
1607
- const environmentVariables = [process.env];
1608
-
1609
- options.audioExtraVariables.forEach((variable) => {
1610
- const key = variable.key;
1611
- const value = variable.value;
1612
- environmentVariables[key] = value;
1613
- });
1614
-
1615
- this.log.debug("Environment Variables (Audio)", environmentVariables);
1616
-
1617
- const ffplayEnv = {
1618
- ...process.env,
1619
- ...environmentVariables,
1620
- };
1621
-
1622
- this.audioProcess = spawn("ffplay", commandArguments, { env: ffplayEnv });
1623
- this.log.debug(`ffplay ${commandArguments.join(" ")}`);
1624
-
1625
- this.audioProcess.on("error", (data) => {
1626
- // Check if command is missing
1627
- if (data !== null && data.toString().indexOf("ENOENT") > -1) {
1628
- this.log.error("Unable to play sound, ffmpeg is not installed.");
1629
- return;
1630
- }
1631
-
1632
- this.log.error(`Unable to play sound.\n${data}`);
1633
- });
1634
-
1635
- this.audioProcess.on("close", function () {
1636
- this.audioProcess = null;
1637
- });
1638
- };
1639
-
1640
- SecuritySystem.prototype.stopAudio = function () {
1641
- if (this.audioProcess !== null) {
1642
- this.audioProcess.kill();
1643
- }
1644
- };
1645
-
1646
- SecuritySystem.prototype.setupAudio = async function () {
1647
- try {
1648
- await fs.promises.access(`${options.audioPath}/${options.audioLanguage}`);
1649
- } catch (error) {
1650
- await fs.promises.mkdir(`${options.audioPath}/${options.audioLanguage}`);
1651
- await fs.promises.copyFile(
1652
- `${__dirname}/sounds/README`,
1653
- `${options.audioPath}/README`
1654
- );
1655
- await fs.promises.copyFile(
1656
- `${__dirname}/sounds/README`,
1657
- `${options.audioPath}/README.txt`
1658
- );
1659
-
1660
- this.log.warn("Check audio path directory for instructions.");
1661
- }
1662
- };
1663
-
1664
- // Command
1665
- SecuritySystem.prototype.executeCommand = function (type, state, origin) {
1666
- // Check proxy mode
1667
- if (options.proxyMode && origin === originTypes.EXTERNAL) {
1668
- this.log.debug("Command bypassed as proxy mode is enabled.");
1669
- return;
1670
- }
1671
-
1672
- let command = null;
1673
-
1674
- switch (state) {
1675
- case Characteristic.SecuritySystemCurrentState.ALARM_TRIGGERED:
1676
- command = options.commandCurrentTriggered;
1677
- break;
1678
-
1679
- case Characteristic.SecuritySystemCurrentState.STAY_ARM:
1680
- if (type === "current") {
1681
- command = options.commandCurrentHome;
1682
- break;
1683
- }
1684
-
1685
- command = options.commandTargetHome;
1686
- break;
1687
-
1688
- case Characteristic.SecuritySystemCurrentState.AWAY_ARM:
1689
- if (type === "current") {
1690
- command = options.commandCurrentAway;
1691
- break;
1692
- }
1693
-
1694
- command = options.commandTargetAway;
1695
- break;
1696
-
1697
- case Characteristic.SecuritySystemCurrentState.NIGHT_ARM:
1698
- if (type === "current") {
1699
- command = options.commandCurrentNight;
1700
- break;
1701
- }
1702
-
1703
- command = options.commandTargetNight;
1704
- break;
1705
-
1706
- case Characteristic.SecuritySystemCurrentState.DISARMED:
1707
- if (type === "current") {
1708
- command = options.commandCurrentOff;
1709
- break;
1710
- }
1711
-
1712
- command = options.commandTargetOff;
1713
- break;
1714
-
1715
- case "warning":
1716
- command = options.commandCurrentWarning;
1717
- break;
1718
-
1719
- default:
1720
- this.log.error(`Unknown command ${type} state (${state})`);
1721
- }
1722
-
1723
- if (command === undefined || command === null) {
1724
- this.log.debug(`Command option for ${type} mode is not set.`);
1725
- return;
1726
- }
1727
-
1728
- // Parameters
1729
- command = command.replace(
1730
- "${currentMode}",
1731
- this.state2Mode(this.currentState)
1732
- );
1733
-
1734
- const process = spawn(command, { shell: true });
1735
-
1736
- process.stderr.on("data", (data) => {
1737
- this.log.error(`Command failed (${command})\n${data}`);
1738
- });
1739
-
1740
- process.stdout.on("data", (data) => {
1741
- this.log.info(`Command output: ${data}`);
1742
- });
1743
- };
1744
-
1745
- // Webhooks
1746
- SecuritySystem.prototype.sendWebhookEvent = function (type, state, origin) {
1747
- // Check webhook host
1748
- if (options.isValueSet(options.webhookUrl) === false) {
1749
- this.log.debug("Webhook base URL option is not set.");
1750
- return;
1751
- }
1752
-
1753
- // Check proxy mode
1754
- if (options.proxyMode && origin === originTypes.EXTERNAL) {
1755
- this.log.debug("Webhook bypassed as proxy mode is enabled.");
1756
- return;
1757
- }
1758
-
1759
- let path = null;
1760
-
1761
- switch (state) {
1762
- case Characteristic.SecuritySystemCurrentState.ALARM_TRIGGERED:
1763
- path = options.webhookCurrentTriggered;
1764
- break;
1765
-
1766
- case Characteristic.SecuritySystemCurrentState.STAY_ARM:
1767
- if (type === "current") {
1768
- path = options.webhookCurrentHome;
1769
- break;
1770
- }
1771
-
1772
- path = options.webhookTargetHome;
1773
- break;
1774
-
1775
- case Characteristic.SecuritySystemCurrentState.AWAY_ARM:
1776
- if (type === "current") {
1777
- path = options.webhookCurrentAway;
1778
- break;
1779
- }
1780
-
1781
- path = options.webhookTargetAway;
1782
- break;
1783
-
1784
- case Characteristic.SecuritySystemCurrentState.NIGHT_ARM:
1785
- if (type === "current") {
1786
- path = options.webhookCurrentNight;
1787
- break;
1788
- }
1789
-
1790
- path = options.webhookTargetNight;
1791
- break;
1792
-
1793
- case Characteristic.SecuritySystemCurrentState.DISARMED:
1794
- if (type === "current") {
1795
- path = options.webhookCurrentOff;
1796
- break;
1797
- }
1798
-
1799
- path = options.webhookTargetOff;
1800
- break;
1801
-
1802
- case "warning":
1803
- path = options.webhookCurrentWarning;
1804
- break;
1805
-
1806
- default:
1807
- this.log.error(`Unknown webhook ${type} state (${state})`);
1808
- return;
1809
- }
1810
-
1811
- if (path === undefined || path === null) {
1812
- this.log.debug(`Webhook option for ${type} mode is not set.`);
1813
- return;
1814
- }
1815
-
1816
- // Parameters
1817
- path = path.replace("${currentMode}", this.state2Mode(this.currentState));
1818
-
1819
- // Send GET request to server
1820
- fetch(options.webhookUrl + path)
1821
- .then((response) => {
1822
- if (response.ok === false) {
1823
- throw new Error(`Status code (${response.status})`);
1824
- }
1825
-
1826
- this.log.info("Webhook event (Sent)");
1827
- })
1828
- .catch((error) => {
1829
- this.log.error(`Request to webhook failed. (${path})`);
1830
- this.log.error(error);
1831
- });
1832
- };
1833
-
1834
- // Trip switches
1835
- SecuritySystem.prototype.getTripSwitch = function (callback) {
1836
- const value = this.tripSwitchService.getCharacteristic(
1837
- Characteristic.On
1838
- ).value;
1839
- callback(null, value);
1840
- };
1841
-
1842
- SecuritySystem.prototype.setTripSwitch = function (value, callback) {
1843
- this.log.info(`Trip Switch (${value ? "On" : "Off"})`);
1844
- this.updateTripSwitch(value, originTypes.REGULAR_SWITCH, false, callback);
1845
- };
1846
-
1847
- SecuritySystem.prototype.getTripHomeSwitch = function (callback) {
1848
- const value = this.tripHomeSwitchService.getCharacteristic(
1849
- Characteristic.On
1850
- ).value;
1851
- callback(null, value);
1852
- };
1853
-
1854
- SecuritySystem.prototype.setTripHomeSwitch = function (value, callback) {
1855
- this.log.info(`Trip Home Switch (${value ? "On" : "Off"})`);
1856
- this.triggerIfModeSet(
1857
- Characteristic.SecuritySystemCurrentState.STAY_ARM,
1858
- value,
1859
- callback
1860
- );
1861
- };
1862
-
1863
- SecuritySystem.prototype.getTripAwaySwitch = function (callback) {
1864
- const value = this.tripAwaySwitchService.getCharacteristic(
1865
- Characteristic.On
1866
- ).value;
1867
- callback(null, value);
1868
- };
1869
-
1870
- SecuritySystem.prototype.setTripAwaySwitch = function (value, callback) {
1871
- this.log.info(`Trip Away Switch (${value ? "On" : "Off"})`);
1872
- this.triggerIfModeSet(
1873
- Characteristic.SecuritySystemCurrentState.AWAY_ARM,
1874
- value,
1875
- callback
1876
- );
1877
- };
1878
-
1879
- SecuritySystem.prototype.getTripNightSwitch = function (callback) {
1880
- const value = this.tripNightSwitchService.getCharacteristic(
1881
- Characteristic.On
1882
- ).value;
1883
- callback(null, value);
1884
- };
1885
-
1886
- SecuritySystem.prototype.setTripNightSwitch = function (value, callback) {
1887
- this.log.info(`Trip Night Switch (${value ? "On" : "Off"})`);
1888
- this.triggerIfModeSet(
1889
- Characteristic.SecuritySystemCurrentState.NIGHT_ARM,
1890
- value,
1891
- callback
1892
- );
1893
- };
1894
-
1895
- SecuritySystem.prototype.getTripOverrideSwitch = function (callback) {
1896
- const value = this.tripOverrideSwitchService.getCharacteristic(
1897
- Characteristic.On
1898
- ).value;
1899
- callback(null, value);
1900
- };
1901
-
1902
- SecuritySystem.prototype.setTripOverrideSwitch = function (value, callback) {
1903
- this.log.info(`Trip Override Switch (${value ? "On" : "Off"})`);
1904
- this.updateTripSwitch(value, originTypes.SPECIAL_SWITCH, false, callback);
1905
- };
1906
-
1907
- SecuritySystem.prototype.triggerIfModeSet = function (
1908
- switchRequiredState,
1909
- value,
1910
- callback
1911
- ) {
1912
- const isCurrentStateAlarmTriggered =
1913
- this.currentState ===
1914
- Characteristic.SecuritySystemCurrentState.ALARM_TRIGGERED;
1915
-
1916
- if (value) {
1917
- if (
1918
- this.currentState === switchRequiredState ||
1919
- (this.targetState === switchRequiredState && isCurrentStateAlarmTriggered)
1920
- ) {
1921
- this.updateTripSwitch(value, originTypes.REGULAR_SWITCH, false, callback);
1922
- } else {
1923
- this.log.debug("Security System (Trip mode not set)");
1924
- callback(HK_NOT_ALLOWED_IN_CURRENT_STATE, false);
1925
- }
1926
- } else {
1927
- this.updateTripSwitch(value, originTypes.REGULAR_SWITCH, false, callback);
1928
- }
1929
- };
1930
-
1931
- SecuritySystem.prototype.resetTripSwitches = function () {
1932
- const tripHomeOnCharacteristic = this.tripHomeSwitchService.getCharacteristic(
1933
- Characteristic.On
1934
- );
1935
- const tripAwayOnCharacteristic = this.tripAwaySwitchService.getCharacteristic(
1936
- Characteristic.On
1937
- );
1938
- const tripNightOnCharacteristic =
1939
- this.tripNightSwitchService.getCharacteristic(Characteristic.On);
1940
-
1941
- const tripOverrideOnCharacteristic =
1942
- this.tripOverrideSwitchService.getCharacteristic(Characteristic.On);
1943
-
1944
- if (tripHomeOnCharacteristic.value) {
1945
- tripHomeOnCharacteristic.updateValue(false);
1946
- this.log.debug("Trip Home Switch (Off)");
1947
- }
1948
-
1949
- if (tripAwayOnCharacteristic.value) {
1950
- tripAwayOnCharacteristic.updateValue(false);
1951
- this.log.debug("Trip Away Switch (Off)");
1952
- }
1953
-
1954
- if (tripNightOnCharacteristic.value) {
1955
- tripNightOnCharacteristic.updateValue(false);
1956
- this.log.debug("Trip Night Switch (Off)");
1957
- }
1958
-
1959
- if (tripOverrideOnCharacteristic.value) {
1960
- tripOverrideOnCharacteristic.updateValue(false);
1961
- this.log.debug("Trip Override Switch (Off)");
1962
- }
1963
- };
1964
-
1965
- // Arming lock switches
1966
- SecuritySystem.prototype.getArmingLockSwitch = function (callback) {
1967
- const value = this.armingLockSwitchService.getCharacteristic(
1968
- Characteristic.On
1969
- ).value;
1970
- callback(null, value);
1971
- };
1972
-
1973
- SecuritySystem.prototype.getArmingLockHomeSwitch = function (callback) {
1974
- const value = this.armingLockHomeSwitchService.getCharacteristic(
1975
- Characteristic.On
1976
- ).value;
1977
- callback(null, value);
1978
- };
1979
-
1980
- SecuritySystem.prototype.getArmingLockAwaySwitch = function (callback) {
1981
- const value = this.armingLockAwaySwitchService.getCharacteristic(
1982
- Characteristic.On
1983
- ).value;
1984
- callback(null, value);
1985
- };
1986
-
1987
- SecuritySystem.prototype.getArmingLockNightSwitch = function (callback) {
1988
- const value = this.armingLockNightSwitchService.getCharacteristic(
1989
- Characteristic.On
1990
- ).value;
1991
- callback(null, value);
1992
- };
1993
-
1994
- SecuritySystem.prototype.logArmingLock = function (mode, value) {
1995
- const modeCapitalized = mode.charAt(0).toUpperCase() + mode.slice(1);
1996
- this.log.info(`Arming lock [${modeCapitalized}] (${value ? "On" : "Off"})`);
1997
- };
1998
-
1999
- SecuritySystem.prototype.isArmingLocked = function (state) {
2000
- let armingLockSwitchService = this.armingLockSwitchService;
2001
-
2002
- // Check global switch
2003
- if (armingLockSwitchService.getCharacteristic(Characteristic.On).value) {
2004
- return true;
2005
- }
2006
-
2007
- // Check mode switches
2008
- switch (state) {
2009
- case Characteristic.SecuritySystemCurrentState.STAY_ARM:
2010
- armingLockSwitchService = this.armingLockHomeSwitchService;
2011
- break;
2012
-
2013
- case Characteristic.SecuritySystemCurrentState.AWAY_ARM:
2014
- armingLockSwitchService = this.armingLockAwaySwitchService;
2015
- break;
2016
-
2017
- case Characteristic.SecuritySystemCurrentState.NIGHT_ARM:
2018
- armingLockSwitchService = this.armingLockNightSwitchService;
2019
- break;
2020
-
2021
- default:
2022
- this.log.debug(`Unknown arming lock state (${state})`);
2023
- }
2024
-
2025
- return armingLockSwitchService.getCharacteristic(Characteristic.On).value;
2026
- };
2027
-
2028
- SecuritySystem.prototype.updateArmingLock = function (mode, value) {
2029
- this.logArmingLock(mode, value);
2030
-
2031
- switch (mode) {
2032
- case "global":
2033
- this.armingLockSwitchService
2034
- .getCharacteristic(Characteristic.On)
2035
- .updateValue(value);
2036
- break;
2037
-
2038
- case "home":
2039
- this.armingLockHomeSwitchService
2040
- .getCharacteristic(Characteristic.On)
2041
- .updateValue(value);
2042
- break;
2043
-
2044
- case "away":
2045
- this.armingLockAwaySwitchService
2046
- .getCharacteristic(Characteristic.On)
2047
- .updateValue(value);
2048
- break;
2049
-
2050
- case "night":
2051
- this.armingLockNightSwitchService
2052
- .getCharacteristic(Characteristic.On)
2053
- .updateValue(value);
2054
- break;
2055
-
2056
- default:
2057
- this.log.debug(`Unknown arming lock mode (${mode})`);
2058
- return false;
2059
- }
2060
-
2061
- return true;
2062
- };
2063
-
2064
- SecuritySystem.prototype.setArmingLockSwitch = function (value, callback) {
2065
- this.logArmingLock("global", value);
2066
- callback(null);
2067
- };
2068
-
2069
- SecuritySystem.prototype.setArmingLockHomeSwitch = function (value, callback) {
2070
- this.logArmingLock("home", value);
2071
- callback(null);
2072
- };
2073
-
2074
- SecuritySystem.prototype.setArmingLockAwaySwitch = function (value, callback) {
2075
- this.logArmingLock("away", value);
2076
- callback(null);
2077
- };
2078
-
2079
- SecuritySystem.prototype.setArmingLockNightSwitch = function (value, callback) {
2080
- this.logArmingLock("night", value);
2081
- callback(null);
2082
- };
2083
-
2084
- // Mode Switches
2085
- SecuritySystem.prototype.resetModeSwitches = function () {
2086
- const modeHomeSwitchCharacteristicOn =
2087
- this.modeHomeSwitchService.getCharacteristic(Characteristic.On);
2088
- const modeAwaySwitchCharacteristicOn =
2089
- this.modeAwaySwitchService.getCharacteristic(Characteristic.On);
2090
- const modeNightSwitchCharacteristicOn =
2091
- this.modeNightSwitchService.getCharacteristic(Characteristic.On);
2092
- const modeOffSwitchCharacteristicOn =
2093
- this.modeOffSwitchService.getCharacteristic(Characteristic.On);
2094
- const modeAwayExtendedSwitchCharacteristicOn =
2095
- this.modeAwayExtendedSwitchService.getCharacteristic(Characteristic.On);
2096
- const modePauseSwitchCharacteristicOn =
2097
- this.modePauseSwitchService.getCharacteristic(Characteristic.On);
2098
-
2099
- if (modeHomeSwitchCharacteristicOn.value) {
2100
- modeHomeSwitchCharacteristicOn.updateValue(false);
2101
- this.log.debug("Mode Home Switch (Off)");
2102
- }
2103
-
2104
- if (modeAwaySwitchCharacteristicOn.value) {
2105
- modeAwaySwitchCharacteristicOn.updateValue(false);
2106
- this.log.debug("Mode Away Switch (Off)");
2107
- }
2108
-
2109
- if (modeNightSwitchCharacteristicOn.value) {
2110
- modeNightSwitchCharacteristicOn.updateValue(false);
2111
- this.log.debug("Mode Night Switch (Off)");
2112
- }
2113
-
2114
- if (modeOffSwitchCharacteristicOn.value) {
2115
- modeOffSwitchCharacteristicOn.updateValue(false);
2116
- this.log.debug("Mode Off Switch (Off)");
2117
- }
2118
-
2119
- if (modeAwayExtendedSwitchCharacteristicOn.value) {
2120
- modeAwayExtendedSwitchCharacteristicOn.updateValue(false);
2121
- this.log.debug("Mode Away Extended Switch (Off)");
2122
- }
2123
-
2124
- if (modePauseSwitchCharacteristicOn.value) {
2125
- modePauseSwitchCharacteristicOn.updateValue(false);
2126
- this.log.debug("Mode Pause Switch (Off)");
2127
- }
2128
- };
2129
-
2130
- SecuritySystem.prototype.updateModeSwitches = function () {
2131
- switch (this.targetState) {
2132
- case Characteristic.SecuritySystemTargetState.STAY_ARM:
2133
- this.modeHomeSwitchService.updateCharacteristic(Characteristic.On, true);
2134
- this.log.debug("Mode Home Switch (On)");
2135
- break;
2136
-
2137
- case Characteristic.SecuritySystemTargetState.AWAY_ARM:
2138
- this.modeAwaySwitchService.updateCharacteristic(Characteristic.On, true);
2139
- this.log.debug("Mode Away Switch (On)");
2140
- break;
2141
-
2142
- case Characteristic.SecuritySystemTargetState.NIGHT_ARM:
2143
- this.modeNightSwitchService.updateCharacteristic(Characteristic.On, true);
2144
- this.log.debug("Mode Night Switch (On)");
2145
- break;
2146
-
2147
- case Characteristic.SecuritySystemTargetState.DISARM:
2148
- this.modeOffSwitchService.updateCharacteristic(Characteristic.On, true);
2149
- this.log.debug("Mode Off Switch (On)");
2150
- break;
2151
- }
2152
- };
2153
-
2154
- SecuritySystem.prototype.getModeHomeSwitch = function (callback) {
2155
- const value = this.modeHomeSwitchService.getCharacteristic(
2156
- Characteristic.On
2157
- ).value;
2158
- callback(null, value);
2159
- };
2160
-
2161
- SecuritySystem.prototype.setModeHomeSwitch = function (value, callback) {
2162
- if (value === false) {
2163
- callback(HK_NOT_ALLOWED_IN_CURRENT_STATE, false);
2164
- return;
2165
- }
2166
-
2167
- this.updateTargetState(
2168
- Characteristic.SecuritySystemTargetState.STAY_ARM,
2169
- originTypes.INTERNAL,
2170
- true,
2171
- null
2172
- );
2173
- callback(null);
2174
- };
2175
-
2176
- SecuritySystem.prototype.getModeAwaySwitch = function (callback) {
2177
- const value = this.modeAwaySwitchService.getCharacteristic(
2178
- Characteristic.On
2179
- ).value;
2180
- callback(null, value);
2181
- };
2182
-
2183
- SecuritySystem.prototype.setModeAwaySwitch = function (value, callback) {
2184
- if (value === false) {
2185
- callback(HK_NOT_ALLOWED_IN_CURRENT_STATE, false);
2186
- return;
2187
- }
2188
-
2189
- this.updateTargetState(
2190
- Characteristic.SecuritySystemTargetState.AWAY_ARM,
2191
- originTypes.INTERNAL,
2192
- true,
2193
- null
2194
- );
2195
- callback(null);
2196
- };
2197
-
2198
- SecuritySystem.prototype.getModeNightSwitch = function (callback) {
2199
- const value = this.modeNightSwitchService.getCharacteristic(
2200
- Characteristic.On
2201
- ).value;
2202
- callback(null, value);
2203
- };
2204
-
2205
- SecuritySystem.prototype.setModeNightSwitch = function (value, callback) {
2206
- if (value === false) {
2207
- callback(HK_NOT_ALLOWED_IN_CURRENT_STATE, false);
2208
- return;
2209
- }
2210
-
2211
- this.updateTargetState(
2212
- Characteristic.SecuritySystemTargetState.NIGHT_ARM,
2213
- originTypes.INTERNAL,
2214
- true,
2215
- null
2216
- );
2217
- callback(null);
2218
- };
2219
-
2220
- SecuritySystem.prototype.getModeOffSwitch = function (callback) {
2221
- const value = this.modeOffSwitchService.getCharacteristic(
2222
- Characteristic.On
2223
- ).value;
2224
- callback(null, value);
2225
- };
2226
-
2227
- SecuritySystem.prototype.setModeOffSwitch = function (value, callback) {
2228
- if (value === false) {
2229
- callback(HK_NOT_ALLOWED_IN_CURRENT_STATE, false);
2230
- return;
2231
- }
2232
-
2233
- this.updateTargetState(
2234
- Characteristic.SecuritySystemTargetState.DISARM,
2235
- originTypes.INTERNAL,
2236
- true,
2237
- null
2238
- );
2239
- callback(null);
2240
- };
2241
-
2242
- SecuritySystem.prototype.getModeAwayExtendedSwitch = function (callback) {
2243
- const value = this.modeAwayExtendedSwitchService.getCharacteristic(
2244
- Characteristic.On
2245
- ).value;
2246
- callback(null, value);
2247
- };
2248
-
2249
- SecuritySystem.prototype.setModeAwayExtendedSwitch = function (
2250
- value,
2251
- callback
2252
- ) {
2253
- if (value === false) {
2254
- callback(HK_NOT_ALLOWED_IN_CURRENT_STATE, false);
2255
- return;
2256
- }
2257
-
2258
- this.updateTargetState(
2259
- Characteristic.SecuritySystemTargetState.AWAY_ARM,
2260
- originTypes.INTERNAL,
2261
- true,
2262
- null
2263
- );
2264
- callback(null);
2265
- };
2266
-
2267
- SecuritySystem.prototype.getModePauseSwitch = function (callback) {
2268
- const value = this.modePauseSwitchService.getCharacteristic(
2269
- Characteristic.On
2270
- ).value;
2271
- callback(null, value);
2272
- };
2273
-
2274
- SecuritySystem.prototype.setModePauseSwitch = function (value, callback) {
2275
- if (
2276
- this.currentState ===
2277
- Characteristic.SecuritySystemCurrentState.ALARM_TRIGGERED
2278
- ) {
2279
- this.log.warn("Mode pause (Alarm is triggered)");
2280
- callback(HK_NOT_ALLOWED_IN_CURRENT_STATE, false);
2281
- return;
2282
- }
2283
-
2284
- if (value) {
2285
- if (
2286
- this.currentState === Characteristic.SecuritySystemCurrentState.DISARMED
2287
- ) {
2288
- this.log.warn("Mode pause (Not armed)");
2289
- callback(HK_NOT_ALLOWED_IN_CURRENT_STATE, false);
2290
- return;
2291
- }
2292
-
2293
- this.log.info("Mode pause (Started)");
2294
-
2295
- this.pausedCurrentState = this.currentState;
2296
- this.updateTargetState(
2297
- Characteristic.SecuritySystemTargetState.DISARM,
2298
- originTypes.INTERNAL,
2299
- true,
2300
- null
2301
- );
2302
-
2303
- // Check if time is set to unlimited
2304
- if (options.pauseMinutes !== 0) {
2305
- this.pauseTimeout = setTimeout(() => {
2306
- this.log.info("Mode pause (Finished)");
2307
- this.updateTargetState(
2308
- this.pausedCurrentState,
2309
- originTypes.INTERNAL,
2310
- true,
2311
- null
2312
- );
2313
- }, options.pauseMinutes * 60 * 1000);
2314
- }
2315
- } else {
2316
- this.log.info("Mode pause (Cancelled)");
2317
-
2318
- if (this.pauseTimeout !== null) {
2319
- clearTimeout(this.pauseTimeout);
2320
- this.pauseTimeout = null;
2321
- }
2322
-
2323
- this.updateTargetState(
2324
- this.pausedCurrentState,
2325
- originTypes.INTERNAL,
2326
- true,
2327
- null
2328
- );
2329
- }
2330
-
2331
- callback(null);
2332
- };
2333
-
2334
- SecuritySystem.prototype.getAudioSwitch = function (callback) {
2335
- const value = this.audioSwitchService.getCharacteristic(
2336
- Characteristic.On
2337
- ).value;
2338
- callback(null, value);
2339
- };
2340
-
2341
- SecuritySystem.prototype.setAudioSwitch = function (value, callback) {
2342
- this.log.info(`Audio (${value ? "Enabled" : "Disabled"})`);
2343
- callback(null);
2344
- };
2345
-
2346
- // Tripped Motion Sensor
2347
- SecuritySystem.prototype.getTrippedMotionDetected = function (callback) {
2348
- const value = this.trippedMotionSensorService.getCharacteristic(
2349
- Characteristic.MotionDetected
2350
- ).value;
2351
- callback(null, value);
2352
- };
2353
-
2354
- SecuritySystem.prototype.updateTrippedMotionDetected = function () {
2355
- this.trippedMotionSensorService.updateCharacteristic(
2356
- Characteristic.MotionDetected,
2357
- true
2358
- );
2359
-
2360
- setTimeout(() => {
2361
- this.trippedMotionSensorService.updateCharacteristic(
2362
- Characteristic.MotionDetected,
2363
- false
2364
- );
2365
- }, 750);
2366
- };
2367
-
2368
- // Triggered Motion Sensor
2369
- SecuritySystem.prototype.getTriggeredMotionDetected = function (callback) {
2370
- const value = this.triggeredMotionSensorService.getCharacteristic(
2371
- Characteristic.MotionDetected
2372
- ).value;
2373
- callback(null, value);
2374
- };
2375
-
2376
- SecuritySystem.prototype.updateTriggeredMotionDetected = function () {
2377
- this.triggeredMotionSensorService.updateCharacteristic(
2378
- Characteristic.MotionDetected,
2379
- true
2380
- );
2381
-
2382
- setTimeout(() => {
2383
- this.triggeredMotionSensorService.updateCharacteristic(
2384
- Characteristic.MotionDetected,
2385
- false
2386
- );
2387
- }, 750);
2388
- };
2389
-
2390
- // Triggered Reset Motion Sensor
2391
- SecuritySystem.prototype.getTriggeredResetMotionDetected = function (callback) {
2392
- const value = this.triggeredResetMotionSensorService.getCharacteristic(
2393
- Characteristic.MotionDetected
2394
- ).value;
2395
- callback(null, value);
2396
- };
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+ const storage = require("node-persist");
4
+ const { spawn } = require("child_process");
5
+ const fetch = require("node-fetch");
6
+ const express = require("express");
7
+ const rateLimit = require("express-rate-limit");
8
+
9
+ const packageJson = require("../package.json");
10
+ const options = require("./utils/options.js");
11
+
12
+ // HomeKit error
13
+ const HK_NOT_ALLOWED_IN_CURRENT_STATE = -70412;
14
+
15
+ const originTypes = {
16
+ REGULAR_SWITCH: 0,
17
+ SPECIAL_SWITCH: 1,
18
+ INTERNAL: 3,
19
+ EXTERNAL: 4,
20
+ };
21
+
22
+ const app = express();
23
+ let Service, Characteristic, storagePath;
24
+
25
+ module.exports = function (homebridge) {
26
+ Service = homebridge.hap.Service;
27
+ Characteristic = homebridge.hap.Characteristic;
28
+ storagePath = homebridge.user.storagePath();
29
+
30
+ homebridge.registerAccessory(
31
+ "homebridge-securitysystem",
32
+ "security-system",
33
+ SecuritySystem
34
+ );
35
+ };
36
+
37
+ function SecuritySystem(log, config) {
38
+ this.log = log;
39
+ options.init(log, config);
40
+
41
+ this.defaultState = this.mode2State(options.defaultMode);
42
+ this.currentState = this.defaultState;
43
+ this.targetState = this.defaultState;
44
+ this.availableTargetStates = null;
45
+
46
+ this.isArming = false;
47
+ this.isKnocked = false;
48
+
49
+ this.invalidCodeCount = 0;
50
+
51
+ this.pausedCurrentState = null;
52
+ this.audioProcess = null;
53
+
54
+ this.armTimeout = null;
55
+ this.pauseTimeout = null;
56
+ this.triggerTimeout = null;
57
+ this.doubleKnockTimeout = null;
58
+ this.resetTimeout = null;
59
+
60
+ this.trippedMotionSensorInterval = null;
61
+ this.triggeredMotionSensorInterval = null;
62
+
63
+ // File logger
64
+ if (options.isValueSet(options.logDirectory)) {
65
+ const logInfo = this.log.info.bind(this.log);
66
+ const logWarn = this.log.warn.bind(this.log);
67
+ const logError = this.log.error.bind(this.log);
68
+
69
+ this.log.info = (message) => {
70
+ logInfo.apply(null, [message]);
71
+ this.log.appendFile(message);
72
+ };
73
+
74
+ this.log.warn = (message) => {
75
+ logWarn.apply(null, [message]);
76
+ this.log.appendFile(message);
77
+ };
78
+
79
+ this.log.error = (message) => {
80
+ logError.apply(null, [message]);
81
+ this.log.appendFile(message);
82
+ };
83
+
84
+ this.log.appendFile = async (message) => {
85
+ const date = new Date();
86
+
87
+ try {
88
+ const stats = await fs.promises.stat(
89
+ `${options.logDirectory}/securitysystem.log`
90
+ );
91
+
92
+ if (
93
+ stats.birthtime.toLocaleDateString() !== date.toLocaleDateString()
94
+ ) {
95
+ await fs.promises.rename(
96
+ `${options.logDirectory}/securitysystem.log`,
97
+ `${options.logDirectory}/securitysystem-${stats.birthtime
98
+ .toLocaleDateString()
99
+ .replaceAll("/", "-")}.log`
100
+ );
101
+ }
102
+ } catch (error) {
103
+ this.log.debug("Previous log file not found.");
104
+ }
105
+
106
+ try {
107
+ await fs.promises.appendFile(
108
+ `${options.logDirectory}/securitysystem.log`,
109
+ `[${new Date().toLocaleString()}] ${message}\n`,
110
+ { flag: "a" }
111
+ );
112
+ } catch (error) {
113
+ logError("File logger (Error)");
114
+ logError(error);
115
+ }
116
+ };
117
+ }
118
+
119
+ // Log
120
+ if (options.testMode) {
121
+ this.log.warn("Test Mode");
122
+ }
123
+
124
+ this.logMode("Default", this.defaultState);
125
+ this.log.info(`Arm delay (${options.armSeconds} second/s)`);
126
+ this.log.info(`Trigger delay (${options.triggerSeconds} second/s)`);
127
+ this.log.info(`Audio (${options.audio ? "Enabled" : "Disabled"})`);
128
+
129
+ if (options.proxyMode) {
130
+ this.log.info("Proxy mode (Enabled)");
131
+ }
132
+
133
+ if (options.isValueSet(options.webhookUrl)) {
134
+ this.log.info(`Webhook (${options.webhookUrl})`);
135
+ }
136
+
137
+ // Security system
138
+ this.service = new Service.SecuritySystem(options.name);
139
+ this.availableTargetStates = this.getAvailableTargetStates();
140
+
141
+ this.service.getCharacteristic(
142
+ Characteristic.SecuritySystemTargetState
143
+ ).value = this.targetState;
144
+
145
+ this.service.addCharacteristic(Characteristic.ConfiguredName);
146
+
147
+ this.service
148
+ .setCharacteristic(Characteristic.ConfiguredName, options.name)
149
+ .getCharacteristic(Characteristic.SecuritySystemTargetState)
150
+ .setProps({ validValues: this.availableTargetStates })
151
+ .on("get", this.getTargetState.bind(this))
152
+ .on("set", this.setTargetState.bind(this));
153
+
154
+ this.service.getCharacteristic(
155
+ Characteristic.SecuritySystemCurrentState
156
+ ).value = this.currentState;
157
+
158
+ this.service
159
+ .getCharacteristic(Characteristic.SecuritySystemCurrentState)
160
+ .on("get", this.getCurrentState.bind(this));
161
+
162
+ // Trip switches
163
+ this.tripSwitchService = new Service.Switch(
164
+ options.tripSwitchName,
165
+ "siren-switch"
166
+ );
167
+
168
+ this.tripSwitchService.addCharacteristic(Characteristic.ConfiguredName);
169
+
170
+ this.tripSwitchService
171
+ .setCharacteristic(Characteristic.ConfiguredName, options.tripSwitchName)
172
+ .getCharacteristic(Characteristic.On)
173
+ .on("get", this.getTripSwitch.bind(this))
174
+ .on("set", this.setTripSwitch.bind(this));
175
+
176
+ this.tripHomeSwitchService = new Service.Switch(
177
+ options.tripHomeSwitchName,
178
+ "siren-home"
179
+ );
180
+
181
+ this.tripHomeSwitchService.addCharacteristic(Characteristic.ConfiguredName);
182
+
183
+ this.tripHomeSwitchService
184
+ .setCharacteristic(
185
+ Characteristic.ConfiguredName,
186
+ options.tripHomeSwitchName
187
+ )
188
+ .getCharacteristic(Characteristic.On)
189
+ .on("get", this.getTripHomeSwitch.bind(this))
190
+ .on("set", this.setTripHomeSwitch.bind(this));
191
+
192
+ this.tripAwaySwitchService = new Service.Switch(
193
+ options.tripAwaySwitchName,
194
+ "siren-away"
195
+ );
196
+
197
+ this.tripAwaySwitchService.addCharacteristic(Characteristic.ConfiguredName);
198
+
199
+ this.tripAwaySwitchService
200
+ .setCharacteristic(
201
+ Characteristic.ConfiguredName,
202
+ options.tripAwaySwitchName
203
+ )
204
+ .getCharacteristic(Characteristic.On)
205
+ .on("get", this.getTripAwaySwitch.bind(this))
206
+ .on("set", this.setTripAwaySwitch.bind(this));
207
+
208
+ this.tripNightSwitchService = new Service.Switch(
209
+ options.tripNightSwitchName,
210
+ "siren-night"
211
+ );
212
+
213
+ this.tripNightSwitchService.addCharacteristic(Characteristic.ConfiguredName);
214
+
215
+ this.tripNightSwitchService
216
+ .setCharacteristic(
217
+ Characteristic.ConfiguredName,
218
+ options.tripNightSwitchName
219
+ )
220
+ .getCharacteristic(Characteristic.On)
221
+ .on("get", this.getTripNightSwitch.bind(this))
222
+ .on("set", this.setTripNightSwitch.bind(this));
223
+
224
+ this.tripOverrideSwitchService = new Service.Switch(
225
+ options.tripOverrideSwitchName,
226
+ "pink-sheep"
227
+ );
228
+
229
+ this.tripOverrideSwitchService.addCharacteristic(
230
+ Characteristic.ConfiguredName
231
+ );
232
+
233
+ this.tripOverrideSwitchService
234
+ .setCharacteristic(
235
+ Characteristic.ConfiguredName,
236
+ options.tripOverrideSwitchName
237
+ )
238
+ .getCharacteristic(Characteristic.On)
239
+ .on("get", this.getTripOverrideSwitch.bind(this))
240
+ .on("set", this.setTripOverrideSwitch.bind(this));
241
+
242
+ // Arming lock switches
243
+ this.armingLockSwitchService = new Service.Switch(
244
+ "Arming Lock",
245
+ "arming-lock"
246
+ );
247
+
248
+ this.armingLockSwitchService.addCharacteristic(Characteristic.ConfiguredName);
249
+
250
+ this.armingLockSwitchService
251
+ .setCharacteristic(Characteristic.ConfiguredName, "Arming Lock")
252
+ .getCharacteristic(Characteristic.On)
253
+ .on("get", this.getArmingLockSwitch.bind(this))
254
+ .on("set", this.setArmingLockSwitch.bind(this));
255
+
256
+ this.armingLockHomeSwitchService = new Service.Switch(
257
+ "Arming Lock Home",
258
+ "arming-lock-home"
259
+ );
260
+
261
+ this.armingLockHomeSwitchService.addCharacteristic(
262
+ Characteristic.ConfiguredName
263
+ );
264
+
265
+ this.armingLockHomeSwitchService
266
+ .setCharacteristic(Characteristic.ConfiguredName, "Arming Lock Home")
267
+ .getCharacteristic(Characteristic.On)
268
+ .on("get", this.getArmingLockHomeSwitch.bind(this))
269
+ .on("set", this.setArmingLockHomeSwitch.bind(this));
270
+
271
+ this.armingLockAwaySwitchService = new Service.Switch(
272
+ "Arming Lock Away",
273
+ "arming-lock-away"
274
+ );
275
+
276
+ this.armingLockAwaySwitchService.addCharacteristic(
277
+ Characteristic.ConfiguredName
278
+ );
279
+
280
+ this.armingLockAwaySwitchService
281
+ .setCharacteristic(Characteristic.ConfiguredName, "Arming Lock Away")
282
+ .getCharacteristic(Characteristic.On)
283
+ .on("get", this.getArmingLockAwaySwitch.bind(this))
284
+ .on("set", this.setArmingLockAwaySwitch.bind(this));
285
+
286
+ this.armingLockNightSwitchService = new Service.Switch(
287
+ "Arming Lock Night",
288
+ "arming-lock-night"
289
+ );
290
+
291
+ this.armingLockNightSwitchService.addCharacteristic(
292
+ Characteristic.ConfiguredName
293
+ );
294
+
295
+ this.armingLockNightSwitchService
296
+ .setCharacteristic(Characteristic.ConfiguredName, "Arming Lock Night")
297
+ .getCharacteristic(Characteristic.On)
298
+ .on("get", this.getArmingLockNightSwitch.bind(this))
299
+ .on("set", this.setArmingLockNightSwitch.bind(this));
300
+
301
+ // Mode switches
302
+ this.modeHomeSwitchService = new Service.Switch(
303
+ options.modeHomeSwitchName,
304
+ "mode-home"
305
+ );
306
+
307
+ this.modeHomeSwitchService.addCharacteristic(Characteristic.ConfiguredName);
308
+
309
+ this.modeHomeSwitchService
310
+ .setCharacteristic(
311
+ Characteristic.ConfiguredName,
312
+ options.modeHomeSwitchName
313
+ )
314
+ .getCharacteristic(Characteristic.On)
315
+ .on("get", this.getModeHomeSwitch.bind(this))
316
+ .on("set", this.setModeHomeSwitch.bind(this));
317
+
318
+ this.modeAwaySwitchService = new Service.Switch(
319
+ options.modeAwaySwitchName,
320
+ "mode-away"
321
+ );
322
+
323
+ this.modeAwaySwitchService.addCharacteristic(Characteristic.ConfiguredName);
324
+
325
+ this.modeAwaySwitchService
326
+ .setCharacteristic(
327
+ Characteristic.ConfiguredName,
328
+ options.modeAwaySwitchName
329
+ )
330
+ .getCharacteristic(Characteristic.On)
331
+ .on("get", this.getModeAwaySwitch.bind(this))
332
+ .on("set", this.setModeAwaySwitch.bind(this));
333
+
334
+ this.modeNightSwitchService = new Service.Switch(
335
+ options.modeNightSwitchName,
336
+ "mode-night"
337
+ );
338
+
339
+ this.modeNightSwitchService.addCharacteristic(Characteristic.ConfiguredName);
340
+
341
+ this.modeNightSwitchService
342
+ .setCharacteristic(
343
+ Characteristic.ConfiguredName,
344
+ options.modeNightSwitchName
345
+ )
346
+ .getCharacteristic(Characteristic.On)
347
+ .on("get", this.getModeNightSwitch.bind(this))
348
+ .on("set", this.setModeNightSwitch.bind(this));
349
+
350
+ this.modeOffSwitchService = new Service.Switch(
351
+ options.modeOffSwitchName,
352
+ "mode-off"
353
+ );
354
+
355
+ this.modeOffSwitchService.addCharacteristic(Characteristic.ConfiguredName);
356
+
357
+ this.modeOffSwitchService
358
+ .setCharacteristic(Characteristic.ConfiguredName, options.modeOffSwitchName)
359
+ .getCharacteristic(Characteristic.On)
360
+ .on("get", this.getModeOffSwitch.bind(this))
361
+ .on("set", this.setModeOffSwitch.bind(this));
362
+
363
+ this.modeAwayExtendedSwitchService = new Service.Switch(
364
+ "Mode Away Extended",
365
+ "mode-away-extended"
366
+ );
367
+
368
+ this.modeAwayExtendedSwitchService.addCharacteristic(
369
+ Characteristic.ConfiguredName
370
+ );
371
+
372
+ this.modeAwayExtendedSwitchService
373
+ .setCharacteristic(Characteristic.ConfiguredName, "Mode Away Extended")
374
+ .getCharacteristic(Characteristic.On)
375
+ .on("get", this.getModeAwayExtendedSwitch.bind(this))
376
+ .on("set", this.setModeAwayExtendedSwitch.bind(this));
377
+
378
+ this.modePauseSwitchService = new Service.Switch("Mode Pause", "mode-pause");
379
+ this.modePauseSwitchService.addCharacteristic(Characteristic.ConfiguredName);
380
+
381
+ this.modePauseSwitchService
382
+ .setCharacteristic(Characteristic.ConfiguredName, "Mode Pause")
383
+ .getCharacteristic(Characteristic.On)
384
+ .on("get", this.getModePauseSwitch.bind(this))
385
+ .on("set", this.setModePauseSwitch.bind(this));
386
+
387
+ // Audio switch
388
+ this.audioSwitchService = new Service.Switch(
389
+ "Audio",
390
+ "kx82r64zN3txDXKFiX9JDi"
391
+ );
392
+
393
+ this.audioSwitchService.addCharacteristic(Characteristic.ConfiguredName);
394
+
395
+ this.audioSwitchService
396
+ .setCharacteristic(Characteristic.ConfiguredName, "Audio")
397
+ .getCharacteristic(Characteristic.On)
398
+ .on("get", this.getAudioSwitch.bind(this))
399
+ .on("set", this.setAudioSwitch.bind(this));
400
+
401
+ this.audioSwitchService.getCharacteristic(Characteristic.On).value = true;
402
+
403
+ // Tripped / Triggered sensors
404
+ this.trippedMotionSensorService = new Service.MotionSensor(
405
+ "Tripped",
406
+ "siren-tripped"
407
+ );
408
+
409
+ this.trippedMotionSensorService.addCharacteristic(
410
+ Characteristic.ConfiguredName
411
+ );
412
+
413
+ this.trippedMotionSensorService
414
+ .setCharacteristic(Characteristic.ConfiguredName, "Tripped")
415
+ .getCharacteristic(Characteristic.MotionDetected)
416
+ .on("get", this.getTrippedMotionDetected.bind(this));
417
+
418
+ this.triggeredMotionSensorService = new Service.MotionSensor(
419
+ "Triggered",
420
+ "siren-triggered"
421
+ );
422
+
423
+ this.triggeredMotionSensorService.addCharacteristic(
424
+ Characteristic.ConfiguredName
425
+ );
426
+
427
+ this.triggeredMotionSensorService
428
+ .setCharacteristic(Characteristic.ConfiguredName, "Triggered")
429
+ .getCharacteristic(Characteristic.MotionDetected)
430
+ .on("get", this.getTriggeredMotionDetected.bind(this));
431
+
432
+ this.triggeredResetMotionSensorService = new Service.MotionSensor(
433
+ "Triggered Reset",
434
+ "reset-event"
435
+ );
436
+
437
+ this.triggeredResetMotionSensorService.addOptionalCharacteristic(
438
+ Characteristic.ConfiguredName
439
+ );
440
+
441
+ this.triggeredResetMotionSensorService
442
+ .setCharacteristic(Characteristic.ConfiguredName, "Triggered Reset")
443
+ .getCharacteristic(Characteristic.MotionDetected)
444
+ .on("get", this.getTriggeredResetMotionDetected.bind(this));
445
+
446
+ // Accessory information
447
+ this.accessoryInformationService = new Service.AccessoryInformation();
448
+
449
+ this.accessoryInformationService.setCharacteristic(
450
+ Characteristic.Identify,
451
+ true
452
+ );
453
+ this.accessoryInformationService.setCharacteristic(
454
+ Characteristic.Manufacturer,
455
+ "MiguelRipoll23"
456
+ );
457
+ this.accessoryInformationService.setCharacteristic(
458
+ Characteristic.Model,
459
+ "DIY"
460
+ );
461
+ this.accessoryInformationService.setCharacteristic(
462
+ Characteristic.Name,
463
+ "homebridge-securitysystem"
464
+ );
465
+ this.accessoryInformationService.setCharacteristic(
466
+ Characteristic.SerialNumber,
467
+ options.serialNumber
468
+ );
469
+ this.accessoryInformationService.setCharacteristic(
470
+ Characteristic.FirmwareRevision,
471
+ packageJson.version
472
+ );
473
+
474
+ // Services list
475
+ this.services = [this.service, this.accessoryInformationService];
476
+
477
+ if (options.trippedMotionSensor) {
478
+ this.services.push(this.trippedMotionSensorService);
479
+ }
480
+
481
+ if (options.triggeredMotionSensor) {
482
+ this.services.push(this.triggeredMotionSensorService);
483
+ }
484
+
485
+ if (options.resetSensor) {
486
+ this.services.push(this.triggeredResetMotionSensorService);
487
+ }
488
+
489
+ if (options.armingLockSwitch) {
490
+ this.services.push(this.armingLockSwitchService);
491
+ }
492
+
493
+ if (options.armingLockSwitches) {
494
+ this.services.push(this.armingLockHomeSwitchService);
495
+ this.services.push(this.armingLockAwaySwitchService);
496
+ this.services.push(this.armingLockNightSwitchService);
497
+ }
498
+
499
+ if (options.tripSwitch) {
500
+ this.services.push(this.tripSwitchService);
501
+ }
502
+
503
+ if (options.tripOverrideSwitch) {
504
+ this.services.push(this.tripOverrideSwitchService);
505
+ }
506
+
507
+ if (
508
+ this.availableTargetStates.includes(
509
+ Characteristic.SecuritySystemTargetState.STAY_ARM
510
+ )
511
+ ) {
512
+ if (options.modeSwitches) {
513
+ this.services.push(this.modeHomeSwitchService);
514
+ }
515
+
516
+ if (options.tripModeSwitches) {
517
+ this.services.push(this.tripHomeSwitchService);
518
+ }
519
+ }
520
+
521
+ if (
522
+ this.availableTargetStates.includes(
523
+ Characteristic.SecuritySystemTargetState.AWAY_ARM
524
+ )
525
+ ) {
526
+ if (options.modeSwitches) {
527
+ this.services.push(this.modeAwaySwitchService);
528
+ }
529
+
530
+ if (options.tripModeSwitches) {
531
+ this.services.push(this.tripAwaySwitchService);
532
+ }
533
+ }
534
+
535
+ if (
536
+ this.availableTargetStates.includes(
537
+ Characteristic.SecuritySystemTargetState.NIGHT_ARM
538
+ )
539
+ ) {
540
+ if (options.modeSwitches) {
541
+ this.services.push(this.modeNightSwitchService);
542
+ }
543
+
544
+ if (options.tripModeSwitches) {
545
+ this.services.push(this.tripNightSwitchService);
546
+ }
547
+ }
548
+
549
+ if (options.modeSwitches && options.modeOffSwitch) {
550
+ this.services.push(this.modeOffSwitchService);
551
+ }
552
+
553
+ if (options.modeAwayExtendedSwitch) {
554
+ this.services.push(this.modeAwayExtendedSwitchService);
555
+ }
556
+
557
+ if (options.modePauseSwitch) {
558
+ this.services.push(this.modePauseSwitchService);
559
+ }
560
+
561
+ if (options.audio && options.audioSwitch) {
562
+ this.services.push(this.audioSwitchService);
563
+ }
564
+
565
+ // Storage
566
+ if (options.saveState) {
567
+ this.load();
568
+ }
569
+
570
+ // Audio
571
+ if (options.isValueSet(options.audioPath)) {
572
+ this.setupAudio();
573
+ }
574
+
575
+ // Server
576
+ if (options.isValueSet(options.serverPort)) {
577
+ this.startServer();
578
+ }
579
+ }
580
+
581
+ SecuritySystem.prototype.getServices = function () {
582
+ return this.services;
583
+ };
584
+
585
+ SecuritySystem.prototype.load = async function () {
586
+ const storageOptions = {
587
+ dir: path.join(storagePath, "homebridge-securitysystem"),
588
+ };
589
+
590
+ await storage
591
+ .init(storageOptions)
592
+ .then()
593
+ .catch((error) => {
594
+ this.log.error("Unable to load state.");
595
+ this.log.error(error);
596
+ });
597
+
598
+ if (options.testMode) {
599
+ await storage.clear();
600
+ this.log.debug("Saved data from the plugin cleared.");
601
+
602
+ return;
603
+ }
604
+
605
+ await storage
606
+ .getItem("state")
607
+ .then((state) => {
608
+ if (state === undefined) {
609
+ return;
610
+ }
611
+
612
+ this.log.debug("State (Loaded)", state);
613
+ this.log.info("Saved state (Found)");
614
+
615
+ const currentState = options.isValueSet(state.currentState)
616
+ ? state.currentState
617
+ : this.defaultState;
618
+ const targetState = options.isValueSet(state.targetState)
619
+ ? state.targetState
620
+ : this.defaultState;
621
+
622
+ // Change target state if triggered
623
+ if (
624
+ currentState ===
625
+ Characteristic.SecuritySystemCurrentState.ALARM_TRIGGERED
626
+ ) {
627
+ this.targetState = targetState;
628
+ } else {
629
+ this.targetState = currentState;
630
+ }
631
+
632
+ this.currentState = currentState;
633
+
634
+ // Update characteristics values
635
+ this.service.updateCharacteristic(
636
+ Characteristic.SecuritySystemTargetState,
637
+ this.targetState
638
+ );
639
+ this.service.updateCharacteristic(
640
+ Characteristic.SecuritySystemCurrentState,
641
+ this.currentState
642
+ );
643
+ this.handleStateUpdate(false);
644
+
645
+ // Log
646
+ this.logMode("Current", this.currentState);
647
+ })
648
+ .catch((error) => {
649
+ this.log.error("Saved state (Error)");
650
+ this.log.error(error);
651
+ });
652
+ };
653
+
654
+ SecuritySystem.prototype.save = async function () {
655
+ // Check option
656
+ if (options.saveState === false) {
657
+ return;
658
+ }
659
+
660
+ if (storage.defaultInstance === undefined) {
661
+ this.log.error("Unable to save state.");
662
+ return;
663
+ }
664
+
665
+ const state = {
666
+ currentState: this.currentState,
667
+ targetState: this.targetState,
668
+ };
669
+
670
+ await storage
671
+ .setItem("state", state)
672
+ .then(() => {
673
+ this.log.debug("State (Saved)", state);
674
+ })
675
+ .catch((error) => {
676
+ this.log.error("Unable to save state.");
677
+ this.log.error(error);
678
+ });
679
+ };
680
+
681
+ SecuritySystem.prototype.identify = function (callback) {
682
+ this.log.info("Identify");
683
+ callback(null);
684
+ };
685
+
686
+ // Security system
687
+ SecuritySystem.prototype.state2Mode = function (state) {
688
+ switch (state) {
689
+ case Characteristic.SecuritySystemCurrentState.ALARM_TRIGGERED:
690
+ return "triggered";
691
+
692
+ case Characteristic.SecuritySystemCurrentState.STAY_ARM:
693
+ return "home";
694
+
695
+ case Characteristic.SecuritySystemCurrentState.AWAY_ARM:
696
+ return "away";
697
+
698
+ case Characteristic.SecuritySystemCurrentState.NIGHT_ARM:
699
+ return "night";
700
+
701
+ case Characteristic.SecuritySystemCurrentState.DISARMED:
702
+ return "off";
703
+
704
+ // Custom
705
+ case "lock":
706
+ // Audio sound
707
+ return state;
708
+
709
+ case "warning":
710
+ return state;
711
+
712
+ default:
713
+ this.log.error(`Unknown state (${state}).`);
714
+ return "unknown";
715
+ }
716
+ };
717
+
718
+ SecuritySystem.prototype.mode2State = function (mode) {
719
+ switch (mode) {
720
+ case "home":
721
+ return Characteristic.SecuritySystemCurrentState.STAY_ARM;
722
+
723
+ case "away":
724
+ return Characteristic.SecuritySystemCurrentState.AWAY_ARM;
725
+
726
+ case "night":
727
+ return Characteristic.SecuritySystemCurrentState.NIGHT_ARM;
728
+
729
+ case "off":
730
+ return Characteristic.SecuritySystemCurrentState.DISARMED;
731
+
732
+ default:
733
+ this.log.error(`Unknown mode (${mode}).`);
734
+ return -1;
735
+ }
736
+ };
737
+
738
+ SecuritySystem.prototype.logMode = function (type, state) {
739
+ let mode = this.state2Mode(state);
740
+ mode = mode.charAt(0).toUpperCase() + mode.slice(1);
741
+
742
+ this.log.info(`${type} mode (${mode})`);
743
+ };
744
+
745
+ SecuritySystem.prototype.getAvailableTargetStates = function () {
746
+ const targetStateCharacteristic = this.service.getCharacteristic(
747
+ Characteristic.SecuritySystemTargetState
748
+ );
749
+ const validValues = targetStateCharacteristic.props.validValues;
750
+ const invalidValues = options.disabledModes.map((value) => {
751
+ return this.mode2State(value.toLowerCase());
752
+ });
753
+
754
+ return validValues.filter((state) => invalidValues.includes(state) === false);
755
+ };
756
+
757
+ SecuritySystem.prototype.getCurrentState = function (callback) {
758
+ callback(null, this.currentState);
759
+ };
760
+
761
+ SecuritySystem.prototype.setCurrentState = function (state, origin) {
762
+ // Check if mode already set
763
+ if (this.currentState === state) {
764
+ return;
765
+ }
766
+
767
+ this.currentState = state;
768
+ this.service.setCharacteristic(
769
+ Characteristic.SecuritySystemCurrentState,
770
+ state
771
+ );
772
+ this.logMode("Current", state);
773
+
774
+ // Audio
775
+ this.playAudio("current", state);
776
+
777
+ // Commands
778
+ this.executeCommand("current", state, origin);
779
+
780
+ // Webhooks
781
+ this.sendWebhookEvent("current", state, origin);
782
+
783
+ if (state === Characteristic.SecuritySystemCurrentState.ALARM_TRIGGERED) {
784
+ // Update triggered motion sensor
785
+ this.triggeredMotionSensorInterval = setInterval(() => {
786
+ this.updateTriggeredMotionDetected();
787
+ }, options.triggeredMotionSensorSeconds * 1000);
788
+
789
+ // Automatically arm the security system
790
+ // when time runs out
791
+ this.resetTimeout = setTimeout(() => {
792
+ this.resetTimeout = null;
793
+ this.log.info("Reset (Finished)");
794
+
795
+ // Update triggered reset motion sensor
796
+ this.triggeredResetMotionSensorService.updateCharacteristic(
797
+ Characteristic.MotionDetected,
798
+ true
799
+ );
800
+
801
+ setTimeout(() => {
802
+ this.triggeredResetMotionSensorService.updateCharacteristic(
803
+ Characteristic.MotionDetected,
804
+ false
805
+ );
806
+ }, 750);
807
+
808
+ // Alternative flow (Triggered -> Off -> Armed mode)
809
+ if (options.resetOffFlow) {
810
+ const originalTargetState = this.targetState;
811
+ this.updateTargetState(
812
+ Characteristic.SecuritySystemTargetState.DISARM,
813
+ originTypes.INTERNAL,
814
+ false,
815
+ null
816
+ );
817
+
818
+ setTimeout(() => {
819
+ this.updateTargetState(
820
+ originalTargetState,
821
+ originTypes.INTERNAL,
822
+ true,
823
+ null
824
+ );
825
+ }, 100);
826
+
827
+ return;
828
+ }
829
+
830
+ // Normal flow
831
+ this.handleStateUpdate(false);
832
+ this.setCurrentState(this.targetState, false);
833
+ }, options.resetMinutes * 60 * 1000);
834
+ }
835
+
836
+ this.save();
837
+ };
838
+
839
+ SecuritySystem.prototype.resetTimers = function () {
840
+ // Clear trigger timeout
841
+ if (this.triggerTimeout !== null) {
842
+ clearTimeout(this.triggerTimeout);
843
+
844
+ this.triggerTimeout = null;
845
+ this.log.debug("Trigger timeout (Cleared)");
846
+ }
847
+
848
+ // Clear arming timeout
849
+ if (this.armTimeout !== null) {
850
+ clearTimeout(this.armTimeout);
851
+
852
+ this.armTimeout = null;
853
+ this.log.debug("Arming timeout (Cleared)");
854
+ }
855
+
856
+ // Clear triggered motion sensor
857
+ if (this.triggeredMotionSensorInterval !== null) {
858
+ clearInterval(this.triggeredMotionSensorInterval);
859
+
860
+ this.triggeredMotionSensorInterval = null;
861
+ this.log.debug("Triggered interval (Cleared)");
862
+ }
863
+
864
+ // Clear tripped sensor
865
+ if (this.trippedMotionSensorInterval !== null) {
866
+ clearInterval(this.trippedMotionSensorInterval);
867
+
868
+ this.trippedMotionSensorInterval = null;
869
+ this.log.debug("Tripped interval (Cleared)");
870
+ }
871
+
872
+ // Clear double-knock timeout
873
+ if (this.doubleKnockTimeout !== null) {
874
+ clearTimeout(this.doubleKnockTimeout);
875
+ this.doubleKnockTimeout = null;
876
+
877
+ this.log.debug("Double-knock timeout (Cleared)");
878
+ }
879
+
880
+ // Clear pause timeout
881
+ if (this.pauseTimeout !== null) {
882
+ clearTimeout(this.pauseTimeout);
883
+ this.pauseTimeout = null;
884
+
885
+ this.log.debug("Pause timeout (Cleared)");
886
+ }
887
+
888
+ // Clear security system reset timeout
889
+ if (this.resetTimeout !== null) {
890
+ clearTimeout(this.resetTimeout);
891
+
892
+ this.resetTimeout = null;
893
+ this.log.debug("Reset timeout (Cleared)");
894
+ }
895
+ };
896
+
897
+ SecuritySystem.prototype.handleStateUpdate = function (alarmTriggered) {
898
+ // Reset double-knock
899
+ this.isKnocked = false;
900
+
901
+ this.resetTimers();
902
+ this.resetModeSwitches();
903
+ this.updateModeSwitches();
904
+
905
+ // Keep characteristic & switches on
906
+ if (alarmTriggered) {
907
+ return;
908
+ }
909
+
910
+ const trippedOnCharacteristic = this.tripSwitchService.getCharacteristic(
911
+ Characteristic.On
912
+ );
913
+
914
+ if (trippedOnCharacteristic.value) {
915
+ this.updateTripSwitch(false, originTypes.INTERNAL, true, null);
916
+ }
917
+
918
+ this.resetTripSwitches();
919
+ };
920
+
921
+ SecuritySystem.prototype.updateTargetState = function (
922
+ state,
923
+ origin,
924
+ delay,
925
+ callback
926
+ ) {
927
+ const isTargetStateAlreadySet = this.targetState === state;
928
+ const isCurrentStateAlarmTriggered =
929
+ this.currentState ===
930
+ Characteristic.SecuritySystemCurrentState.ALARM_TRIGGERED;
931
+ const isTargetStateDisarm =
932
+ state === Characteristic.SecuritySystemTargetState.DISARM;
933
+
934
+ // Check if target state is already set
935
+ if (isTargetStateAlreadySet && isCurrentStateAlarmTriggered === false) {
936
+ this.log.warn("Target mode (Already set)");
937
+
938
+ if (callback !== null) {
939
+ callback(null);
940
+ }
941
+
942
+ return false;
943
+ }
944
+
945
+ // Check if state is enabled
946
+ if (this.availableTargetStates.includes(state) === false) {
947
+ this.log.warn("Target mode (Disabled)");
948
+
949
+ if (callback !== null) {
950
+ // Tip: this will revert the original state
951
+ // HomeKit error
952
+ callback(Characteristic.SecuritySystemTargetState.DISARM);
953
+ }
954
+
955
+ return false;
956
+ }
957
+
958
+ // Check arming lock switches
959
+ const isArmingLockEnabled =
960
+ options.isValueSet(options.armingLockSwitch) ||
961
+ options.isValueSet(options.armingLockSwitches);
962
+
963
+ if (
964
+ isTargetStateDisarm === false &&
965
+ isArmingLockEnabled &&
966
+ this.isArmingLocked(state)
967
+ ) {
968
+ this.log.warn("Arming lock (Not allowed)");
969
+
970
+ if (callback !== null) {
971
+ // Tip: this will revert the original state
972
+ // HomeKit error
973
+ callback(Characteristic.SecuritySystemTargetState.DISARM);
974
+ }
975
+
976
+ return false;
977
+ }
978
+
979
+ // Update target state
980
+ this.targetState = state;
981
+ this.logMode("Target", state);
982
+
983
+ const isTargetStateHome =
984
+ this.targetState === Characteristic.SecuritySystemTargetState.STAY_ARM;
985
+ const isTargetStateAway =
986
+ this.targetState === Characteristic.SecuritySystemTargetState.AWAY_ARM;
987
+ const isTargetStateNight =
988
+ this.targetState === Characteristic.SecuritySystemTargetState.NIGHT_ARM;
989
+
990
+ // Update characteristic
991
+ if (origin === originTypes.INTERNAL || origin === originTypes.EXTERNAL) {
992
+ this.service.updateCharacteristic(
993
+ Characteristic.SecuritySystemTargetState,
994
+ this.targetState
995
+ );
996
+ }
997
+
998
+ // Reset everything
999
+ this.handleStateUpdate(false);
1000
+
1001
+ // Commands
1002
+ this.executeCommand("target", state, origin);
1003
+
1004
+ // Webhooks
1005
+ this.sendWebhookEvent("target", state, origin);
1006
+
1007
+ // Check if current state is already set
1008
+ if (state === this.currentState) {
1009
+ this.log.warn("Current mode (Already set)");
1010
+
1011
+ // Play audio
1012
+ this.playAudio("current", this.currentState);
1013
+
1014
+ if (callback !== null) {
1015
+ callback(null);
1016
+ }
1017
+
1018
+ return false;
1019
+ }
1020
+
1021
+ // Set arming delay
1022
+ let armSeconds = 0;
1023
+
1024
+ if (delay) {
1025
+ armSeconds = options.armSeconds;
1026
+
1027
+ // No delay when triggered or set to Off
1028
+ if (isCurrentStateAlarmTriggered || isTargetStateDisarm) {
1029
+ armSeconds = 0;
1030
+ }
1031
+
1032
+ // Custom mode seconds
1033
+ if (isTargetStateHome && options.isValueSet(options.homeArmSeconds)) {
1034
+ armSeconds = options.homeArmSeconds;
1035
+ } else if (
1036
+ isTargetStateAway &&
1037
+ options.isValueSet(options.awayArmSeconds)
1038
+ ) {
1039
+ armSeconds = options.awayArmSeconds;
1040
+ } else if (
1041
+ isTargetStateNight &&
1042
+ options.isValueSet(options.nightArmSeconds)
1043
+ ) {
1044
+ armSeconds = options.nightArmSeconds;
1045
+ }
1046
+
1047
+ // Delay actions
1048
+ if (armSeconds > 0) {
1049
+ this.isArming = true;
1050
+
1051
+ // Play sound
1052
+ this.playAudio("target", state);
1053
+
1054
+ // Log
1055
+ this.log.info("Arm delay (" + armSeconds + " second/s)");
1056
+ }
1057
+ }
1058
+
1059
+ // Arm the security system
1060
+ this.armTimeout = setTimeout(() => {
1061
+ this.armTimeout = null;
1062
+ this.setCurrentState(state, origin);
1063
+ this.isArming = false;
1064
+ }, armSeconds * 1000);
1065
+
1066
+ if (callback !== null) {
1067
+ callback(null);
1068
+ }
1069
+
1070
+ return true;
1071
+ };
1072
+
1073
+ SecuritySystem.prototype.getTargetState = function (callback) {
1074
+ callback(null, this.targetState);
1075
+ };
1076
+
1077
+ SecuritySystem.prototype.setTargetState = function (value, callback) {
1078
+ this.updateTargetState(value, originTypes.REGULAR_SWITCH, true, callback);
1079
+ };
1080
+
1081
+ SecuritySystem.prototype.updateTripSwitch = function (
1082
+ value,
1083
+ origin,
1084
+ stateChanged,
1085
+ callback
1086
+ ) {
1087
+ const isCurrentStateAlarmTriggered =
1088
+ this.currentState ===
1089
+ Characteristic.SecuritySystemCurrentState.ALARM_TRIGGERED;
1090
+ const isCurrentStateHome =
1091
+ this.currentState === Characteristic.SecuritySystemCurrentState.STAY_ARM;
1092
+ const isCurrentStateAway =
1093
+ this.currentState === Characteristic.SecuritySystemCurrentState.AWAY_ARM;
1094
+ const isCurrentStateNight =
1095
+ this.currentState === Characteristic.SecuritySystemCurrentState.NIGHT_ARM;
1096
+ const isCurrentStateDisarmed =
1097
+ this.currentState === Characteristic.SecuritySystemCurrentState.DISARMED;
1098
+
1099
+ // Check if the security system is disarmed
1100
+ const isNotOverridingOff = options.overrideOff === false;
1101
+ const isNotSpecialSwitch = origin !== originTypes.SPECIAL_SWITCH;
1102
+
1103
+ if (isCurrentStateDisarmed && isNotOverridingOff && isNotSpecialSwitch) {
1104
+ this.log.warn("Trip Switch (Not armed)");
1105
+
1106
+ if (callback !== null) {
1107
+ callback(HK_NOT_ALLOWED_IN_CURRENT_STATE, false);
1108
+ }
1109
+
1110
+ return false;
1111
+ }
1112
+
1113
+ // Check if arming
1114
+ if (this.isArming) {
1115
+ this.log.warn("Trip Switch (Still arming)");
1116
+
1117
+ if (callback !== null) {
1118
+ callback(HK_NOT_ALLOWED_IN_CURRENT_STATE, false);
1119
+ }
1120
+
1121
+ return false;
1122
+ }
1123
+
1124
+ // Check double knock
1125
+ if (options.doubleKnock) {
1126
+ const doubleKnockStates = options.doubleKnockModes.map((value) => {
1127
+ return this.mode2State(value.toLowerCase());
1128
+ });
1129
+
1130
+ const isFirstKnock = this.isKnocked === false;
1131
+ const isSpecialSwitch = origin === originTypes.SPECIAL_SWITCH;
1132
+ const isStateKnockable = doubleKnockStates.includes(this.currentState);
1133
+
1134
+ if (
1135
+ value &&
1136
+ isStateKnockable &&
1137
+ isFirstKnock &&
1138
+ isSpecialSwitch === false
1139
+ ) {
1140
+ this.log.warn("Trip Switch (Knock)");
1141
+ this.isKnocked = true;
1142
+
1143
+ // Custom mode seconds
1144
+ let doubleKnockSeconds = options.doubleKnockSeconds;
1145
+
1146
+ if (
1147
+ isCurrentStateHome &&
1148
+ options.isValueSet(options.homeDoubleKnockSeconds)
1149
+ ) {
1150
+ doubleKnockSeconds = options.homeDoubleKnockSeconds;
1151
+ } else if (
1152
+ isCurrentStateAway &&
1153
+ options.isValueSet(options.awayDoubleKnockSeconds)
1154
+ ) {
1155
+ doubleKnockSeconds = options.awayDoubleKnockSeconds;
1156
+ } else if (
1157
+ isCurrentStateNight &&
1158
+ options.isValueSet(options.nightDoubleKnockSeconds)
1159
+ ) {
1160
+ doubleKnockSeconds = options.nightDoubleKnockSeconds;
1161
+ }
1162
+
1163
+ this.doubleKnockTimeout = setTimeout(() => {
1164
+ this.doubleKnockTimeout = null;
1165
+ this.isKnocked = false;
1166
+
1167
+ this.log.info("Trip Switch (Reset)");
1168
+ }, doubleKnockSeconds * 1000);
1169
+
1170
+ if (callback !== null) {
1171
+ callback(HK_NOT_ALLOWED_IN_CURRENT_STATE, false);
1172
+ }
1173
+
1174
+ return false;
1175
+ }
1176
+ }
1177
+
1178
+ // Clear double-knock timeout
1179
+ if (this.doubleKnockTimeout !== null) {
1180
+ clearTimeout(this.doubleKnockTimeout);
1181
+ this.doubleKnockTimeout = null;
1182
+
1183
+ this.log.debug("Double-knock timeout (Cleared)");
1184
+ }
1185
+
1186
+ if (origin === originTypes.INTERNAL || origin === originTypes.EXTERNAL) {
1187
+ this.tripSwitchService.updateCharacteristic(Characteristic.On, value);
1188
+ }
1189
+
1190
+ if (value) {
1191
+ // Already triggered
1192
+ if (isCurrentStateAlarmTriggered) {
1193
+ this.log.warn("Security System (Already triggered)");
1194
+
1195
+ if (callback !== null) {
1196
+ callback(HK_NOT_ALLOWED_IN_CURRENT_STATE, false);
1197
+ }
1198
+
1199
+ return false;
1200
+ }
1201
+
1202
+ // Already about to trigger
1203
+ if (this.triggerTimeout !== null) {
1204
+ this.log.warn("Security System (Already tripped)");
1205
+
1206
+ if (callback !== null) {
1207
+ callback(HK_NOT_ALLOWED_IN_CURRENT_STATE, false);
1208
+ }
1209
+
1210
+ return false;
1211
+ }
1212
+
1213
+ this.log.info("Security System (Tripped)");
1214
+
1215
+ // Update tripped motion sensor
1216
+ if (options.trippedMotionSensor) {
1217
+ this.updateTrippedMotionDetected();
1218
+
1219
+ this.trippedMotionSensorInterval = setInterval(() => {
1220
+ this.updateTrippedMotionDetected();
1221
+ }, options.trippedMotionSensorSeconds * 1000);
1222
+ }
1223
+
1224
+ const isCurrentStateHome =
1225
+ this.currentState === Characteristic.SecuritySystemCurrentState.STAY_ARM;
1226
+ const isCurrentStateAway =
1227
+ this.currentState === Characteristic.SecuritySystemCurrentState.AWAY_ARM;
1228
+ const isCurrentStateNight =
1229
+ this.currentState === Characteristic.SecuritySystemCurrentState.NIGHT_ARM;
1230
+
1231
+ // Set trigger delay
1232
+ let triggerSeconds = options.triggerSeconds;
1233
+
1234
+ // User options
1235
+ if (isCurrentStateHome && options.isValueSet(options.homeTriggerSeconds)) {
1236
+ triggerSeconds = options.homeTriggerSeconds;
1237
+ }
1238
+
1239
+ if (isCurrentStateAway) {
1240
+ const modeAwayExtendedSwitchCharacteristicOn =
1241
+ this.modeAwayExtendedSwitchService.getCharacteristic(Characteristic.On);
1242
+ const modeAwayExtendedSwitchCharacteristicOnValue =
1243
+ modeAwayExtendedSwitchCharacteristicOn.value;
1244
+
1245
+ if (
1246
+ options.isValueSet(options.awayExtendedTriggerSeconds) &&
1247
+ modeAwayExtendedSwitchCharacteristicOnValue
1248
+ ) {
1249
+ triggerSeconds = options.awayExtendedTriggerSeconds;
1250
+ } else if (options.isValueSet(options.awayTriggerSeconds)) {
1251
+ triggerSeconds = options.awayTriggerSeconds;
1252
+ }
1253
+ }
1254
+
1255
+ if (
1256
+ isCurrentStateNight &&
1257
+ options.isValueSet(options.nightTriggerSeconds)
1258
+ ) {
1259
+ triggerSeconds = options.nightTriggerSeconds;
1260
+ }
1261
+
1262
+ // Log
1263
+ this.log.debug("Trigger delay (" + triggerSeconds + " second/s)");
1264
+
1265
+ this.triggerTimeout = setTimeout(() => {
1266
+ this.triggerTimeout = null;
1267
+ this.setCurrentState(
1268
+ Characteristic.SecuritySystemCurrentState.ALARM_TRIGGERED,
1269
+ origin
1270
+ );
1271
+ }, triggerSeconds * 1000);
1272
+
1273
+ // Audio
1274
+ if (triggerSeconds > 0) {
1275
+ this.playAudio("current", "warning");
1276
+ }
1277
+
1278
+ // Commands
1279
+ this.executeCommand("current", "warning", origin);
1280
+
1281
+ // Webhooks
1282
+ this.sendWebhookEvent("current", "warning", origin);
1283
+ } else {
1284
+ // Off
1285
+ this.log.info("Security System (Cancelled)");
1286
+ this.stopAudio();
1287
+
1288
+ if (isCurrentStateAlarmTriggered) {
1289
+ if (stateChanged === false) {
1290
+ this.updateTargetState(
1291
+ Characteristic.SecuritySystemTargetState.DISARM,
1292
+ originTypes.INTERNAL,
1293
+ false,
1294
+ null
1295
+ );
1296
+ }
1297
+ } else {
1298
+ this.resetTimers();
1299
+ }
1300
+
1301
+ // Update tripped motion sensor
1302
+ if (options.trippedSensor) {
1303
+ this.trippedMotionSensorService.updateCharacteristic(
1304
+ Characteristic.MotionDetected,
1305
+ false
1306
+ );
1307
+ }
1308
+
1309
+ this.isKnocked = false;
1310
+ }
1311
+
1312
+ if (callback !== null) {
1313
+ callback(null);
1314
+ }
1315
+
1316
+ return true;
1317
+ };
1318
+
1319
+ // Server
1320
+ SecuritySystem.prototype.isAuthenticated = function (req, res) {
1321
+ // Check if authentication is disabled
1322
+ if (options.serverCode === null) {
1323
+ return null;
1324
+ }
1325
+
1326
+ let code = req.query.code;
1327
+
1328
+ // Check if code sent
1329
+ if (code === undefined) {
1330
+ this.sendCodeRequiredError(res);
1331
+ return false;
1332
+ }
1333
+
1334
+ // Check brute force
1335
+ if (this.invalidCodeCount >= 5) {
1336
+ req.blocked = true;
1337
+ this.sendCodeInvalidError(req, res);
1338
+ return false;
1339
+ }
1340
+
1341
+ const userCode = parseInt(req.query.code);
1342
+
1343
+ if (userCode !== options.serverCode) {
1344
+ this.invalidCodeCount++;
1345
+ this.sendCodeInvalidError(req, res);
1346
+ return false;
1347
+ }
1348
+
1349
+ // Reset
1350
+ this.invalidCodeCount = 0;
1351
+
1352
+ return true;
1353
+ };
1354
+
1355
+ SecuritySystem.prototype.getDelayParameter = function (req) {
1356
+ return req.query.delay === "true" ? true : false;
1357
+ };
1358
+
1359
+ SecuritySystem.prototype.sendCodeRequiredError = function (res) {
1360
+ this.log.info("Code required (Server)");
1361
+
1362
+ const response = {
1363
+ error: true,
1364
+ message: "Code required",
1365
+ hint: "Add the 'code' URL parameter with your security code",
1366
+ };
1367
+
1368
+ res.status(401).json(response);
1369
+ };
1370
+
1371
+ SecuritySystem.prototype.sendCodeInvalidError = function (req, res) {
1372
+ const response = { error: true };
1373
+
1374
+ if (req.blocked) {
1375
+ this.log.info("Code blocked (Server)");
1376
+ response.message = "Code blocked";
1377
+ } else {
1378
+ this.log.info("Code invalid (Server)");
1379
+ response.message = "Code invalid";
1380
+ }
1381
+
1382
+ res.status(403).json(response);
1383
+ };
1384
+
1385
+ SecuritySystem.prototype.sendResultResponse = function (res, success) {
1386
+ const response = {
1387
+ error: success ? false : true,
1388
+ };
1389
+
1390
+ res.json(response);
1391
+ };
1392
+
1393
+ SecuritySystem.prototype.startServer = async function () {
1394
+ const apiLimiter = rateLimit({
1395
+ windowMs: 1 * 60 * 1000,
1396
+ max: 100,
1397
+ standardHeaders: true,
1398
+ legacyHeaders: false,
1399
+ });
1400
+
1401
+ app.use(apiLimiter);
1402
+
1403
+ app.get("/", (req, res) => {
1404
+ res.redirect(
1405
+ "https://github.com/MiguelRipoll23/homebridge-securitysystem/wiki/Server"
1406
+ );
1407
+ });
1408
+
1409
+ app.get("/status", (req, res) => {
1410
+ if (this.isAuthenticated(req, res) === false) {
1411
+ return;
1412
+ }
1413
+
1414
+ const response = {
1415
+ arming: this.isArming,
1416
+ current_mode: this.state2Mode(this.currentState),
1417
+ target_mode: this.state2Mode(this.targetState),
1418
+ tripped: this.triggerTimeout !== null,
1419
+ };
1420
+
1421
+ res.json(response);
1422
+ });
1423
+
1424
+ app.get("/triggered", (req, res) => {
1425
+ if (this.isAuthenticated(req, res) === false) {
1426
+ return;
1427
+ }
1428
+
1429
+ let result = true;
1430
+
1431
+ if (this.getDelayParameter(req)) {
1432
+ // Delay
1433
+ result = this.updateTripSwitch(true, originTypes.EXTERNAL, false, null);
1434
+ } else {
1435
+ const isCurrentStateDisarmed =
1436
+ this.currentState ===
1437
+ Characteristic.SecuritySystemCurrentState.DISARMED;
1438
+
1439
+ // Not armed
1440
+ if (isCurrentStateDisarmed && options.overrideOff === false) {
1441
+ this.sendResultResponse(res, false);
1442
+ return;
1443
+ }
1444
+
1445
+ this.handleStateUpdate(true);
1446
+ this.setCurrentState(
1447
+ Characteristic.SecuritySystemCurrentState.ALARM_TRIGGERED,
1448
+ true
1449
+ );
1450
+ }
1451
+
1452
+ this.sendResultResponse(res, result);
1453
+ });
1454
+
1455
+ app.get("/home", (req, res) => {
1456
+ if (this.isAuthenticated(req, res) === false) {
1457
+ return;
1458
+ }
1459
+
1460
+ const state = Characteristic.SecuritySystemTargetState.STAY_ARM;
1461
+ const delay = this.getDelayParameter(req);
1462
+ const result = this.updateTargetState(
1463
+ state,
1464
+ originTypes.EXTERNAL,
1465
+ delay,
1466
+ null
1467
+ );
1468
+
1469
+ this.sendResultResponse(res, result);
1470
+ });
1471
+
1472
+ app.get("/away", (req, res) => {
1473
+ if (this.isAuthenticated(req, res) === false) {
1474
+ return;
1475
+ }
1476
+
1477
+ const state = Characteristic.SecuritySystemTargetState.AWAY_ARM;
1478
+ const delay = this.getDelayParameter(req);
1479
+ const result = this.updateTargetState(
1480
+ state,
1481
+ originTypes.EXTERNAL,
1482
+ delay,
1483
+ null
1484
+ );
1485
+
1486
+ this.sendResultResponse(res, result);
1487
+ });
1488
+
1489
+ app.get("/night", (req, res) => {
1490
+ if (this.isAuthenticated(req, res) === false) {
1491
+ return;
1492
+ }
1493
+
1494
+ const state = Characteristic.SecuritySystemTargetState.NIGHT_ARM;
1495
+ const delay = this.getDelayParameter(req);
1496
+ const result = this.updateTargetState(
1497
+ state,
1498
+ originTypes.EXTERNAL,
1499
+ delay,
1500
+ null
1501
+ );
1502
+
1503
+ this.sendResultResponse(res, result);
1504
+ });
1505
+
1506
+ app.get("/off", (req, res) => {
1507
+ if (this.isAuthenticated(req, res) === false) {
1508
+ return;
1509
+ }
1510
+
1511
+ const state = Characteristic.SecuritySystemTargetState.DISARM;
1512
+ const delay = this.getDelayParameter(req);
1513
+ const result = this.updateTargetState(
1514
+ state,
1515
+ originTypes.EXTERNAL,
1516
+ delay,
1517
+ null
1518
+ );
1519
+
1520
+ this.sendResultResponse(res, result);
1521
+ });
1522
+
1523
+ app.get("/arming-lock/:mode/:value", (req, res) => {
1524
+ if (this.isAuthenticated(req, res) === false) {
1525
+ return;
1526
+ }
1527
+
1528
+ const mode = req.params["mode"].toLowerCase();
1529
+ const value = req.params["value"].includes("on");
1530
+ const result = this.updateArmingLock(mode, value);
1531
+
1532
+ this.sendResultResponse(res, result);
1533
+ });
1534
+
1535
+ // Listener
1536
+ const server = app.listen(options.serverPort, (error) => {
1537
+ if (error) {
1538
+ this.log.error("Error while starting server.");
1539
+ this.log.error(error);
1540
+ return;
1541
+ }
1542
+
1543
+ this.log.info(`Server (${options.serverPort})`);
1544
+ });
1545
+
1546
+ server.on("error", (error) => {
1547
+ this.log.error("Error while starting server.");
1548
+ this.log.error(error);
1549
+ });
1550
+ };
1551
+
1552
+ // Audio
1553
+ SecuritySystem.prototype.playAudio = async function (type, state) {
1554
+ // Check option
1555
+ if (options.audio === false) {
1556
+ return;
1557
+ }
1558
+
1559
+ const mode = this.state2Mode(state);
1560
+
1561
+ // Close previous player
1562
+ this.stopAudio();
1563
+
1564
+ // Ignore 'Current Off' event
1565
+ if (mode === "off") {
1566
+ if (type === "target") {
1567
+ return;
1568
+ }
1569
+ }
1570
+
1571
+ // Check audio switch except for triggered
1572
+ const audioSwitchOnCharacteristic = this.audioSwitchService.getCharacteristic(
1573
+ Characteristic.On
1574
+ );
1575
+ const isAudioDisabledBySwitch = audioSwitchOnCharacteristic.value === false;
1576
+
1577
+ if (mode !== "triggered" && isAudioDisabledBySwitch) {
1578
+ return;
1579
+ }
1580
+
1581
+ // Directory
1582
+ let directory = `${__dirname}/../sounds`;
1583
+
1584
+ if (options.isValueSet(options.audioPath)) {
1585
+ directory = options.audioPath;
1586
+
1587
+ if (directory[directory.length] === "/") {
1588
+ directory = directory.substring(0, directory.length - 1);
1589
+ }
1590
+ }
1591
+
1592
+ // Check if file exists
1593
+ const filename = `${type}-${mode}.mp3`;
1594
+ const filePath = `${directory}/${options.audioLanguage}/${filename}`;
1595
+
1596
+ try {
1597
+ await fs.promises.access(filePath);
1598
+ } catch (error) {
1599
+ this.log.debug(`Sound file not found (${filePath})`);
1600
+ return;
1601
+ }
1602
+
1603
+ // Arguments
1604
+ let commandArguments = ["-loglevel", "error", "-nodisp", "-i", `${filePath}`];
1605
+
1606
+ if (mode === "triggered") {
1607
+ commandArguments.push("-loop");
1608
+ commandArguments.push("-1");
1609
+ } else if (
1610
+ (mode === "home" || mode === "night" || mode === "away") &&
1611
+ type === "target" &&
1612
+ options.audioArmingLooped
1613
+ ) {
1614
+ commandArguments.push("-loop");
1615
+ commandArguments.push("-1");
1616
+ } else if (mode === "warning" && options.audioAlertLooped) {
1617
+ commandArguments.push("-loop");
1618
+ commandArguments.push("-1");
1619
+ } else {
1620
+ commandArguments.push("-autoexit");
1621
+ }
1622
+
1623
+ if (options.isValueSet(options.audioVolume)) {
1624
+ commandArguments.push("-volume");
1625
+ commandArguments.push(options.audioVolume);
1626
+ }
1627
+
1628
+ // Process
1629
+ const environmentVariables = [process.env];
1630
+
1631
+ options.audioExtraVariables.forEach((variable) => {
1632
+ const key = variable.key;
1633
+ const value = variable.value;
1634
+ environmentVariables[key] = value;
1635
+ });
1636
+
1637
+ this.log.debug("Environment Variables (Audio)", environmentVariables);
1638
+
1639
+ const ffplayEnv = {
1640
+ ...process.env,
1641
+ ...environmentVariables,
1642
+ };
1643
+
1644
+ this.audioProcess = spawn("ffplay", commandArguments, { env: ffplayEnv });
1645
+ this.log.debug(`ffplay ${commandArguments.join(" ")}`);
1646
+
1647
+ this.audioProcess.on("error", (data) => {
1648
+ // Check if command is missing
1649
+ if (data !== null && data.toString().indexOf("ENOENT") > -1) {
1650
+ this.log.error("Unable to play sound, ffmpeg is not installed.");
1651
+ return;
1652
+ }
1653
+
1654
+ this.log.error(`Unable to play sound.\n${data}`);
1655
+ });
1656
+
1657
+ this.audioProcess.on("close", function () {
1658
+ this.audioProcess = null;
1659
+ });
1660
+ };
1661
+
1662
+ SecuritySystem.prototype.stopAudio = function () {
1663
+ if (this.audioProcess !== null) {
1664
+ this.audioProcess.kill();
1665
+ }
1666
+ };
1667
+
1668
+ SecuritySystem.prototype.setupAudio = async function () {
1669
+ try {
1670
+ await fs.promises.access(`${options.audioPath}/${options.audioLanguage}`);
1671
+ } catch (error) {
1672
+ await fs.promises.mkdir(`${options.audioPath}/${options.audioLanguage}`);
1673
+ await fs.promises.copyFile(
1674
+ `${__dirname}/sounds/README`,
1675
+ `${options.audioPath}/README`
1676
+ );
1677
+ await fs.promises.copyFile(
1678
+ `${__dirname}/sounds/README`,
1679
+ `${options.audioPath}/README.txt`
1680
+ );
1681
+
1682
+ this.log.warn("Check audio path directory for instructions.");
1683
+ }
1684
+ };
1685
+
1686
+ // Command
1687
+ SecuritySystem.prototype.executeCommand = function (type, state, origin) {
1688
+ // Check proxy mode
1689
+ if (options.proxyMode && origin === originTypes.EXTERNAL) {
1690
+ this.log.debug("Command bypassed as proxy mode is enabled.");
1691
+ return;
1692
+ }
1693
+
1694
+ let command = null;
1695
+
1696
+ switch (state) {
1697
+ case Characteristic.SecuritySystemCurrentState.ALARM_TRIGGERED:
1698
+ command = options.commandCurrentTriggered;
1699
+ break;
1700
+
1701
+ case Characteristic.SecuritySystemCurrentState.STAY_ARM:
1702
+ if (type === "current") {
1703
+ command = options.commandCurrentHome;
1704
+ break;
1705
+ }
1706
+
1707
+ command = options.commandTargetHome;
1708
+ break;
1709
+
1710
+ case Characteristic.SecuritySystemCurrentState.AWAY_ARM:
1711
+ if (type === "current") {
1712
+ command = options.commandCurrentAway;
1713
+ break;
1714
+ }
1715
+
1716
+ command = options.commandTargetAway;
1717
+ break;
1718
+
1719
+ case Characteristic.SecuritySystemCurrentState.NIGHT_ARM:
1720
+ if (type === "current") {
1721
+ command = options.commandCurrentNight;
1722
+ break;
1723
+ }
1724
+
1725
+ command = options.commandTargetNight;
1726
+ break;
1727
+
1728
+ case Characteristic.SecuritySystemCurrentState.DISARMED:
1729
+ if (type === "current") {
1730
+ command = options.commandCurrentOff;
1731
+ break;
1732
+ }
1733
+
1734
+ command = options.commandTargetOff;
1735
+ break;
1736
+
1737
+ case "warning":
1738
+ command = options.commandCurrentWarning;
1739
+ break;
1740
+
1741
+ default:
1742
+ this.log.error(`Unknown command ${type} state (${state})`);
1743
+ }
1744
+
1745
+ if (command === undefined || command === null) {
1746
+ this.log.debug(`Command option for ${type} mode is not set.`);
1747
+ return;
1748
+ }
1749
+
1750
+ // Parameters
1751
+ command = command.replace(
1752
+ "${currentMode}",
1753
+ this.state2Mode(this.currentState)
1754
+ );
1755
+
1756
+ const process = spawn(command, { shell: true });
1757
+
1758
+ process.stderr.on("data", (data) => {
1759
+ this.log.error(`Command failed (${command})\n${data}`);
1760
+ });
1761
+
1762
+ process.stdout.on("data", (data) => {
1763
+ this.log.info(`Command output: ${data}`);
1764
+ });
1765
+ };
1766
+
1767
+ // Webhooks
1768
+ SecuritySystem.prototype.sendWebhookEvent = function (type, state, origin) {
1769
+ // Check webhook host
1770
+ if (options.isValueSet(options.webhookUrl) === false) {
1771
+ this.log.debug("Webhook base URL option is not set.");
1772
+ return;
1773
+ }
1774
+
1775
+ // Check proxy mode
1776
+ if (options.proxyMode && origin === originTypes.EXTERNAL) {
1777
+ this.log.debug("Webhook bypassed as proxy mode is enabled.");
1778
+ return;
1779
+ }
1780
+
1781
+ let path = null;
1782
+
1783
+ switch (state) {
1784
+ case Characteristic.SecuritySystemCurrentState.ALARM_TRIGGERED:
1785
+ path = options.webhookCurrentTriggered;
1786
+ break;
1787
+
1788
+ case Characteristic.SecuritySystemCurrentState.STAY_ARM:
1789
+ if (type === "current") {
1790
+ path = options.webhookCurrentHome;
1791
+ break;
1792
+ }
1793
+
1794
+ path = options.webhookTargetHome;
1795
+ break;
1796
+
1797
+ case Characteristic.SecuritySystemCurrentState.AWAY_ARM:
1798
+ if (type === "current") {
1799
+ path = options.webhookCurrentAway;
1800
+ break;
1801
+ }
1802
+
1803
+ path = options.webhookTargetAway;
1804
+ break;
1805
+
1806
+ case Characteristic.SecuritySystemCurrentState.NIGHT_ARM:
1807
+ if (type === "current") {
1808
+ path = options.webhookCurrentNight;
1809
+ break;
1810
+ }
1811
+
1812
+ path = options.webhookTargetNight;
1813
+ break;
1814
+
1815
+ case Characteristic.SecuritySystemCurrentState.DISARMED:
1816
+ if (type === "current") {
1817
+ path = options.webhookCurrentOff;
1818
+ break;
1819
+ }
1820
+
1821
+ path = options.webhookTargetOff;
1822
+ break;
1823
+
1824
+ case "warning":
1825
+ path = options.webhookCurrentWarning;
1826
+ break;
1827
+
1828
+ default:
1829
+ this.log.error(`Unknown webhook ${type} state (${state})`);
1830
+ return;
1831
+ }
1832
+
1833
+ if (path === undefined || path === null) {
1834
+ this.log.debug(`Webhook option for ${type} mode is not set.`);
1835
+ return;
1836
+ }
1837
+
1838
+ // Parameters
1839
+ path = path.replace("${currentMode}", this.state2Mode(this.currentState));
1840
+
1841
+ // Send GET request to server
1842
+ fetch(options.webhookUrl + path)
1843
+ .then((response) => {
1844
+ if (response.ok === false) {
1845
+ throw new Error(`Status code (${response.status})`);
1846
+ }
1847
+
1848
+ this.log.info("Webhook event (Sent)");
1849
+ })
1850
+ .catch((error) => {
1851
+ this.log.error(`Request to webhook failed. (${path})`);
1852
+ this.log.error(error);
1853
+ });
1854
+ };
1855
+
1856
+ // Trip switches
1857
+ SecuritySystem.prototype.getTripSwitch = function (callback) {
1858
+ const value = this.tripSwitchService.getCharacteristic(
1859
+ Characteristic.On
1860
+ ).value;
1861
+ callback(null, value);
1862
+ };
1863
+
1864
+ SecuritySystem.prototype.setTripSwitch = function (value, callback) {
1865
+ this.log.info(`Trip Switch (${value ? "On" : "Off"})`);
1866
+ this.updateTripSwitch(value, originTypes.REGULAR_SWITCH, false, callback);
1867
+ };
1868
+
1869
+ SecuritySystem.prototype.getTripHomeSwitch = function (callback) {
1870
+ const value = this.tripHomeSwitchService.getCharacteristic(
1871
+ Characteristic.On
1872
+ ).value;
1873
+ callback(null, value);
1874
+ };
1875
+
1876
+ SecuritySystem.prototype.setTripHomeSwitch = function (value, callback) {
1877
+ this.log.info(`Trip Home Switch (${value ? "On" : "Off"})`);
1878
+ this.triggerIfModeSet(
1879
+ Characteristic.SecuritySystemCurrentState.STAY_ARM,
1880
+ value,
1881
+ callback
1882
+ );
1883
+ };
1884
+
1885
+ SecuritySystem.prototype.getTripAwaySwitch = function (callback) {
1886
+ const value = this.tripAwaySwitchService.getCharacteristic(
1887
+ Characteristic.On
1888
+ ).value;
1889
+ callback(null, value);
1890
+ };
1891
+
1892
+ SecuritySystem.prototype.setTripAwaySwitch = function (value, callback) {
1893
+ this.log.info(`Trip Away Switch (${value ? "On" : "Off"})`);
1894
+ this.triggerIfModeSet(
1895
+ Characteristic.SecuritySystemCurrentState.AWAY_ARM,
1896
+ value,
1897
+ callback
1898
+ );
1899
+ };
1900
+
1901
+ SecuritySystem.prototype.getTripNightSwitch = function (callback) {
1902
+ const value = this.tripNightSwitchService.getCharacteristic(
1903
+ Characteristic.On
1904
+ ).value;
1905
+ callback(null, value);
1906
+ };
1907
+
1908
+ SecuritySystem.prototype.setTripNightSwitch = function (value, callback) {
1909
+ this.log.info(`Trip Night Switch (${value ? "On" : "Off"})`);
1910
+ this.triggerIfModeSet(
1911
+ Characteristic.SecuritySystemCurrentState.NIGHT_ARM,
1912
+ value,
1913
+ callback
1914
+ );
1915
+ };
1916
+
1917
+ SecuritySystem.prototype.getTripOverrideSwitch = function (callback) {
1918
+ const value = this.tripOverrideSwitchService.getCharacteristic(
1919
+ Characteristic.On
1920
+ ).value;
1921
+ callback(null, value);
1922
+ };
1923
+
1924
+ SecuritySystem.prototype.setTripOverrideSwitch = function (value, callback) {
1925
+ this.log.info(`Trip Override Switch (${value ? "On" : "Off"})`);
1926
+ this.updateTripSwitch(value, originTypes.SPECIAL_SWITCH, false, callback);
1927
+ };
1928
+
1929
+ SecuritySystem.prototype.triggerIfModeSet = function (
1930
+ switchRequiredState,
1931
+ value,
1932
+ callback
1933
+ ) {
1934
+ const isCurrentStateAlarmTriggered =
1935
+ this.currentState ===
1936
+ Characteristic.SecuritySystemCurrentState.ALARM_TRIGGERED;
1937
+
1938
+ if (value) {
1939
+ if (
1940
+ this.currentState === switchRequiredState ||
1941
+ (this.targetState === switchRequiredState && isCurrentStateAlarmTriggered)
1942
+ ) {
1943
+ this.updateTripSwitch(value, originTypes.REGULAR_SWITCH, false, callback);
1944
+ } else {
1945
+ this.log.warn("Security System (Trip mode not set)");
1946
+ callback(HK_NOT_ALLOWED_IN_CURRENT_STATE, false);
1947
+ }
1948
+ } else {
1949
+ this.updateTripSwitch(value, originTypes.REGULAR_SWITCH, false, callback);
1950
+ }
1951
+ };
1952
+
1953
+ SecuritySystem.prototype.resetTripSwitches = function () {
1954
+ const tripHomeOnCharacteristic = this.tripHomeSwitchService.getCharacteristic(
1955
+ Characteristic.On
1956
+ );
1957
+ const tripAwayOnCharacteristic = this.tripAwaySwitchService.getCharacteristic(
1958
+ Characteristic.On
1959
+ );
1960
+ const tripNightOnCharacteristic =
1961
+ this.tripNightSwitchService.getCharacteristic(Characteristic.On);
1962
+
1963
+ const tripOverrideOnCharacteristic =
1964
+ this.tripOverrideSwitchService.getCharacteristic(Characteristic.On);
1965
+
1966
+ if (tripHomeOnCharacteristic.value) {
1967
+ tripHomeOnCharacteristic.updateValue(false);
1968
+ this.log.debug("Trip Home Switch (Off)");
1969
+ }
1970
+
1971
+ if (tripAwayOnCharacteristic.value) {
1972
+ tripAwayOnCharacteristic.updateValue(false);
1973
+ this.log.debug("Trip Away Switch (Off)");
1974
+ }
1975
+
1976
+ if (tripNightOnCharacteristic.value) {
1977
+ tripNightOnCharacteristic.updateValue(false);
1978
+ this.log.debug("Trip Night Switch (Off)");
1979
+ }
1980
+
1981
+ if (tripOverrideOnCharacteristic.value) {
1982
+ tripOverrideOnCharacteristic.updateValue(false);
1983
+ this.log.debug("Trip Override Switch (Off)");
1984
+ }
1985
+ };
1986
+
1987
+ // Arming lock switches
1988
+ SecuritySystem.prototype.getArmingLockSwitch = function (callback) {
1989
+ const value = this.armingLockSwitchService.getCharacteristic(
1990
+ Characteristic.On
1991
+ ).value;
1992
+ callback(null, value);
1993
+ };
1994
+
1995
+ SecuritySystem.prototype.getArmingLockHomeSwitch = function (callback) {
1996
+ const value = this.armingLockHomeSwitchService.getCharacteristic(
1997
+ Characteristic.On
1998
+ ).value;
1999
+ callback(null, value);
2000
+ };
2001
+
2002
+ SecuritySystem.prototype.getArmingLockAwaySwitch = function (callback) {
2003
+ const value = this.armingLockAwaySwitchService.getCharacteristic(
2004
+ Characteristic.On
2005
+ ).value;
2006
+ callback(null, value);
2007
+ };
2008
+
2009
+ SecuritySystem.prototype.getArmingLockNightSwitch = function (callback) {
2010
+ const value = this.armingLockNightSwitchService.getCharacteristic(
2011
+ Characteristic.On
2012
+ ).value;
2013
+ callback(null, value);
2014
+ };
2015
+
2016
+ SecuritySystem.prototype.logArmingLock = function (mode, value) {
2017
+ const modeCapitalized = mode.charAt(0).toUpperCase() + mode.slice(1);
2018
+ this.log.info(`Arming lock [${modeCapitalized}] (${value ? "On" : "Off"})`);
2019
+ };
2020
+
2021
+ SecuritySystem.prototype.isArmingLocked = function (state) {
2022
+ let armingLockSwitchService = this.armingLockSwitchService;
2023
+
2024
+ // Check global switch
2025
+ if (armingLockSwitchService.getCharacteristic(Characteristic.On).value) {
2026
+ return true;
2027
+ }
2028
+
2029
+ // Check mode switches
2030
+ switch (state) {
2031
+ case Characteristic.SecuritySystemCurrentState.STAY_ARM:
2032
+ armingLockSwitchService = this.armingLockHomeSwitchService;
2033
+ break;
2034
+
2035
+ case Characteristic.SecuritySystemCurrentState.AWAY_ARM:
2036
+ armingLockSwitchService = this.armingLockAwaySwitchService;
2037
+ break;
2038
+
2039
+ case Characteristic.SecuritySystemCurrentState.NIGHT_ARM:
2040
+ armingLockSwitchService = this.armingLockNightSwitchService;
2041
+ break;
2042
+
2043
+ default:
2044
+ this.log.debug(`Unknown arming lock state (${state})`);
2045
+ }
2046
+
2047
+ return armingLockSwitchService.getCharacteristic(Characteristic.On).value;
2048
+ };
2049
+
2050
+ SecuritySystem.prototype.updateArmingLock = function (mode, value) {
2051
+ this.logArmingLock(mode, value);
2052
+
2053
+ switch (mode) {
2054
+ case "global":
2055
+ this.armingLockSwitchService
2056
+ .getCharacteristic(Characteristic.On)
2057
+ .updateValue(value);
2058
+ break;
2059
+
2060
+ case "home":
2061
+ this.armingLockHomeSwitchService
2062
+ .getCharacteristic(Characteristic.On)
2063
+ .updateValue(value);
2064
+ break;
2065
+
2066
+ case "away":
2067
+ this.armingLockAwaySwitchService
2068
+ .getCharacteristic(Characteristic.On)
2069
+ .updateValue(value);
2070
+ break;
2071
+
2072
+ case "night":
2073
+ this.armingLockNightSwitchService
2074
+ .getCharacteristic(Characteristic.On)
2075
+ .updateValue(value);
2076
+ break;
2077
+
2078
+ default:
2079
+ this.log.debug(`Unknown arming lock mode (${mode})`);
2080
+ return false;
2081
+ }
2082
+
2083
+ return true;
2084
+ };
2085
+
2086
+ SecuritySystem.prototype.setArmingLockSwitch = function (value, callback) {
2087
+ this.logArmingLock("global", value);
2088
+ callback(null);
2089
+ };
2090
+
2091
+ SecuritySystem.prototype.setArmingLockHomeSwitch = function (value, callback) {
2092
+ this.logArmingLock("home", value);
2093
+ callback(null);
2094
+ };
2095
+
2096
+ SecuritySystem.prototype.setArmingLockAwaySwitch = function (value, callback) {
2097
+ this.logArmingLock("away", value);
2098
+ callback(null);
2099
+ };
2100
+
2101
+ SecuritySystem.prototype.setArmingLockNightSwitch = function (value, callback) {
2102
+ this.logArmingLock("night", value);
2103
+ callback(null);
2104
+ };
2105
+
2106
+ // Mode Switches
2107
+ SecuritySystem.prototype.resetModeSwitches = function () {
2108
+ const modeHomeSwitchCharacteristicOn =
2109
+ this.modeHomeSwitchService.getCharacteristic(Characteristic.On);
2110
+ const modeAwaySwitchCharacteristicOn =
2111
+ this.modeAwaySwitchService.getCharacteristic(Characteristic.On);
2112
+ const modeNightSwitchCharacteristicOn =
2113
+ this.modeNightSwitchService.getCharacteristic(Characteristic.On);
2114
+ const modeOffSwitchCharacteristicOn =
2115
+ this.modeOffSwitchService.getCharacteristic(Characteristic.On);
2116
+ const modeAwayExtendedSwitchCharacteristicOn =
2117
+ this.modeAwayExtendedSwitchService.getCharacteristic(Characteristic.On);
2118
+ const modePauseSwitchCharacteristicOn =
2119
+ this.modePauseSwitchService.getCharacteristic(Characteristic.On);
2120
+
2121
+ if (modeHomeSwitchCharacteristicOn.value) {
2122
+ modeHomeSwitchCharacteristicOn.updateValue(false);
2123
+ this.log.debug("Mode Home Switch (Off)");
2124
+ }
2125
+
2126
+ if (modeAwaySwitchCharacteristicOn.value) {
2127
+ modeAwaySwitchCharacteristicOn.updateValue(false);
2128
+ this.log.debug("Mode Away Switch (Off)");
2129
+ }
2130
+
2131
+ if (modeNightSwitchCharacteristicOn.value) {
2132
+ modeNightSwitchCharacteristicOn.updateValue(false);
2133
+ this.log.debug("Mode Night Switch (Off)");
2134
+ }
2135
+
2136
+ if (modeOffSwitchCharacteristicOn.value) {
2137
+ modeOffSwitchCharacteristicOn.updateValue(false);
2138
+ this.log.debug("Mode Off Switch (Off)");
2139
+ }
2140
+
2141
+ if (modeAwayExtendedSwitchCharacteristicOn.value) {
2142
+ modeAwayExtendedSwitchCharacteristicOn.updateValue(false);
2143
+ this.log.debug("Mode Away Extended Switch (Off)");
2144
+ }
2145
+
2146
+ if (modePauseSwitchCharacteristicOn.value) {
2147
+ modePauseSwitchCharacteristicOn.updateValue(false);
2148
+ this.log.debug("Mode Pause Switch (Off)");
2149
+ }
2150
+ };
2151
+
2152
+ SecuritySystem.prototype.updateModeSwitches = function () {
2153
+ switch (this.targetState) {
2154
+ case Characteristic.SecuritySystemTargetState.STAY_ARM:
2155
+ this.modeHomeSwitchService.updateCharacteristic(Characteristic.On, true);
2156
+ this.log.debug("Mode Home Switch (On)");
2157
+ break;
2158
+
2159
+ case Characteristic.SecuritySystemTargetState.AWAY_ARM:
2160
+ this.modeAwaySwitchService.updateCharacteristic(Characteristic.On, true);
2161
+ this.log.debug("Mode Away Switch (On)");
2162
+ break;
2163
+
2164
+ case Characteristic.SecuritySystemTargetState.NIGHT_ARM:
2165
+ this.modeNightSwitchService.updateCharacteristic(Characteristic.On, true);
2166
+ this.log.debug("Mode Night Switch (On)");
2167
+ break;
2168
+
2169
+ case Characteristic.SecuritySystemTargetState.DISARM:
2170
+ this.modeOffSwitchService.updateCharacteristic(Characteristic.On, true);
2171
+ this.log.debug("Mode Off Switch (On)");
2172
+ break;
2173
+ }
2174
+ };
2175
+
2176
+ SecuritySystem.prototype.getModeHomeSwitch = function (callback) {
2177
+ const value = this.modeHomeSwitchService.getCharacteristic(
2178
+ Characteristic.On
2179
+ ).value;
2180
+ callback(null, value);
2181
+ };
2182
+
2183
+ SecuritySystem.prototype.setModeHomeSwitch = function (value, callback) {
2184
+ if (value === false) {
2185
+ callback(HK_NOT_ALLOWED_IN_CURRENT_STATE, false);
2186
+ return;
2187
+ }
2188
+
2189
+ this.updateTargetState(
2190
+ Characteristic.SecuritySystemTargetState.STAY_ARM,
2191
+ originTypes.INTERNAL,
2192
+ true,
2193
+ null
2194
+ );
2195
+ callback(null);
2196
+ };
2197
+
2198
+ SecuritySystem.prototype.getModeAwaySwitch = function (callback) {
2199
+ const value = this.modeAwaySwitchService.getCharacteristic(
2200
+ Characteristic.On
2201
+ ).value;
2202
+ callback(null, value);
2203
+ };
2204
+
2205
+ SecuritySystem.prototype.setModeAwaySwitch = function (value, callback) {
2206
+ if (value === false) {
2207
+ callback(HK_NOT_ALLOWED_IN_CURRENT_STATE, false);
2208
+ return;
2209
+ }
2210
+
2211
+ this.updateTargetState(
2212
+ Characteristic.SecuritySystemTargetState.AWAY_ARM,
2213
+ originTypes.INTERNAL,
2214
+ true,
2215
+ null
2216
+ );
2217
+ callback(null);
2218
+ };
2219
+
2220
+ SecuritySystem.prototype.getModeNightSwitch = function (callback) {
2221
+ const value = this.modeNightSwitchService.getCharacteristic(
2222
+ Characteristic.On
2223
+ ).value;
2224
+ callback(null, value);
2225
+ };
2226
+
2227
+ SecuritySystem.prototype.setModeNightSwitch = function (value, callback) {
2228
+ if (value === false) {
2229
+ callback(HK_NOT_ALLOWED_IN_CURRENT_STATE, false);
2230
+ return;
2231
+ }
2232
+
2233
+ this.updateTargetState(
2234
+ Characteristic.SecuritySystemTargetState.NIGHT_ARM,
2235
+ originTypes.INTERNAL,
2236
+ true,
2237
+ null
2238
+ );
2239
+ callback(null);
2240
+ };
2241
+
2242
+ SecuritySystem.prototype.getModeOffSwitch = function (callback) {
2243
+ const value = this.modeOffSwitchService.getCharacteristic(
2244
+ Characteristic.On
2245
+ ).value;
2246
+ callback(null, value);
2247
+ };
2248
+
2249
+ SecuritySystem.prototype.setModeOffSwitch = function (value, callback) {
2250
+ if (value === false) {
2251
+ callback(HK_NOT_ALLOWED_IN_CURRENT_STATE, false);
2252
+ return;
2253
+ }
2254
+
2255
+ this.updateTargetState(
2256
+ Characteristic.SecuritySystemTargetState.DISARM,
2257
+ originTypes.INTERNAL,
2258
+ true,
2259
+ null
2260
+ );
2261
+ callback(null);
2262
+ };
2263
+
2264
+ SecuritySystem.prototype.getModeAwayExtendedSwitch = function (callback) {
2265
+ const value = this.modeAwayExtendedSwitchService.getCharacteristic(
2266
+ Characteristic.On
2267
+ ).value;
2268
+ callback(null, value);
2269
+ };
2270
+
2271
+ SecuritySystem.prototype.setModeAwayExtendedSwitch = function (
2272
+ value,
2273
+ callback
2274
+ ) {
2275
+ if (value === false) {
2276
+ callback(HK_NOT_ALLOWED_IN_CURRENT_STATE, false);
2277
+ return;
2278
+ }
2279
+
2280
+ this.updateTargetState(
2281
+ Characteristic.SecuritySystemTargetState.AWAY_ARM,
2282
+ originTypes.INTERNAL,
2283
+ true,
2284
+ null
2285
+ );
2286
+ callback(null);
2287
+ };
2288
+
2289
+ SecuritySystem.prototype.getModePauseSwitch = function (callback) {
2290
+ const value = this.modePauseSwitchService.getCharacteristic(
2291
+ Characteristic.On
2292
+ ).value;
2293
+ callback(null, value);
2294
+ };
2295
+
2296
+ SecuritySystem.prototype.setModePauseSwitch = function (value, callback) {
2297
+ if (
2298
+ this.currentState ===
2299
+ Characteristic.SecuritySystemCurrentState.ALARM_TRIGGERED
2300
+ ) {
2301
+ this.log.warn("Mode pause (Alarm is triggered)");
2302
+ callback(HK_NOT_ALLOWED_IN_CURRENT_STATE, false);
2303
+ return;
2304
+ }
2305
+
2306
+ if (value) {
2307
+ if (
2308
+ this.currentState === Characteristic.SecuritySystemCurrentState.DISARMED
2309
+ ) {
2310
+ this.log.warn("Mode pause (Not armed)");
2311
+ callback(HK_NOT_ALLOWED_IN_CURRENT_STATE, false);
2312
+ return;
2313
+ }
2314
+
2315
+ this.log.info("Mode pause (Started)");
2316
+
2317
+ this.pausedCurrentState = this.currentState;
2318
+ this.updateTargetState(
2319
+ Characteristic.SecuritySystemTargetState.DISARM,
2320
+ originTypes.INTERNAL,
2321
+ true,
2322
+ null
2323
+ );
2324
+
2325
+ // Check if time is set to unlimited
2326
+ if (options.pauseMinutes !== 0) {
2327
+ this.pauseTimeout = setTimeout(() => {
2328
+ this.log.info("Mode pause (Finished)");
2329
+ this.updateTargetState(
2330
+ this.pausedCurrentState,
2331
+ originTypes.INTERNAL,
2332
+ true,
2333
+ null
2334
+ );
2335
+ }, options.pauseMinutes * 60 * 1000);
2336
+ }
2337
+ } else {
2338
+ this.log.info("Mode pause (Cancelled)");
2339
+
2340
+ if (this.pauseTimeout !== null) {
2341
+ clearTimeout(this.pauseTimeout);
2342
+ this.pauseTimeout = null;
2343
+ }
2344
+
2345
+ this.updateTargetState(
2346
+ this.pausedCurrentState,
2347
+ originTypes.INTERNAL,
2348
+ true,
2349
+ null
2350
+ );
2351
+ }
2352
+
2353
+ callback(null);
2354
+ };
2355
+
2356
+ SecuritySystem.prototype.getAudioSwitch = function (callback) {
2357
+ const value = this.audioSwitchService.getCharacteristic(
2358
+ Characteristic.On
2359
+ ).value;
2360
+ callback(null, value);
2361
+ };
2362
+
2363
+ SecuritySystem.prototype.setAudioSwitch = function (value, callback) {
2364
+ this.log.info(`Audio (${value ? "Enabled" : "Disabled"})`);
2365
+ callback(null);
2366
+ };
2367
+
2368
+ // Tripped Motion Sensor
2369
+ SecuritySystem.prototype.getTrippedMotionDetected = function (callback) {
2370
+ const value = this.trippedMotionSensorService.getCharacteristic(
2371
+ Characteristic.MotionDetected
2372
+ ).value;
2373
+ callback(null, value);
2374
+ };
2375
+
2376
+ SecuritySystem.prototype.updateTrippedMotionDetected = function () {
2377
+ this.trippedMotionSensorService.updateCharacteristic(
2378
+ Characteristic.MotionDetected,
2379
+ true
2380
+ );
2381
+
2382
+ setTimeout(() => {
2383
+ this.trippedMotionSensorService.updateCharacteristic(
2384
+ Characteristic.MotionDetected,
2385
+ false
2386
+ );
2387
+ }, 750);
2388
+ };
2389
+
2390
+ // Triggered Motion Sensor
2391
+ SecuritySystem.prototype.getTriggeredMotionDetected = function (callback) {
2392
+ const value = this.triggeredMotionSensorService.getCharacteristic(
2393
+ Characteristic.MotionDetected
2394
+ ).value;
2395
+ callback(null, value);
2396
+ };
2397
+
2398
+ SecuritySystem.prototype.updateTriggeredMotionDetected = function () {
2399
+ this.triggeredMotionSensorService.updateCharacteristic(
2400
+ Characteristic.MotionDetected,
2401
+ true
2402
+ );
2403
+
2404
+ setTimeout(() => {
2405
+ this.triggeredMotionSensorService.updateCharacteristic(
2406
+ Characteristic.MotionDetected,
2407
+ false
2408
+ );
2409
+ }, 750);
2410
+ };
2411
+
2412
+ // Triggered Reset Motion Sensor
2413
+ SecuritySystem.prototype.getTriggeredResetMotionDetected = function (callback) {
2414
+ const value = this.triggeredResetMotionSensorService.getCharacteristic(
2415
+ Characteristic.MotionDetected
2416
+ ).value;
2417
+ callback(null, value);
2418
+ };