diff --git a/README.md b/README.md index b7f3d03..8bbf6df 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ Sample accessory: "pinDown": 11, "durationUp": 13000, "durationDown": 13000, + "durationOffset": 1000, "pinClosed": 17, "pinOpen": 18, "activeLow": false, @@ -38,6 +39,7 @@ Fields: - `pinDown` pin for moving down - `durationUp` milliseconds to open blinds completely - `durationDown` milliseconds to close blinds completely +- `durationOffset` [optional, default: *0*] milliseconds added to durationUp and durationDown to make sure that blinds are completely open or closed - `pinClosed` [optional] pin connected to reed switch which is active when blind is closed, see *reedActiveLow* - `pinOpen` [optional] pin connected to reed switch which is active when blind is open, see *reedActiveLow* - `activeLow` [optional, default: *true*] true: relay activated by low state (0), false: relay activated by high state (1), affects *pinUp*, *pinDown* diff --git a/config-sample.json b/config-sample.json index 999210b..e27c540 100644 --- a/config-sample.json +++ b/config-sample.json @@ -22,6 +22,7 @@ "pinDown": 3, "durationUp": 27000, "durationDown": 25000, + "durationOffset": 1000, "pinClosed": 17, "pinOpen": 18, "activeLow": false, diff --git a/index.js b/index.js index d9918bb..0fa1348 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,6 @@ var _ = require('underscore'); var rpio = require('rpio'); -var Service, Characteristic; +var Service, Characteristic, HomebridgeAPI; const STATE_DECREASING = 0; const STATE_INCREASING = 1; @@ -9,12 +9,12 @@ const STATE_STOPPED = 2; module.exports = function(homebridge) { Service = homebridge.hap.Service; Characteristic = homebridge.hap.Characteristic; - + HomebridgeAPI = homebridge; homebridge.registerAccessory('homebridge-gpio-blinds', 'Blinds', BlindsAccessory); } function BlindsAccessory(log, config) { - _.defaults(config, {activeLow: true, reedSwitchActiveLow: true}); + _.defaults(config, {durationOffset: 0, activeLow: true, reedSwitchActiveLow: true}); this.log = log; this.name = config['name']; @@ -22,14 +22,25 @@ function BlindsAccessory(log, config) { this.pinDown = config['pinDown']; this.durationUp = config['durationUp']; this.durationDown = config['durationDown']; + this.durationOffset = config['durationOffset']; this.pinClosed = config['pinClosed']; this.pinOpen = config['pinOpen']; 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.currentPosition = 0; // down by default - this.targetPosition = 0; // down by default + this.cacheDirectory = HomebridgeAPI.user.persistPath(); + this.storage = require('node-persist'); + this.storage.initSync({dir:this.cacheDirectory, forgiveParseErrors: true}); + + var cachedCurrentPosition = this.storage.getItemSync(this.name); + if((cachedCurrentPosition === undefined) || (cachedCurrentPosition === false)) { + this.currentPosition = 0; // down by default + } else { + this.currentPosition = cachedCurrentPosition; + } + + this.targetPosition = this.currentPosition; this.positionState = STATE_STOPPED; // stopped by default this.service = new Service.WindowCovering(this.name); @@ -38,7 +49,7 @@ function BlindsAccessory(log, config) { this.infoService .setCharacteristic(Characteristic.Manufacturer, 'Radoslaw Sporny') .setCharacteristic(Characteristic.Model, 'RaspberryPi GPIO Blinds') - .setCharacteristic(Characteristic.SerialNumber, 'Version 1.1.1'); + .setCharacteristic(Characteristic.SerialNumber, 'Version 1.1.2'); this.finalBlindsStateTimeout; this.togglePinTimeout; @@ -80,18 +91,24 @@ BlindsAccessory.prototype.getCurrentPosition = function(callback) { } BlindsAccessory.prototype.getTargetPosition = function(callback) { - if (this.closedAndOutOfSync()) { + var updatedPosition; + if (this.openCloseSensorMalfunction()) { + this.log("Open and close reed switches are active, setting to 50"); + updatedPosition = 50; + } else if (this.closedAndOutOfSync()) { this.log("Current position is out of sync, setting to 0"); - this.currentPosition = 0; - this.targetPosition = 0; + updatedPosition = 0; } else if (this.openAndOutOfSync()) { this.log("Current position is out of sync, setting to 100"); - this.currentPosition = 100; - this.targetPosition = 100; + updatedPosition = 100; } else if (this.partiallyOpenAndOutOfSync()) { this.log("Current position is out of sync, setting to 50"); - this.currentPosition = 50; - this.targetPosition = 50; + updatedPosition = 50; + } + if (updatedPosition !== undefined) { + this.currentPosition = updatedPosition; + this.targetPosition = updatedPosition; + this.storage.setItemSync(this.name, updatedPosition); } this.log("Target position: %s", this.targetPosition); callback(null, this.targetPosition); @@ -122,10 +139,10 @@ BlindsAccessory.prototype.setTargetPosition = function(position, callback) { if (moveUp) { duration = Math.round((this.targetPosition - this.currentPosition) / 100 * this.durationUp); - this.currentPositionInterval = setInterval(function(){ this.currentPosition++; }.bind(this), this.intervalUp); + this.currentPositionInterval = setInterval(this.setCurrentPosition.bind(this, moveUp), this.intervalUp); } else { duration = Math.round((this.currentPosition - this.targetPosition) / 100 * this.durationDown); - this.currentPositionInterval = setInterval(function(){ this.currentPosition--; }.bind(this), this.intervalDown); + this.currentPositionInterval = setInterval(this.setCurrentPosition.bind(this, moveUp), this.intervalDown); } this.log((moveUp ? 'Moving up' : 'Moving down') + ". Duration: %s ms.", duration); @@ -142,9 +159,10 @@ BlindsAccessory.prototype.setTargetPosition = function(position, callback) { BlindsAccessory.prototype.togglePin = function(pin, duration) { if (rpio.read(pin) != this.activeState) rpio.write(pin, this.activeState); + if (this.durationOffset && (this.targetPosition == 0 || this.targetPosition == 100)) this.duration += this.durationOffset; this.togglePinTimeout = setTimeout(function() { rpio.write(pin, this.initialState); - }.bind(this), duration); + }.bind(this), parseInt(duration)); } BlindsAccessory.prototype.setFinalBlindsState = function() { @@ -153,9 +171,19 @@ BlindsAccessory.prototype.setFinalBlindsState = function() { this.service.setCharacteristic(Characteristic.PositionState, STATE_STOPPED); this.service.setCharacteristic(Characteristic.CurrentPosition, this.targetPosition); this.currentPosition = this.targetPosition; + this.storage.setItemSync(this.name, this.currentPosition); this.log("Successfully moved to target position: %s", this.targetPosition); } +BlindsAccessory.prototype.setCurrentPosition = function(moveUp) { + if (moveUp) { + this.currentPosition++; + } else { + this.currentPosition--; + } + this.storage.setItemSync(this.name, this.currentPosition); +} + BlindsAccessory.prototype.closedAndOutOfSync = function() { return this.currentPosition != 0 && this.pinClosed && (rpio.read(this.pinClosed) == this.reedSwitchActiveState); } @@ -169,6 +197,12 @@ BlindsAccessory.prototype.partiallyOpenAndOutOfSync = function() { (this.currentPosition == 100 && this.pinOpen && (rpio.read(this.pinOpen) != this.reedSwitchActiveState)); } +BlindsAccessory.prototype.openCloseSensorMalfunction = function() { + return (this.pinClosed && this.pinOpen && + (rpio.read(this.pinClosed) == this.reedSwitchActiveState) && + (rpio.read(this.pinOpen) == this.reedSwitchActiveState)); +} + BlindsAccessory.prototype.oppositeDirection = function(moveUp) { return (this.positionState == STATE_INCREASING && !moveUp) || (this.positionState == STATE_DECREASING && moveUp); } diff --git a/package.json b/package.json index fbbb695..9c48116 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,8 @@ }, "dependencies": { "rpio": "^0.9.12", - "underscore": "^1.8.3" + "underscore": "^1.8.3", + "node-persist": "^2.1.0" }, "engines": { "homebridge": ">=0.4.6",