diff --git a/README.md b/README.md index bfc0450..437ffd8 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,45 @@ -# homebridge-gpio-electromagnetic-lock +# Homebridge GPIO Electromagnetic Lock Homebridge plugin to control electromagnetic lock via Raspberry Pi GPIO pins. + +## Motivation +I haven't found similar script working with Raspberry Pi GPIO. + +## Installation +1. install homebridge + `npm install -g homebridge` +2. install this plugin + `npm install -g homebridge-gpio-electromagnetic-lock` +3. update your `~/.homebridge/config.json` file (use `sample-config.json` as a reference) + +## Configuration +Sample accessory: +``` +"accessories": [ + { + "accessory": "ElectromagneticLock", + "name": "Lock", + "lockPin": 5, + "doorPin": 16, + "activeLow": false, + "reedSwitchActiveLow": false, + "unlockingDuration": 2, + "lockWithMemory": true + } +] +``` + +Fields: + +- `accessory` must always be *ElectromagneticLock* +- `name` accessory name, e.g. *Lock* +- `lockPin` pin for unlocking lock (use *gpio numbering*, not *physical*) +- `doorPin` [optional] door contact sensor, ignored when *lockWithMemory* is set to false +- `activeLow` [optional, default: *true*] true: relay activated by low state (0), false: relay activated by high state (1), affects *lockPin* +- `reedSwitchActiveLow` [optional, default: *true*] true: reed switch activated by low state (0), false: reed switch activated by high state (1), affects *doorPin* +- `unlockingDuration` [optional, default: *2*] how long *lockPin* should be active (seconds) +- `lockWithMemory` [optional, default: *true*] true: electromagnetic lock that stays unlocked until full door cycle, false: stays unlocked only for *unlockingDuration* seconds + +## Troubleshooting +- check platform: [Homebridge](https://github.com/nfarina/homebridge) +- check plugin dependency: [rpio](https://www.npmjs.com/package/rpio) +- or create issue diff --git a/config-sample.json b/config-sample.json index 96a2bbd..fd6ebd0 100644 --- a/config-sample.json +++ b/config-sample.json @@ -13,7 +13,9 @@ "lockPin": 5, "doorPin": 16, "activeLow": false, - "reedSwitchActiveLow": false + "reedSwitchActiveLow": false, + "unlockingDuration": 2, + "lockWithMemory": true } ] } diff --git a/index.js b/index.js index 87d768b..58ad22b 100644 --- a/index.js +++ b/index.js @@ -16,7 +16,7 @@ module.exports = function(homebridge) { } function ElectromagneticLockAccessory(log, config) { - _.defaults(config, {activeLow: true, reedSwitchActiveLow: true}); + _.defaults(config, {activeLow: true, reedSwitchActiveLow: true, unlockingDuration: 2, lockWithMemory: true}); this.log = log; this.name = config['name']; @@ -25,8 +25,8 @@ function ElectromagneticLockAccessory(log, config) { this.initialState = config['activeLow'] ? rpio.HIGH : rpio.LOW; this.activeState = config['activeLow'] ? rpio.LOW : rpio.HIGH; this.reedSwitchActiveState = config['reedSwitchActiveLow'] ? rpio.LOW : rpio.HIGH; - this.pollTransition = config['reedSwitchActiveLow'] ? rpio.POLL_LOW : rpio.POLL_HIGH; - this.doorWasOpenedAfterUnlocking = false; + this.unlockingDuration = config['unlockingDuration']; + this.lockWithMemory = config['lockWithMemory']; this.cacheDirectory = HomebridgeAPI.user.persistPath(); this.storage = require('node-persist'); @@ -39,7 +39,12 @@ function ElectromagneticLockAccessory(log, config) { this.currentState = cachedCurrentState; } - this.targetState = this.currentState; + this.lockState = this.currentState; + if (this.currentState == STATE_UNKNOWN) { + this.targetState = STATE_SECURED; + } else { + this.targetState = this.currentState; + } this.service = new Service.LockMechanism(this.name); @@ -47,21 +52,23 @@ function ElectromagneticLockAccessory(log, config) { this.infoService .setCharacteristic(Characteristic.Manufacturer, 'Radoslaw Sporny') .setCharacteristic(Characteristic.Model, 'RaspberryPi GPIO Electromagnetic Lock') - .setCharacteristic(Characteristic.SerialNumber, 'Version 1.0.3'); + .setCharacteristic(Characteristic.SerialNumber, 'Version 1.1.0'); + + this.unlockTimeout; // use gpio pin numbering rpio.init({mapping: 'gpio'}); rpio.open(this.lockPin, rpio.OUTPUT, this.initialState); + + if (this.doorPin && !this.lockWithMemory) { + this.log("Electromagnetic lock without memory doesn't support doorPin, setting to null. Consider using separate contact sensor."); + this.doorPin = undefined; + } + if (this.doorPin) { rpio.open(this.doorPin, rpio.INPUT); - if (this.currentState == STATE_UNSECURED) { - this.log('Lock is unsecured, waiting for door cycle.'); - this.doorWasOpenedAfterUnlocking = true; - try { - rpio.poll(this.doorPin, this.secureLock.bind(this), this.pollTransition); - } catch (error) { - this.log('Door is already polling for events.') - } + if (this.lockWithMemory) { + rpio.poll(this.doorPin, this.calculateLockWithMemoryState.bind(this)); } } @@ -76,16 +83,6 @@ function ElectromagneticLockAccessory(log, config) { } ElectromagneticLockAccessory.prototype.getCurrentState = function(callback) { - if (this.isDoorOpen()) { - this.targetState = STATE_UNSECURED; - this.currentState = STATE_UNSECURED; - this.doorWasOpenedAfterUnlocking = true; - try { - rpio.poll(this.doorPin, this.secureLock.bind(this), this.pollTransition); - } catch (error) { - this.log('Door is already polling for events.') - } - } this.log("Lock current state: %s", this.currentState); callback(null, this.currentState); } @@ -96,64 +93,75 @@ ElectromagneticLockAccessory.prototype.getTargetState = function(callback) { } ElectromagneticLockAccessory.prototype.setTargetState = function(state, callback) { - if (state) { // can't lock electromagnetic with memory - callback(null, this.currentState); - return false; - } - - this.log('Setting lock to UNSECURED'); - - rpio.write(this.lockPin, this.activeState); - this.service.setCharacteristic(Characteristic.LockCurrentState, state); - this.currentState = state; - this.targetState = state; - this.storage.setItemSync(this.name, this.currentState); - rpio.sleep(2); - rpio.write(this.lockPin, this.initialState); - if (this.doorPin) { - if (rpio.read(this.doorPin)) { - this.doorWasOpenedAfterUnlocking = true; - rpio.poll(this.doorPin, this.secureLock.bind(this), this.pollTransition); - } else { - rpio.poll(this.doorPin, this.waitForDoorCycleAndSecureLock.bind(this)); - } + this.log('Setting lock to %s', state ? 'secured' : 'unsecured'); + if (state && this.lockWithMemory) { + this.log("Can't lock electromagnetic lock with memory."); + this.service.updateCharacteristic(Characteristic.LockCurrentState, state); + setTimeout(function() { + this.service.updateCharacteristic(Characteristic.LockTargetState, this.targetState); + this.service.updateCharacteristic(Characteristic.LockCurrentState, this.currentState); + }.bind(this), 500); + callback(); + } else if (state && !this.lockWithMemory) { + clearTimeout(this.unlockTimeout); + this.secureLock(); + callback(); } else { - this.log('Setting lock to SECURED'); - this.service.setCharacteristic(Characteristic.LockTargetState, STATE_SECURED); - this.service.setCharacteristic(Characteristic.LockCurrentState, STATE_SECURED); - this.currentState = STATE_SECURED; - this.targetState = STATE_SECURED; - this.storage.setItemSync(this.name, this.currentState); + rpio.write(this.lockPin, this.activeState); + this.service.setCharacteristic(Characteristic.LockCurrentState, state); + this.lockState = state; + this.storage.setItemSync(this.name, this.lockState); + this.unlockTimeout = setTimeout(this.secureLock.bind(this), this.unlockingDuration*1000); + callback(); } - - callback(); } -ElectromagneticLockAccessory.prototype.waitForDoorCycleAndSecureLock = function() { +ElectromagneticLockAccessory.prototype.calculateLockWithMemoryState = function() { rpio.msleep(20); - if (rpio.read(this.doorPin)) { - this.doorWasOpenedAfterUnlocking = true; - return; + let doorOpen = rpio.read(this.doorPin) ? true : false; + if (doorOpen && this.lockState == STATE_UNSECURED) { + this.log('Door has been opened, lock: secured, current state: unsecured.'); + this.lockState = STATE_SECURED; + this.currentState = STATE_UNSECURED; + this.targetState = STATE_UNSECURED; + } else if (doorOpen && this.lockState == STATE_SECURED) { + this.log('Door has been opened, lock already secured, current state: unsecured.'); + this.currentState = STATE_UNSECURED; + this.targetState = STATE_UNSECURED; + } else if (!doorOpen && this.lockState == STATE_SECURED) { + this.log('Door has been closed, lock already secured, current state: secured.'); + this.currentState = STATE_SECURED; + this.targetState = STATE_SECURED; + } else if (!doorOpen && this.lockState == STATE_UNSECURED) { + this.log('Door has been closed, lock: unsecured, current state: unsecured.'); + this.currentState = STATE_UNSECURED; + this.targetState = STATE_UNSECURED; + } else { + this.log('State unknown, door open: ' + doorOpen + ', lock state: ' + this.lockState); + this.lockState == STATE_UNKNOWN; + this.currentState = STATE_UNKNOWN; } - this.secureLock(); -} - -ElectromagneticLockAccessory.prototype.secureLock = function() { - if (!this.doorWasOpenedAfterUnlocking) { - return; - } - this.log('Setting lock to SECURED'); - this.service.setCharacteristic(Characteristic.LockTargetState, STATE_SECURED); - this.service.setCharacteristic(Characteristic.LockCurrentState, STATE_SECURED); - this.currentState = STATE_SECURED; - this.targetState = STATE_SECURED; + this.service.updateCharacteristic(Characteristic.LockTargetState, this.targetState); + this.service.updateCharacteristic(Characteristic.LockCurrentState, this.currentState); this.storage.setItemSync(this.name, this.currentState); - this.doorWasOpenedAfterUnlocking = false; - rpio.poll(this.doorPin, null); } -ElectromagneticLockAccessory.prototype.isDoorOpen = function() { - return this.doorPin && rpio.read(this.doorPin); +ElectromagneticLockAccessory.prototype.secureLock = function() { + rpio.write(this.lockPin, this.initialState); + if (!this.doorPin && !this.lockWithMemory) { + this.service.updateCharacteristic(Characteristic.LockTargetState, STATE_SECURED); + this.service.updateCharacteristic(Characteristic.LockCurrentState, STATE_SECURED); + this.currentState = STATE_SECURED; + this.targetState = STATE_SECURED; + this.storage.setItemSync(this.name, this.currentState); + } else if (!this.doorPin && this.lockWithMemory) { + this.service.updateCharacteristic(Characteristic.LockTargetState, STATE_SECURED); + this.service.updateCharacteristic(Characteristic.LockCurrentState, STATE_SECURED); + this.service.updateCharacteristic(Characteristic.LockCurrentState, STATE_UNKNOWN); + this.currentState = STATE_UNKNOWN; + this.targetState = STATE_SECURED; + this.storage.setItemSync(this.name, this.currentState); + } } ElectromagneticLockAccessory.prototype.getServices = function() { diff --git a/package.json b/package.json index 5941960..8904504 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "homebridge-gpio-electromagnetic-lock", - "version": "1.0.3", + "version": "1.1.0", "description": "Homebridge plugin to control electromagnetic lock via Raspberry Pi GPIO pins", "license": "MIT", "keywords": [