homebridge-securitysystem 7.4.0 → 7.5.0-beta.2

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