From 22077693a26899c3779d0e80d7af89abb155f7dd Mon Sep 17 00:00:00 2001 From: SteidlD <62817699+SteidlD@users.noreply.github.com> Date: Tue, 2 Jun 2020 20:49:29 +0200 Subject: [PATCH] Add files via upload --- CHANGELOG.md | 12 ++ HomeSecurityLitePlatform.js | 259 ++++++++++++++++++++++++++++ README.md | 51 ++++++ SecuritySystemAccessory.js | 327 ++++++++++++++++++++++++++++++++++++ WindowAndDoorAccessory.js | 253 ++++++++++++++++++++++++++++ config.schema.json | 91 ++++++++++ index.js | 67 ++++++++ package.json | 31 ++++ 8 files changed, 1091 insertions(+) create mode 100644 CHANGELOG.md create mode 100644 HomeSecurityLitePlatform.js create mode 100644 README.md create mode 100644 SecuritySystemAccessory.js create mode 100644 WindowAndDoorAccessory.js create mode 100644 config.schema.json create mode 100644 index.js create mode 100644 package.json diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..9a6b88b --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,12 @@ +# Changelog for homebridge-home-security-lite +This is the change log for the plugin, all relevant changes will be listed here. + +For documentation please see the [README](https://github.com/SteidlD/homebridge-home-security-lite/blob/master/README.md) + +## 0.3.0 +- Bugfix: Open window warning wasn't set back in certain cases +- JSDoc documentation added to code +- go public + +## 0.2.0 +- first private check-in diff --git a/HomeSecurityLitePlatform.js b/HomeSecurityLitePlatform.js new file mode 100644 index 0000000..8ce9731 --- /dev/null +++ b/HomeSecurityLitePlatform.js @@ -0,0 +1,259 @@ +// Implements the HomeSecurityLitePlatform class that is for keeping all the things together +// +//----------------------------------------------------------------------- +// Date Author Change +//----------------------------------------------------------------------- +// 15.05.2020 D.Steidl Created +// 22.05.2020 D.Steidl JSDoc added +//----------------------------------------------------------------------- + +//----------------------------------------------------------------------- +// Global variables +//----------------------------------------------------------------------- + +// variables have to be declared explicitly +'use strict' + +//----------------------------------------------------------------------- +// Imports +//----------------------------------------------------------------------- + +// from JavaScript + +// from HomeSecurityLite + +const cWindowAndDoorAccessory = require('./WindowAndDoorAccessory') +const cSecuritySystemAccessory = require('./SecuritySystemAccessory') + +//----------------------------------------------------------------------- +// Classes +//----------------------------------------------------------------------- + +/** + * Class for the Platform of the home security lite system + */ +class cHomeSecurityLitePlatform +{ + //----------------------------------------------------------------------- + /** + * Platform constructor + * + * @param {Object} cLog Pointer to logging class + * @param {Object} dConfig Configuration for the plugin + * @param {Object} cApi Pointer to the API for registering new accessory + * @returns {void} nothing + */ + constructor(cLog, dConfig, cApi) + { + var self = this; + cLog("HomeSecurityLite Init"); + + // Store and initialize values + self.cLog = cLog; + self.dConfig = dConfig; + self.cAccessories = []; // Store all accessories to find it again + this.bFastRemind = false; // State of the fast remind feature + + // Check if API pointer is valid + if (cApi) + { // API valid + // Save the API object as plugin needs to register new accessory via self object + self.cApi = cApi; + + // Listen to event "didFinishLaunching", this means homebridge already finished loading cached accessories. + // Platform Plugin should only register new accessory that doesn't exist in homebridge after this event. + // Or start discover new accessories. + self.cApi.on + ('didFinishLaunching', + function () + { + self.cLog("DidFinishLaunching"); + + // Analyse config, use config first, if not set then fall back to default values + var lWindowsNDoors = dConfig["windows_and_doors"]; // Configuration of the window and door sensors + var strHomeSecurityLiteName = dConfig["home_security_lite_name"] || "Home Security Lite"; // Name of the Home Security Lite accessory + var strSecuritySystemName = dConfig["security_system_name"] || "Security system"; // Name of the Security System accessory + var strFastCyclicReminderName = dConfig["fast_cyclic_reminder_name"] || "Remind me again fast"; // Name of the remind me fast switch + var strForgotWindowWarningName = dConfig["forgot_window_warning_name"] || "You forgot to close a window"; // Name of the opened window reminder + + // ------------------------------------------------------------------------ + // Initialize Home security lite accessory + // ------------------------------------------------------------------------ + + // Create a unique id for the home security lite accessory and try to get it from list of known accessories + var HomeSecurityLiteUUID = global.cUUIDGen.generate("name" + strHomeSecurityLiteName); // UUID of the Home Security Lite accessory + var cActAccessory = self.cAccessories[HomeSecurityLiteUUID]; // Pointer to the accessory if it was restored by homebridge + + if (cActAccessory === undefined) + { // If the accessory is not known to homebridge + self.cLog("Found new Home Security Lite accessory : %s", strHomeSecurityLiteName); + // Instantiate Accessory object + cActAccessory = new global.cAccessory(strHomeSecurityLiteName, HomeSecurityLiteUUID); + // Add a service for a switch input + cActAccessory.addService(global.cService.Switch, strFastCyclicReminderName); + // Hang accessory into list to find it again later + self.cAccessories[cActAccessory.UUID] = cActAccessory; + // Now register this accessory with homebridge + self.cApi.registerPlatformAccessories("homebridge-home-security-lite", "HomeSecurityLitePlatform", [cActAccessory]); + self.cLog("Added new Home Security Lite accessory switch: %s", strFastCyclicReminderName); + } + else + { // If the accessory is already known to homebridge + self.cLog("Home Security Lite accessory is online: %s", cActAccessory.displayName); + } + + // Set Information Accessory + var cInfo = cActAccessory.getService(global.cService.AccessoryInformation); // Temporary variable with the AccessoryInformation service + cInfo.setCharacteristic(global.cCharacteristic.Manufacturer, "D. Steidl"); + cInfo.setCharacteristic(global.cCharacteristic.Model, "HomeSecurityLite"); + cInfo.setCharacteristic(global.cCharacteristic.SerialNumber, "1"); + cInfo.setCharacteristic(global.cCharacteristic.FirmwareRevision, global.strFWVersion); + + // Initialize SwitchService (set value and connect setter and getter method) + cActAccessory.getService(global.cService.Switch) + .getCharacteristic(global.Characteristic.On) + .setValue(this.bFastRemind) + .on('get', function (fCallback) { fCallback(null, this.bFastRemind); }.bind(this)) + .on('set', this.setFastRemind.bind(this)); + + // ------------------------------------------------------------------------ + // Initialize System security accessory + // ------------------------------------------------------------------------ + + // Create a unique id for the security system accessory and try to get it from list of known accessories + var SecuritySystemUUID = global.cUUIDGen.generate("name" + strSecuritySystemName); // UUID of the Security System accessory + var cActAccessory = self.cAccessories[SecuritySystemUUID]; // Pointer to the accessory if it was restored by homebridge + + if (cActAccessory === undefined) + { // If the accessory is not known to homebridge + self.cLog("Found new security system : %s", strSecuritySystemName); + // Instantiate Accessory object + var cActAccessory = new global.cAccessory(strSecuritySystemName, SecuritySystemUUID); + // Add a security system + cActAccessory.addService(global.cService.SecuritySystem, strSecuritySystemName); + // Add a service for a contact sensor output (forgot_window_warning) + cActAccessory.addService(global.cService.ContactSensor, strForgotWindowWarningName); + // Hang accessory into list to find it again later + self.cAccessories[cActAccessory.UUID] = new cSecuritySystemAccessory(self.cLog, cActAccessory, dConfig); + // Now register this accessory with homebridge + self.cApi.registerPlatformAccessories("homebridge-home-security-lite", "HomeSecurityLitePlatform", [cActAccessory]); + self.cLog("Added new security system : %s", strSecuritySystemName); + } + else + { // If the accessory is already known to homebridge + self.cLog("Security system is online: %s", cActAccessory.displayName); + self.cAccessories[cActAccessory.UUID] = new cSecuritySystemAccessory(self.cLog, (cActAccessory instanceof cSecuritySystemAccessory ? cActAccessory.accessory : cActAccessory), dConfig); + } + + // ------------------------------------------------------------------------ + // Initialize window and door accessory + // ------------------------------------------------------------------------ + + if (lWindowsNDoors) + { // If there's a config for Window and door sensors + var iSerial = 0; // Fake serial number (staring by 1) + lWindowsNDoors.forEach + ( // Parse through all configured window and door sensors + function (dWinDoorConfig) + { // Function to initialize one window and door sensor accessory + if (dWinDoorConfig.name) + { // If there's a name in the config + // Create a unique id for the accessory and try to get it from list of known accessories + var UUID = global.cUUIDGen.generate("name" + dWinDoorConfig.name); // UUID of the window and door accessory + var cActAccessory = self.cAccessories[UUID]; // Pointer to the accessory if it was restored by homebridge + + // Calculate serial number + iSerial = iSerial + 1; + + if (cActAccessory === undefined) + { // If the accessory is not known to homebridge + self.cLog("Found new window or door : %s - %s", dWinDoorConfig.name, dWinDoorConfig.description); + + // Read the configuration, if not configured then fall back on default values + var strSwitchName = (dConfig["prefix_remind_me"] || "Remind me of" ) + " " + dWinDoorConfig.name; // Name of the switch + var strSensorName = (dConfig["prefix_reminder"] || "Reminder:" ) + " " + dWinDoorConfig.name; // Name of the sensor + var strAccessoryName = (dConfig["prefix_reminder_accessory"] || "Reminder accessory" ) + " " + dWinDoorConfig.name; // Name of the accessory + + // Instantiate Accessory object + var cActAccessory = new global.cAccessory(strAccessoryName, UUID); + // Add a service for a switch input + cActAccessory.addService(global.cService.Switch, strSwitchName); + // Add a service for a contact sensor output (delayed contact switch) + cActAccessory.addService(global.cService.ContactSensor, strSensorName); + // Hang accessory into list to find it again later + self.cAccessories[cActAccessory.UUID] = new cWindowAndDoorAccessory(self.cLog, cActAccessory, self.cAccessories[SecuritySystemUUID], dWinDoorConfig, dConfig, iSerial); + // Now register this accessory with homebridge + self.cApi.registerPlatformAccessories("homebridge-home-security-lite", "HomeSecurityLitePlatform", [cActAccessory]); + self.cLog("Added new window or door switch and contact sensor : %s - %s", strSwitchName, strSensorName); + } + else + { // If the accessory is already known to homebridge + self.cLog("Window or door is online: %s", cActAccessory.displayName); + self.cAccessories[cActAccessory.UUID] = new cWindowAndDoorAccessory(self.cLog, (cActAccessory instanceof cWindowAndDoorAccessory ? cActAccessory.accessory : cActAccessory), self.cAccessories[SecuritySystemUUID], dWinDoorConfig, dConfig, iSerial); + } + } + else { + // If there's no name in config then log an error + self.cLog("Window or door %s in configuration has no name", dWinDoorConfig); + } + } + ); + } + else { // If there's no windows or doors in config then log an error + self.cLog("No windows or doors found in configuration", dConfig); + } + }.bind(self) // function() + ); // self.cApi.on('didFinishLaunching', + } // if (cApi) + } + + /** + * Function invoked when homebridge tries to restore cached accessory. + * The accessory will be stored in a list identified by it's UUID to be processed later + * + * @param {Object} cActAccessory Accessory object restored from cache + * @returns {void} nothing + */ + configureAccessory(cActAccessory) + { + this.cLog(cActAccessory.displayName, "Configure Accessory"); + this.cAccessories[cActAccessory.UUID] = cActAccessory; + } + + /** + * "Set" function for fast remind. Value will be stored and distributed to all the window/door objects + * + * @param {boolean} bValue Value of the fast remind feature + * @param {function} fCallback Callback function to confirm setting of the value + * @returns {void} nothing + */ + setFastRemind(bValue, fCallback) + { + var self = this; + // Has value changed? + var bChanged = (self.bFastRemind != bValue); + if (bChanged) + { // If the value has changed + // Store value + self.bFastRemind = bValue; + self.cLog("%s - setting fast remind: %s", this.cActAccessory.displayName, (bValue ? "true" : "false")); + self.cAccessories.forEach + ( // Go through all the window and door accessories + function (cAccessory) + { + if (cAccessory instanceof cWindowAndDoorAccessory) + { // If the accessory has the right type, then give it the value + cAccessory.setFastRemind(bValue); + } + } + ); + } + fCallback(null, self.bFastRemind); + } +} + +//----------------------------------------------------------------------- +// Exports +//----------------------------------------------------------------------- + +module.exports = cHomeSecurityLitePlatform; diff --git a/README.md b/README.md new file mode 100644 index 0000000..0c6a45d --- /dev/null +++ b/README.md @@ -0,0 +1,51 @@ +# homebridge-home-security-lite +A homebridge-plugin for using your available homekit sensors and actors to build a security system. + +### Features: +- include door and window sensors +- get reminded of opened windows after a configurable time +- get reminded of opened windows when leaving your home +- get an alarm if a window or door is opened when security system is armed + +## Installation: + +### 1. Install homebridge and home-security-lite plugin. +- 1.a `sudo npm install -g homebridge --unsafe-perm` +- 1.b `sudo npm install -g homebridge-home-security-lite` + +### 2. Update homebridge configuration file. +``` +"platforms": [ + { + "platform" : "HomeSecurityLite", + "name" : "HomeSecurityLite", + "plugin_map" : + { + "plugin_name" : "homebridge-home-security-lite" + }, + "prefix_remind_me" : "Remind me of", + "prefix_reminder" : "Reminder:", + "prefix_reminder_accessory" : "Remind timer", + "forgot_window_warning_name" : "Forgot open window or door", + "home_security_lite_name" : "Home Security Lite", + "security_system_name" : "Security system", + "fast_cyclic_reminder_name" : "Remind me fast again", + "windows_and_doors" : + [ + { + "name" : "bathroom window", + "description" : "input/output for the window contact sensor in the bathroom" + }, + { + "name" : "patio door", + "first_remind" : 1200, + "fast_cyclic_remind" : 180, + "slow_cyclic_remind" : 43200 + } + ], + "first_remind" : 900, + "fast_cyclic_remind" : 120, + "slow_cyclic_remind" : 1800 + } +] +``` diff --git a/SecuritySystemAccessory.js b/SecuritySystemAccessory.js new file mode 100644 index 0000000..bcecb62 --- /dev/null +++ b/SecuritySystemAccessory.js @@ -0,0 +1,327 @@ +// Implements the SecuritySystemAccessory class that is executing the security system +// +//----------------------------------------------------------------------- +// Date Author Change +//----------------------------------------------------------------------- +// 15.05.2020 D.Steidl Created +// 31.05.2020 D.Steidl JSDoc added +// 02.06.2020 D.Steidl Bugfix: Open window warning wasn't set back in certain cases +//----------------------------------------------------------------------- + +//----------------------------------------------------------------------- +// Global variables +//----------------------------------------------------------------------- + +// variables have to be declared explicitly +'use strict' + +/** @const {Object} LSTATES Strings for the logging of the actual security system state */ +const LSTATES = ["STAY_ARMED", "AWAY_ARMED", "NIGHT_ARMED", "DISARMED", "ALARM_TRIGGERED"]; +/** @const {Object} LTARGETSTATES Strings for the logging of the target security system state */ +const LTARGETSTATES = ["STAY_ARM", "AWAY_ARM", "NIGHT_ARM", "DISARM"]; + +//----------------------------------------------------------------------- +// Imports +//----------------------------------------------------------------------- + +// from JavaScript + +// from HomeSecurityLite + +//----------------------------------------------------------------------- +// Classes +//----------------------------------------------------------------------- + +/** + * This class provides a security system based on doors and windows. If you arm it (i.e. automatically when + * the last person leave your home), then it checks whether all of the doors and windows are closed. If not + * it will sent you a message to remind you, that you left a window or a door open. Otherwise - once armed - + * it will react on any door or window opening and will trigger the alarm. + */ +class cSecuritySystemAccessory +{ + /** + * Constructor of the class + * + * @param {Object} cLog Pointer to logging class + * @param {Object} cActAccessory Pointer to own accessory + * @param {Object} dConfig Configuration for the plugin + * @returns {void} Nothing + */ + constructor(cLog, cActAccessory, dConfig) + { + // Store values + this.cActAccessory = cActAccessory; // Pointer to logging class + this.cLog = cLog; // Pointer to own accessory + this.Config = dConfig; // Configuration for the plugin + + // Initialize + this.eTargetState = cCharacteristic.SecuritySystemTargetState.DISARM; // Target state of the security system (disarmed) + this.eCurrentState = cCharacteristic.SecuritySystemCurrentState.DISARMED; // Actual state of the security system (disarmed) + this.bCurrentSensorState = false; // Actual state of the reminder (off) + this.iTimeoutId = undefined; // Id of a set timeout to find it again (no timeout started) + this.lWindowStates = []; // Array with the states of the windows/doors + + // Set Information Accessory + var cInfo = this.cActAccessory.getService(global.cService.AccessoryInformation); // Temporary variable with the AccessoryInformation service + cInfo.setCharacteristic(global.cCharacteristic.Manufacturer, "D. Steidl"); + cInfo.setCharacteristic(global.cCharacteristic.Model, "SecuritySystem"); + cInfo.setCharacteristic(global.cCharacteristic.SerialNumber, "1"); + cInfo.setCharacteristic(global.cCharacteristic.FirmwareRevision, global.strFWVersion); + + // Initialize SecuritySystem (set values, getter and setter for the target state and the actual state) + this.cSecuritySystem = this.cActAccessory.getService(global.cService.SecuritySystem); // Pointer to the SecuritySystem service + this.cSecuritySystem + .getCharacteristic(global.cCharacteristic.SecuritySystemTargetState) + .setValue(this.eTargetState) + .on('get', this.getSecuritySystemTargetState.bind(this)) + .on('set', this.setSecuritySystemTargetState.bind(this)); + this.cSecuritySystem + .getCharacteristic(global.cCharacteristic.SecuritySystemCurrentState) + .setValue(this.eCurrentState) + .on('get', this.getSecuritySystemCurrentState.bind(this)); + + // Initialize the ContactSensor Service (set value and the getter method) + this.cContactSensorService = this.cActAccessory.getService(global.cService.ContactSensor); // Pointer to the ContactSensor service + this.cContactSensorService + .getCharacteristic(global.cCharacteristic.ContactSensorState) + .setValue(this.bCurrentSensorState) + .on('get', this.getCurrentSensorState.bind(this)); + + // Accessory is online now + // this.cActAccessory.updateReachability(true); + return; + } + + /** + * "Get" function for the target state. Delivers the stored state set before only + * + * @param {function} fCallback Callback function to give back the stored security system target state + * @returns {void} Nothing (value will be given by Callback) + */ + getSecuritySystemTargetState(fCallback) + { + var self = this; + self.cLog("%s - target state: %s", self.cActAccessory.displayName, LTARGETSTATES[self.eTargetState]); + // Give back stored state + fCallback(null, self.eTargetState); + return; + } + + /** + * "Set" function for the target state. + * + * @param {boolean} bValue New value for the security system target state + * @param {function} fCallback Callback function (send back value if value was set) + * @returns {void} Nothing (value will be given by Callback) + */ + setSecuritySystemTargetState(bValue, fCallback) + { + var self = this; + // Compare with old value + var bChanged = (self.eTargetState != bValue); // Value has been changed + + if (bChanged) + { // If value has changed + // Store value + self.eTargetState = bValue; + self.cLog("%s - setting target state: %s, Changed: %s", this.cActAccessory.displayName, LTARGETSTATES[self.eTargetState], (bChanged ? "true" : "false")); + // Run statemachine + self.RunStatemachine(bChanged, false); + } + fCallback(null, self.bCurrentSwitchState); + return; + } + + /** + * "Get" function for the current state. Delivers the state calculated by the state machine + * + * @param {function} fCallback Callback function to give back the stored security system state + * @returns {void} Nothing (value will be given by Callback) + */ + getSecuritySystemCurrentState(fCallback) + { + var self = this; + self.cLog("%s - current state: %s", self.cActAccessory.displayName, LSTATES[self.eCurrentState]); + // Give back stored state + fCallback(null, self.eCurrentState); + return; + } + + /** + * Function to actively change the current state of the security system + * + * @param {Enumerator} eValue New value of the actual security system state + * @returns {void} Nothing + */ + UpdateSecuritySystemCurrentState(eValue) + { + var self = this; + self.cLog("%s - updating current state to %s", this.cActAccessory.displayName, LSTATES[eValue]); + // Store value local and send it to homebridge + self.eCurrentState = eValue; + self.cSecuritySystem.getCharacteristic(global.cCharacteristic.SecuritySystemCurrentState).setValue(eValue, undefined, self.cActAccessory.context); + return; + } + + /** + * "Get" function for current state of the forgot_window_warning + * + * @param {function} fCallback Callback function to give back the actual state of the forgot_window_warning reminder + * @returns {void} Nothing (value will be given by Callback) + */ + getCurrentSensorState(fCallback) + { + var self = this; + self.cLog("%s - getting current forgot_window_warning state", this.cActAccessory.displayName); + // Give back current state + fCallback(null, self.bCurrentSensorState); + return; + } + + /** + * Function to actively change the state of the virtual window + * + * @param {boolean} bValue New state of the forgot_window_warning reminder + * @returns {void} Nothing + */ + UpdateCurrentSensorState(bValue) + { + var self = this; + self.cLog("%s - updating current forgot_window_warning state to %s", this.cActAccessory.displayName, (bValue ? "true" : "false")); + // Store value local and send it to homebridge + self.bCurrentSensorState = bValue; + self.cContactSensorService.getCharacteristic(global.cCharacteristic.ContactSensorState).setValue(bValue, undefined, self.cActAccessory.context); + return; + } + + /** + * Function that is called from the WindowAndDoorAccessory class every time a window state changes. The state of the corresponding + * window will be stored and the state machine will be run to see what effects this will take. + * + * @param {number} iSerial Serial number of the window or door + * @param {boolean} bState New state of the window or door + * @returns {void} Nothing + */ + setWindowState(iSerial, bState) + { + var self = this; + self.lWindowStates[iSerial] = bState; + self.RunStatemachine(false, false); + return; + } + + /** + * State machine that is doing the actual security system. + * + * @param {boolean} bChangedTargetState True if the statemachine is run because of a new target state + * @param {boolean} bTimeout True if the statemachine is run because of a timeout + * @returns Nothing + */ + RunStatemachine(bChangedTargetState, bTimeout) + { + var eNewCurrentState; // New actual state of the security system to be changed to + var bNewCurrentSensorState; // New reminder state to be changed to + var self = this; + + // If there's still a timeout running, then stop it + clearTimeout(self.iTimeoutId); + self.iTimeoutId = undefined; + + // In case of alarm triggered wait for disarm only + if ((self.eCurrentState == global.cCharacteristic.SecuritySystemCurrentState.ALARM_TRIGGERED) && + (self.eTargetState != global.cCharacteristic.SecuritySystemTargetState.DISARM)) + { + return; + } + + // Determine whether a window is opened + var bWindowOpened = false; + self.lWindowStates.forEach(bThisWindowOpened => { bWindowOpened = bWindowOpened || bThisWindowOpened; }); + + // The state machine + switch (self.eTargetState) + { + //--------------------------------------------------------------- + // STATE: Disarmed + //----------------- + case global.cCharacteristic.SecuritySystemTargetState.DISARM: + // Always go to new state without condition + eNewCurrentState = global.cCharacteristic.SecuritySystemCurrentState.DISARMED; + // If switched off then release forgot_window_warning + bNewCurrentSensorState = false; + break; + + //--------------------------------------------------------------- + // STATE: At home + //----------------- + case global.cCharacteristic.SecuritySystemTargetState.STAY_ARM: + // Always go to new state without condition + eNewCurrentState = global.cCharacteristic.SecuritySystemCurrentState.STAY_ARM; + // If I'm back home release forgot_window_warning + bNewCurrentSensorState = false; + break; + + //--------------------------------------------------------------- + // STATE: Away + //----------------- + case global.cCharacteristic.SecuritySystemTargetState.AWAY_ARM: + + if (self.eCurrentState != global.cCharacteristic.SecuritySystemCurrentState.AWAY_ARM) + { // If the security system is not armed yet, then try to arm it + if (bWindowOpened) + { // If a window is opened, then the security system cannot be activated and a warning for the opened window / door shall be sent + bNewCurrentSensorState = true; + } + else + { // otherwise switch of warning and activate the security system + bNewCurrentSensorState = false; + eNewCurrentState = global.cCharacteristic.SecuritySystemCurrentState.AWAY_ARM; + } + } + else + { // If it already was armed before and now a window opens, an alarm will be triggered + if (bWindowOpened) + eNewCurrentState = global.cCharacteristic.SecuritySystemCurrentState.ALARM_TRIGGERED; + } + break; + + //--------------------------------------------------------------- + // STATE: At night + //----------------- + case global.cCharacteristic.SecuritySystemTargetState.NIGHT_ARM: + + if (self.eCurrentState != global.cCharacteristic.SecuritySystemCurrentState.NIGHT_ARM) + { // If the security system is not armed for the night yet, then try to arm it + if (bWindowOpened) + { // If a window is opened, then the security system cannot be activated and a warning for the opened window / door shall be sent + bNewCurrentSensorState = true; + } + else + { // otherwise switch of warning and activate the security system + bNewCurrentSensorState = false; + eNewCurrentState = global.cCharacteristic.SecuritySystemCurrentState.NIGHT_ARM; + } + } + else + { // If it already was armed before and now a window opens, an alarm will be triggered + if (bWindowOpened) + eNewCurrentState = global.cCharacteristic.SecuritySystemCurrentState.ALARM_TRIGGERED; + } + break; + } + + // If values have been changed, call update functions + if (eNewCurrentState != this.eCurrentState) + self.UpdateSecuritySystemCurrentState(eNewCurrentState); + if (bNewCurrentSensorState != this.bCurrentSensorState) + self.UpdateCurrentSensorState(bNewCurrentSensorState); + return; + } +} + +//----------------------------------------------------------------------- +// Exports +//----------------------------------------------------------------------- + +module.exports = cSecuritySystemAccessory; diff --git a/WindowAndDoorAccessory.js b/WindowAndDoorAccessory.js new file mode 100644 index 0000000..cf9cbbf --- /dev/null +++ b/WindowAndDoorAccessory.js @@ -0,0 +1,253 @@ +// Implements the cWindowAndDoorAccessory class that is handling the door and window sensors, +// activating and deactivating the window reminders. +// +//----------------------------------------------------------------------- +// Date Author Change +//----------------------------------------------------------------------- +// 15.05.2020 D.Steidl Created +// 31.05.2020 D.Steidl JSDoc added +//----------------------------------------------------------------------- + +//----------------------------------------------------------------------- +// Global variables +//----------------------------------------------------------------------- + +// variables have to be declared explicitly +'use strict' + +/** @const {Object} ESTATES Enumeration for state machine */ +const ESTATES = {CLOSED: 1, OPENED: 2, REMIND: 3} + +//----------------------------------------------------------------------- +// Imports +//----------------------------------------------------------------------- + +// from JavaScript + +// from HomeSecurityLite + +//----------------------------------------------------------------------- +// Classes +//----------------------------------------------------------------------- + +/** + * Class for the window and door contact sensors of the home security lite system. This class provides a virtual switch + * as input for the state of a real contact sensor and one virtual contact sensor, that "opens" a virtual window with delay + * to the real window, so a message can be sent as a reminder to close the window. + */ +class cWindowAndDoorAccessory +{ + /** + * Constructor of the cWindowAndDoorAccessory class. This is reading in all the values and configuration and add all the + * services and characteristics. + * + * @param {Object} cLog Pointer to logging class + * @param {Object} cActAccessory Pointer to own accessory + * @param {Object} cSecuritySystem Pointer to the security system object + * @param {Object} dWinDoorConfig Configuration for this window / door sensor + * @param {Object} dConfig Configuration for the plugin + * @param {number} iSerial A fake serial number (staring with 1 counting up) for the AccessoryInformation Service + * @returns {void} Nothing + */ + constructor(cLog, cActAccessory, cSecuritySystem, dWinDoorConfig, dConfig, iSerial) + { + // Store values + this.cLog = cLog; // Pointer to logging class + this.cActAccessory = cActAccessory; // Pointer to own accessory + this.cSecuritySystem = cSecuritySystem; // Pointer to the security system object + this.dWinDoorConfig = dWinDoorConfig; // Configuration for this window / door sensor + this.iSerial = iSerial; // A fake serial number (staring with 1 counting up) for the AccessoryInformation Service + + // Read the configuration, use door/window config first, if not set then fall back to global config, if not set fall back to default values + this.iFirstRemind = dWinDoorConfig["first_remind"] || dConfig["first_remind"] || 900; // for remind of open window after x seconds + this.iFastCyclicRemind = dWinDoorConfig["fast_cyclic_remind"] || dConfig["fast_cyclic_remind"] || 120; // time in seconds for fast cyclic remind (i.e in winter) + this.iSlowCyclicRemind = dWinDoorConfig["slow_cyclic_remind"] || dConfig["slow_cyclic_remind"] || 1800; // time in seconds for slow cyclic remind + + // Initialize + this.bCurrentSwitchState = false; // State of the input switch (window closed) + this.cSecuritySystem.setWindowState(this.iSerial, this.bCurrentSwitchState); // Sent state to security system + this.bCurrentSensorState = false; // State of the reminder (switched off) + this.eState = ESTATES.CLOSED; // State of the state machine (window closed= + this.iTimeoutId = undefined; // Id of a started timeout to find it again (No Timeout started yet) + this.bFastRemind = false; // Use fast remind (don't use) + + // Set Information Accessory + var cInfo = this.cActAccessory.getService(global.cService.AccessoryInformation); + cInfo.setCharacteristic(global.cCharacteristic.Manufacturer, "D. Steidl"); + cInfo.setCharacteristic(global.cCharacteristic.Model, "ContactSensorDelayer"); + cInfo.setCharacteristic(global.cCharacteristic.SerialNumber, this.iSerial.toString()); + cInfo.setCharacteristic(global.cCharacteristic.FirmwareRevision, global.strFWVersion); + + // Initialize SwitchService (set value and connect get and set methods) + this.cSwitchService = this.cActAccessory.getService(global.cService.Switch); + this.cSwitchService + .getCharacteristic(global.cCharacteristic.On) + .setValue(this.bCurrentSwitchState) + .on('get', this.getCurrentSwitchState.bind(this)) + .on('set', this.setCurrentSwitchState.bind(this)); + + // Initialize ContactSensor Service (set value and connect get method) + this.cContactSensorService = this.cActAccessory.getService(global.cService.ContactSensor); + this.cContactSensorService + .getCharacteristic(global.cCharacteristic.ContactSensorState) + .setValue(this.bCurrentSensorState) + .on('get', this.getCurrentSensorState.bind(this)); + + // Accessory is online now + // this.cActAccessory.updateReachability(true); + return; + } + + /** + * "Get" function for the switch state. Delivers the stored state set before only + * + * @param {function} fCallback Callback funtion pointer to sent back the value + * @returns {void} Nothing (value will be given by Callback) + */ + getCurrentSwitchState(fCallback) + { + var self = this; + self.cLog("%s - current state: %s", self.cActAccessory.displayName, (self.bCurrentSwitchState ? "true" : "false")); + // Give back stored state + fCallback(null, self.bCurrentSwitchState); + return; + } + + // + /** + * "Set" function for the switch state. On the rising edge (window opened) a timer will be started with the configured + * "remind_after_secs" delay. + * + * @param {boolean} bValue Window state (true = open, false = closed) + * @param {function} fCallback Callback function (send back value if value was set) + * @returns {void} Nothing (value will be given by Callback) + */ + setCurrentSwitchState(bValue, fCallback) + { + var self = this; + // Compare with old value + var bChanged = (self.bCurrentSwitchState != bValue); // Value has been changed + + if (bChanged) + { // If value changed + // Store value + self.bCurrentSwitchState = bValue; + // Inform the security system + self.cSecuritySystem.setWindowState(self.iSerial, self.bCurrentSwitchState); + self.cLog("%s - setting switch: %s, Changed: %s", this.cActAccessory.displayName, (bValue ? "true" : "false"), (bChanged ? "true" : "false")); + // Run statemachine + self.RunStatemachine(false); + } + // always acknowledge the reception of the value + fCallback(null, self.bCurrentSwitchState); + return; + } + + /** + * "Get" function for current state of the virtual window + * + * @param {function} fCallback Callback function (sent back actual state of the reminder) + * @returns {void} Nothing (value will be given by Callback) + */ + getCurrentSensorState(fCallback) + { + var self = this; + self.cLog("%s - getting current window/door state", this.cActAccessory.displayName); + // Give back current state + fCallback(null, self.bCurrentSensorState); + return; + } + + /** + * Function to actively change the state of the virtual window + * + * @param {boolean} bValue New state of the reminder + * @returns {void} Nothing + */ + UpdateCurrentSensorState(bValue) + { + var self = this; + self.cLog("%s - updating current window/door state to %s", this.cActAccessory.displayName, (bValue ? "true" : "false")); + // Store value local and send it to homebridge + self.bCurrentSensorState = bValue; + self.cContactSensorService.getCharacteristic(global.cCharacteristic.ContactSensorState).setValue(bValue, undefined, self.cActAccessory.context); + return; + } + + /** + * "Set" function for the fast reminder switch + * + * @param {boolean} bValue New value of the fast reminder switch + * @returns {void} Nothing + */ + setFastRemind(bValue) + { + this.bFastRemind = bValue; + return; + } + + /** + * State machine that is doing the reminder feature + * + * @param {boolean} bTimeout Will be set if the statemachine is run by the runout timeout, otherwise it's false + * @returns {void} Nothing + */ + RunStatemachine(bTimeout) + { + var self = this; + + // If there's still a timeout running, then stop it + clearTimeout(self.iTimeoutId); + self.iTimeoutId = undefined; + + // The state machine + switch (self.eState) + { + case ESTATES.CLOSED: + // The window is closed + if (self.bCurrentSwitchState == true) + { // If window was opened, start Timeout and go to state opened + self.iTimeoutId = setInterval(self.RunStatemachine.bind(self), self.iFirstRemind * 1000, true); + self.eState = ESTATES.OPENED; + } + break; + + case ESTATES.OPENED: + // The window is opened + if (self.bCurrentSwitchState == false) + { // If window was closed before Timeout, go back to closed (Timeout stopped above) + self.eState = ESTATES.CLOSED; + } + else if (bTimeout == true) + { // Time is over. Send a remind, start another timer for reminding again and go to remind state + self.UpdateCurrentSensorState(true); + self.iTimeoutId = setInterval(self.RunStatemachine.bind(self), ((self.bFastRemind ? self.iFastCyclicRemind : self.iSlowCyclicRemind) - 1) * 1000, true); + self.eState = ESTATES.REMIND; + } + break; + + case ESTATES.REMIND: + // The reminder was sent, wait for window to be closed or send another reminder + if (self.bCurrentSwitchState == false) + { // If window was closed now, quit the reminder and go back to closed (Timeout stopped above) + self.UpdateCurrentSensorState(false); + self.eState = ESTATES.CLOSED; + } + else if (bTimeout == true) + { // Time is over again and the window still not closed. "Close" virtual window for a second and start timer to reopen it in a second. + // Then go to OPENED to wait for the timer to run out again + self.UpdateCurrentSensorState(false); + self.iTimeoutId = setInterval(self.RunStatemachine.bind(self), 1000, true); + self.eState = ESTATES.OPENED; + } + break; + } + return; + } +} + +//----------------------------------------------------------------------- +// Exports +//----------------------------------------------------------------------- + +module.exports = cWindowAndDoorAccessory; diff --git a/config.schema.json b/config.schema.json new file mode 100644 index 0000000..89ba2fa --- /dev/null +++ b/config.schema.json @@ -0,0 +1,91 @@ +{ + "pluginAlias": "HomeSecurityLitePlatform", + "pluginType": "platform", + "schema": { + "type": "object", + "properties": { + "prefix_remind_me": { + "type": "string", + "title": "Prefix for Remind me", + "required": false + }, + "prefix_reminder": { + "type": "string", + "title": "Prefix for Reminder", + "required": false + }, + "prefix_reminder_accessory": { + "type": "string", + "title": "Prefix for the Reminder accessory", + "required": false + }, + "forgot_window_warning_name": { + "type": "string", + "title": "Name of the Forgot Window Warning contact sensor", + "required": false + }, + "home_security_lite_name": { + "type": "string", + "title": "Name of the Home Security Lite accessory", + "required": false + }, + "security_system_name": { + "type": "string", + "title": "Name of the Security system", + "required": false + }, + "fast_cyclic_reminder_name": { + "type": "string", + "title": "Name of the Remind me fast again switch", + "required": false + }, + "first_remind": { + "type": "number", + "title": "First reminder after x seconds", + "required": false + }, + "fast_cyclic_remind": { + "type": "number", + "title": "Remind me again fast after x seconds", + "required": false + }, + "slow_cyclic_remind": { + "type": "number", + "title": "Remind me again slow after x seconds", + "required": false + }, + "windows_and_doors": { + "title": "Window and door contact sensors", + "required": false, + "type": "array", + "items": { + "name": { + "type": "string", + "title": "Name of door or window", + "required": true + }, + "description": { + "type": "string", + "title": "Optional description of door or window", + "required": false + }, + "first_remind": { + "type": "number", + "title": "First reminder after x seconds", + "required": false + }, + "fast_cyclic_remind": { + "type": "number", + "title": "Remind me again fast after x seconds", + "required": false + }, + "slow_cyclic_remind": { + "type": "number", + "title": "Remind me again slow after x seconds", + "required": false + } + } + } + } + } +} \ No newline at end of file diff --git a/index.js b/index.js new file mode 100644 index 0000000..b3e3f12 --- /dev/null +++ b/index.js @@ -0,0 +1,67 @@ +// Main file for plugin +// +//----------------------------------------------------------------------- +// Date Author Change +//----------------------------------------------------------------------- +// 15.05.2020 D.Steidl Created +// 22.05.2020 D.Steidl JSDoc added +//----------------------------------------------------------------------- + +//----------------------------------------------------------------------- +// Global variables +//----------------------------------------------------------------------- + +// variables have to be declared explicitly +'use strict' + +/** @type {Object} Pointer to Homebridge.platformAccessory */ +var cAccessory; +/** @type {Object} Pointer to Homebridge.hap.Service */ +var cService; +/** @type {Object} Pointer to Homebridge.hap.Characteristic */ +var cCharacteristic; +/** @type {Object} Pointer to Homebridge.hap.uuid */ +var cUUIDGen; +/** @type {String} FW-Version of the plugin (shown in Homekit) */ +var strFWVersion; +//----------------------------------------------------------------------- +// Imports +//----------------------------------------------------------------------- + +// from JavaScript + +// from HomeSecurityLite +const cHomeSecurityLitePlatform = require('./HomeSecurityLitePlatform') +const packageJson = require('./package.json') + +//----------------------------------------------------------------------- +// Classes +//----------------------------------------------------------------------- + +//----------------------------------------------------------------------- +// Exports +//----------------------------------------------------------------------- + +/** + * Anonymous funtion called by homebridge + * + * @param {Object} cHomebridge Pointer to homebridge object + * @returns {void} nothing + */ +module.exports = function (cHomebridge) +{ + global.strFWVersion = packageJson.version; + console.log("Homebridge API version: " + cHomebridge.version + " HomeSecurityLite V" + global.strFWVersion); + + // Accessory must be created from PlatformAccessory Constructor + global.cAccessory = cHomebridge.platformAccessory; + + // Service and Characteristic are from hap-nodejs + global.cService = cHomebridge.hap.Service; + global.cCharacteristic = cHomebridge.hap.Characteristic; + global.cUUIDGen = cHomebridge.hap.uuid; + + // For platform plugin to be considered as dynamic platform plugin, + // registerPlatform(pluginName, platformName, constructor, dynamic), dynamic must be true + cHomebridge.registerPlatform(packageJson.name, "HomeSecurityLitePlatform", cHomeSecurityLitePlatform, true); +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..59824e6 --- /dev/null +++ b/package.json @@ -0,0 +1,31 @@ +{ + "name": "homebridge-home-security-lite", + "version": "0.3.0", + "description": "A homebridge-plugin for using your available homekit sensors and actors to build a security system", + "displayName": "Homebridge Home Security Lite", + "author": "D. Steidl", + "main": "index.js", + "scripts": { + "test": "./node_modules/homebridge/bin/homebridge -P ./ -I -U ./.homebridge" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/SteidlD/homebridge-home-security-lite.git" + }, + "keywords": [ + "homebridge-plugin", + "security system", + "alarm", + "contact sensor", + "homebridge" + ], + "license": "gpl-3.0", + "bugs": { + "url": "https://github.com/SteidlD/homebridge-home-security-lite/issues" + }, + "homepage": "https://github.com/SteidlD/homebridge-home-security-lite#readme", + "engines": { + "homebridge": ">=1.0.1", + "node": ">=12.16.2" + } +}