homebridge-securitysystem 7.3.0 → 7.4.0

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