├── .gitignore ├── README.md ├── images ├── Close_Sensor.jpg └── Relay_Wiring.jpg ├── index.js ├── package.json └── scripts ├── etc ├── defaults │ └── homebridge └── systemd │ └── system │ └── homebridge.service └── var └── lib └── homebridge ├── config-sample-two-doors.json └── garage-door-gpio /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # homebridge-rasppi-gpio-garagedoor 2 | Raspberry Pi GPIO GarageDoor plugin for [HomeBridge](https://github.com/nfarina/homebridge) 3 | 4 | # Circuit 5 | This plugin assumes that you are using a Raspberry Pi to directly control your garage door. Garage Door openers usually have 6 | a switch on the wall that you can push to open the garage door. On my model, this is just a very simple switch that completes 7 | a 24vdc circuit. The button must be pressed for about a second before the door will open. In order for this to be an effective 8 | garage door opener, you need two parts, a relay that will perform the duty of the button, and a reed switch that will 9 | detect when your garage door is closed. 10 | 11 | ![](https://raw.githubusercontent.com/benlamonica/homebridge-rasppi-gpio-garagedoor/master/images/Close_Sensor.jpg) 12 | 13 | ![](https://raw.githubusercontent.com/benlamonica/homebridge-rasppi-gpio-garagedoor/master/images/Relay_Wiring.jpg) 14 | 15 | # Installation 16 | 17 | ## IMPORTANT NOTE ON PIN SELECTION 18 | When the Raspberry Pi reboots GPIO pins are reset to their default state. This can cause your garage door to open without you issuing a command. Please make sure you pick the correct pins so that you don't accidentally have your garage door opening after a power loss. 19 | 20 | The following pins are pulled HIGH (they output a 3.3 volt signal) on reboot: 21 | * GPIO0/2 22 | * GPIO1/3 23 | * GPIO4 24 | * GPIO7 25 | * GPIO8 26 | 27 | GPIO14 is configured as a Serial Transmit line, so avoid choosing that pin. 28 | 29 | All other pins are pulled LOW (they have a 0 volt signal, same as GND). 30 | 31 | If your relay triggers when the GPIO pin goes LOW, then pick a pin that starts out HIGH on reboot. If your relay triggers with the GPIO PIN goes HIGH then pick a GPIO pin that starts out LOW on reboot. 32 | 33 | (information comes from https://www.raspberrypi.org/forums/viewtopic.php?f=44&t=24491) 34 | 35 | -------------------- 36 | 37 | 1. Install the following software: (assuming you are using debian stretch or later) 38 | 1. sudo apt-get install libavahi-client-dev nodejs-legacy nodejs npm 39 | 2. sudo npm install -g --unsafe-perf homebridge 40 | 3. sudo npm install homebridge-rasppi-gpio-garagedoor -g 41 | 2. Choose the GPIO pins that you are going to use, following the above information 42 | 3. Configure the system: 43 | 1. Create the /var/lib/homebridge directory 44 | 2. Copy the files from the [scripts/var/lib/homebridge directory](https://github.com/benlamonica/homebridge-rasppi-gpio-garagedoor/tree/master/scripts/) into appropriate locations; 45 | * scripts/etc/default/homebridge => /etc/default/homebridge 46 | * scripts/etc/systemd/system/homebridge.service => /etc/systemd/system/homebridge.service 47 | * scripts/etc/var/lib/homebridge/garage-door-gpio => /var/lib/homebridge/garage-door-gpio 48 | 3. Create the config.json to control homebridge at /var/lib/homebridge/config.json. Here is a sample of a config for [two garage doors](https://raw.githubusercontent.com/benlamonica/homebridge-rasppi-gpio-garagedoor/master/scripts/var/lib/homebridge/config-sample-two-doors.json). 49 | 4. Run the following commands to enable homebridge 50 | 1. sudo systemctl daemon-reload 51 | 2. sudo systemctl enable homebridge 52 | 3. sudo systemctl start homebridge 53 | 54 | # Configuration 55 | 56 | You will need to add the following accessory configuration to the Homebridge [config.json](https://github.com/nfarina/homebridge/blob/master/config-sample.json) 57 | 58 | Configuration sample: 59 | 60 | ``` 61 | "accessories": [ 62 | { 63 | "accessory": "RaspPiGPIOGarageDoor", 64 | "name": "Garage Door", 65 | "doorSwitchPin": 23, 66 | "doorSwitchPressTimeInMs": 1000, 67 | "doorSwitchValue": 1, 68 | "closedDoorSensorPin": 24, 69 | "closedDoorSensorValue": 1, 70 | "openDoorSensorPin": 25, 71 | "openDoorSensorValue": 1, 72 | "doorPollInMs": 4000, 73 | "doorOpensInSeconds": 14 74 | } 75 | ], 76 | ``` 77 | 78 | Fields: 79 | 80 | * name - Can be anything (required) 81 | * doorSwitchPin - The physical GPIO pin number that controls the relay to trigger the garage door 82 | * doorSwitchPressTimeInMs - number of milliseconds to trigger the garage door button. defaults to 1000 millseconds (1 second) if not specified 83 | * doorSwitchValue - 1 = ACTIVE_HIGH, 0 = ACTIVE_LOW, defaults to 1 if not specified. Set to 0 if you have a relay that requires the signal to be 0v to trigger. 84 | * closedDoorSensorPin - The physical GPIO pin that senses if the door is closed, do not specify if no sensor present 85 | * closedDoorSensorValue - 1 = ACTIVE_HIGH, 0 = ACTIVE_LOW, defaults to 1 if not specified 86 | * openDoorSensorPin - **OPTIONAL** Omit line if you don't have an open sensor. The physical GPIO pin that senses if the door is open, do not specify if no sensor present 87 | * openDoorSensorValue - **OPTIONAL** Omit line if you don't have an open sensor. 1 = ACTIVE_HIGH, 0 = ACTIVE_LOW, defaults to 1 if not specified 88 | * doorPollInMs - Number of milliseconds to wait before polling the doorSensorPin to report if the door is open or closed 89 | * doorOpensInSeconds - Number of seconds it takes your garage door to open or close (err on the side of being longer than it actually takes) 90 | 91 | 92 | -------------------------------------------------------------------------------- /images/Close_Sensor.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benlamonica/homebridge-rasppi-gpio-garagedoor/2479870a2bc75b19d5c4a4f5ce57be42ee90a512/images/Close_Sensor.jpg -------------------------------------------------------------------------------- /images/Relay_Wiring.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benlamonica/homebridge-rasppi-gpio-garagedoor/2479870a2bc75b19d5c4a4f5ce57be42ee90a512/images/Relay_Wiring.jpg -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /* jshint node: true */ 2 | "use strict"; 3 | var Service; 4 | var Characteristic; 5 | var DoorState; 6 | var process = require('process'); 7 | var rpio = require('rpio'); 8 | 9 | module.exports = function(homebridge) { 10 | Service = homebridge.hap.Service; 11 | Characteristic = homebridge.hap.Characteristic; 12 | DoorState = homebridge.hap.Characteristic.CurrentDoorState; 13 | 14 | homebridge.registerAccessory("homebridge-rasppi-gpio-garagedoor", "RaspPiGPIOGarageDoor", RaspPiGPIOGarageDoorAccessory); 15 | }; 16 | 17 | function getVal(config, key, defaultVal) { 18 | var val = config[key]; 19 | if (val === null) { 20 | return defaultVal; 21 | } 22 | return val; 23 | } 24 | 25 | function RaspPiGPIOGarageDoorAccessory(log, config) { 26 | this.log = log; 27 | this.version = require('./package.json').version; 28 | log("RaspPiGPIOGarageDoorAccessory version " + this.version); 29 | 30 | if (process.geteuid() !== 0) { 31 | log("WARN! WARN! WARN! may not be able to control GPIO pins because not running as root!"); 32 | } 33 | 34 | this.name = config.name; 35 | this.doorSwitchPin = config.doorSwitchPin; 36 | this.relayOn = getVal(config, "doorSwitchValue", 1); 37 | this.relayOff = 1-this.relayOn; //opposite of relayOn (O/1) 38 | this.doorSwitchPressTimeInMs = getVal(config, "doorSwitchPressTimeInMs", 1000); 39 | this.closedDoorSensorPin = getVal(config, "closedDoorSensorPin", config.doorSensorPin); 40 | this.openDoorSensorPin = config.openDoorSensorPin; 41 | this.sensorPollInMs = getVal(config, "doorPollInMs", 4000); 42 | this.doorOpensInSeconds = config.doorOpensInSeconds; 43 | this.closedDoorSensorValue = getVal(config, "closedDoorSensorValue", 1); 44 | this.openDoorSensorValue = getVal(config, "openDoorSensorValue", 1); 45 | log("Door Switch Pin: " + this.doorSwitchPin); 46 | log("Door Switch Val: " + (this.relayOn == 1 ? "ACTIVE_HIGH" : "ACTIVE_LOW")); 47 | log("Door Switch Active Time in ms: " + this.doorSwitchPressTimeInMs); 48 | 49 | if (this.hasClosedSensor()) { 50 | log("Door Closed Sensor: Configured"); 51 | log(" Door Closed Sensor Pin: " + this.closedDoorSensorPin); 52 | log(" Door Closed Sensor Val: " + (this.closedDoorSensorValue == 1 ? "ACTIVE_HIGH" : "ACTIVE_LOW")); 53 | } else { 54 | log("Door Closed Sensor: Not Configured"); 55 | } 56 | 57 | if(this.hasOpenSensor()) { 58 | log("Door Open Sensor: Configured"); 59 | log(" Door Open Sensor Pin: " + this.openDoorSensorPin); 60 | log(" Door Open Sensor Val: " + (this.openDoorSensorValue == 1 ? "ACTIVE_HIGH" : "ACTIVE_LOW")); 61 | } else { 62 | log("Door Open Sensor: Not Configured"); 63 | } 64 | 65 | if (!this.hasClosedSensor() && !this.hasOpenSensor()) { 66 | this.wasClosed = true; //Set a valid initial state 67 | log("NOTE: Neither Open nor Closed sensor is configured. Will be unable to determine what state the garage door is in, and will rely on last known state."); 68 | } 69 | log("Sensor Poll in ms: " + this.sensorPollInMs); 70 | log("Door Opens in seconds: " + this.doorOpensInSeconds); 71 | this.initService(); 72 | } 73 | 74 | RaspPiGPIOGarageDoorAccessory.prototype = { 75 | 76 | determineCurrentDoorState: function() { 77 | if (this.isClosed()) { 78 | return DoorState.CLOSED; 79 | } else if (this.hasOpenSensor()) { 80 | return this.isOpen() ? DoorState.OPEN : DoorState.STOPPED; 81 | } else { 82 | return DoorState.OPEN; 83 | } 84 | }, 85 | 86 | doorStateToString: function(state) { 87 | switch (state) { 88 | case DoorState.OPEN: 89 | return "OPEN"; 90 | case DoorState.CLOSED: 91 | return "CLOSED"; 92 | case DoorState.STOPPED: 93 | return "STOPPED"; 94 | default: 95 | return "UNKNOWN"; 96 | } 97 | }, 98 | 99 | monitorDoorState: function() { 100 | var isClosed = this.isClosed(); 101 | if (isClosed != this.wasClosed) { 102 | var state = this.determineCurrentDoorState(); 103 | if (!this.operating) { 104 | this.log("Door state changed to " + this.doorStateToString(state)); 105 | this.wasClosed = isClosed; 106 | this.currentDoorState.updateValue(state); 107 | this.targetState = state; 108 | } 109 | } 110 | setTimeout(this.monitorDoorState.bind(this), this.sensorPollInMs); 111 | }, 112 | 113 | hasOpenSensor : function() { 114 | return this.openDoorSensorPin !== null; 115 | }, 116 | 117 | hasClosedSensor : function() { 118 | return this.closedDoorSensorPin !== null; 119 | }, 120 | 121 | initService: function() { 122 | this.garageDoorOpener = new Service.GarageDoorOpener(this.name,this.name); 123 | this.currentDoorState = this.garageDoorOpener.getCharacteristic(DoorState); 124 | this.currentDoorState.on('get', this.getState.bind(this)); 125 | this.targetDoorState = this.garageDoorOpener.getCharacteristic(Characteristic.TargetDoorState); 126 | this.targetDoorState.on('set', this.setState.bind(this)); 127 | this.targetDoorState.on('get', this.getTargetState.bind(this)); 128 | var isClosed = this.isClosed(); 129 | 130 | this.wasClosed = isClosed; 131 | this.operating = false; 132 | this.infoService = new Service.AccessoryInformation(); 133 | this.infoService 134 | .setCharacteristic(Characteristic.Manufacturer, "Opensource Community") 135 | .setCharacteristic(Characteristic.Model, "RaspPi GPIO GarageDoor") 136 | .setCharacteristic(Characteristic.SerialNumber, "Version 1.0.0"); 137 | 138 | if (this.hasOpenSensor() || this.hasClosedSensor()) { 139 | this.log("We have a door sensor, monitoring door state enabled."); 140 | setTimeout(this.monitorDoorState.bind(this), this.sensorPollInMs); 141 | } 142 | 143 | this.log("Initial Door State: " + (isClosed ? "CLOSED" : "OPEN")); 144 | this.currentDoorState.updateValue(isClosed ? DoorState.CLOSED : DoorState.OPEN); 145 | this.targetDoorState.updateValue(isClosed ? DoorState.CLOSED : DoorState.OPEN); 146 | 147 | rpio.open(this.doorSwitchPin, rpio.OUTPUT, this.relayOff); 148 | if (this.hasClosedSensor()) { 149 | rpio.open(this.closedDoorSensorPin, rpio.INPUT); 150 | } 151 | if (this.hasOpenSensor()) { 152 | rpio.open(this.openDoorSensorPin, rpio.INPUT); 153 | } 154 | }, 155 | 156 | getTargetState: function(callback) { 157 | callback(null, this.targetState); 158 | }, 159 | 160 | readPin: function(pin) { 161 | return rpio.read(pin); 162 | }, 163 | 164 | writePin: function(pin,val) { 165 | rpio.write(this.doorSwitchPin, val); 166 | }, 167 | 168 | isClosed: function() { 169 | if (this.hasClosedSensor()) { 170 | return this.readPin(this.closedDoorSensorPin) == this.closedDoorSensorValue; 171 | } else if (this.hasOpenSensor()) { 172 | return !this.isOpen(); 173 | } else { 174 | return this.wasClosed; 175 | } 176 | }, 177 | 178 | isOpen: function() { 179 | if (this.hasOpenSensor()) { 180 | return this.readPin(this.openDoorSensorPin) == this.openDoorSensorValue; 181 | } else if (this.hasClosedSensor()) { 182 | return !this.isClosed(); 183 | } else { 184 | return !this.wasClosed; 185 | } 186 | }, 187 | 188 | switchOn: function() { 189 | this.writePin(this.doorSwitchPin, this.relayOn); 190 | this.log("Turning on GarageDoor Relay, pin " + this.doorSwitchPin + " = " + this.relayOn); 191 | setTimeout(this.switchOff.bind(this), this.doorSwitchPressTimeInMs); 192 | }, 193 | 194 | switchOff: function() { 195 | this.writePin(this.doorSwitchPin, this.relayOff); 196 | this.log("Turning off GarageDoor Relay, pin " + this.doorSwitchPin + " = " + this.relayOff); 197 | }, 198 | 199 | setFinalDoorState: function() { 200 | var isClosed, isOpen; 201 | if (!this.hasClosedSensor() && !this.hasOpenSensor()) { 202 | isClosed = !this.isClosed(); 203 | isOpen = this.isClosed(); 204 | } else { 205 | isClosed = this.isClosed(); 206 | isOpen = this.isOpen(); 207 | } 208 | if ( (this.targetState == DoorState.CLOSED && !isClosed) || (this.targetState == DoorState.OPEN && !isOpen) ) { 209 | this.log("Was trying to " + (this.targetState == DoorState.CLOSED ? "CLOSE" : "OPEN") + " the door, but it is still " + (isClosed ? "CLOSED":"OPEN")); 210 | this.currentDoorState.updateValue(DoorState.STOPPED); 211 | } else { 212 | this.log("Set current state to " + (this.targetState == DoorState.CLOSED ? "CLOSED" : "OPEN")); 213 | this.wasClosed = this.targetState == DoorState.CLOSED; 214 | this.currentDoorState.updateValue(this.targetState); 215 | } 216 | this.operating = false; 217 | }, 218 | 219 | setState: function(state, callback) { 220 | this.log("Setting state to " + state); 221 | this.targetState = state; 222 | var isClosed = this.isClosed(); 223 | if ((state == DoorState.OPEN && isClosed) || (state == DoorState.CLOSED && !isClosed)) { 224 | this.log("Triggering GarageDoor Relay"); 225 | this.operating = true; 226 | if (state == DoorState.OPEN) { 227 | this.currentDoorState.updateValue(DoorState.OPENING); 228 | } else { 229 | this.currentDoorState.updateValue(DoorState.CLOSING); 230 | } 231 | setTimeout(this.setFinalDoorState.bind(this), this.doorOpensInSeconds * 1000); 232 | this.switchOn(); 233 | } 234 | 235 | callback(); 236 | return true; 237 | }, 238 | 239 | getState: function(callback) { 240 | var isClosed = this.isClosed(); 241 | var isOpen = this.isOpen(); 242 | var state = isClosed ? DoorState.CLOSED : isOpen ? DoorState.OPEN : DoorState.STOPPED; 243 | this.log("GarageDoor is " + (isClosed ? "CLOSED ("+DoorState.CLOSED+")" : isOpen ? "OPEN ("+DoorState.OPEN+")" : "STOPPED (" + DoorState.STOPPED + ")")); 244 | callback(null, state); 245 | }, 246 | 247 | getServices: function() { 248 | return [this.infoService, this.garageDoorOpener]; 249 | } 250 | }; 251 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "homebridge-rasppi-gpio-garagedoor", 3 | "version": "1.0.10", 4 | "description": "Exposes a garage door accessory to HomeBridge that uses a RaspberryPi GPIO pins", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [ 10 | "homebridge-plugin", 11 | "homebridge", 12 | "garagedoor" 13 | ], 14 | "author": "Ben La Monica ", 15 | "license": "ISC", 16 | "repository": { 17 | "type": "git", 18 | "url": "https://github.com/benlamonica/homebridge-rasppi-gpio-garagedoor.git" 19 | }, 20 | "bugs": { 21 | "url": "https://github.com/benlamonica/homebridge-rasppi-gpio-garagedoor/issues" 22 | }, 23 | "homepage": "https://github.com/benlamonica/homebridge-rasppi-gpio-garagedoor", 24 | "engines": { 25 | "node": ">=0.12.0", 26 | "homebridge": ">=0.2.0" 27 | }, 28 | "dependencies": { 29 | "rpio": "^0.9.13" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /scripts/etc/defaults/homebridge: -------------------------------------------------------------------------------- 1 | # Defaults / Configuration options for homebridge 2 | # The following settings tells homebridge where to find the config.json file and where to persist the data (i.e. pairing and others) 3 | HOMEBRIDGE_OPTS=-U /var/lib/homebridge 4 | 5 | # If you uncomment the following line, homebridge will log more 6 | # You can display this via systemd's journalctl: journalctl -f -u homebridge 7 | # DEBUG=* -------------------------------------------------------------------------------- /scripts/etc/systemd/system/homebridge.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Node.js HomeKit Server 3 | After=syslog.target network-online.target 4 | 5 | [Service] 6 | Type=simple 7 | User=pi 8 | EnvironmentFile=/etc/default/homebridge 9 | # Adapt this to your specific setup (could be /usr/bin/homebridge) 10 | # See comments below for more information 11 | ExecStart=/usr/local/bin/homebridge $HOMEBRIDGE_OPTS 12 | Restart=on-failure 13 | RestartSec=10 14 | KillMode=process 15 | PermissionsStartOnly=true 16 | ExecStartPre=/var/lib/homebridge/garage-door-gpio start 17 | ExecStopPost=/var/lib/homebridge/garage-door-gpio stop 18 | 19 | [Install] 20 | WantedBy=multi-user.target -------------------------------------------------------------------------------- /scripts/var/lib/homebridge/config-sample-two-doors.json: -------------------------------------------------------------------------------- 1 | { 2 | "bridge": { 3 | "name": "Garage", 4 | "username": "01:23:45:67:89:AB", 5 | "port": 51826, 6 | "pin": "123-45-678" 7 | }, 8 | 9 | "description": "Garage Door HomeBridge Config, please file bugs at https://github.com/benlamonica/homebridge-rasppi-gpio-garagedoor/issues/new", 10 | 11 | "note":"The below accessories show how you can configure 2 garage doors at once. If you only have 1 garage door, remove one of the below blocks. The below config is the bare minimum configuration needed. You can configure other options (see the README.md for more detail)", 12 | 13 | "accessories": [{ 14 | "accessory": "RaspPiGPIOGarageDoor", 15 | "name": "Large Garage Door", 16 | "doorSwitchPin": 5, 17 | "doorSwitchPressTimeInMs": 1500, 18 | "doorSwitchValue": 1, 19 | "closedDoorSensorPin": 20, 20 | "closedDoorSensorValue": 1, 21 | "doorPollInMs": 4000, 22 | "doorOpensInSeconds": 14 23 | }, { 24 | "accessory": "RaspPiGPIOGarageDoor", 25 | "name": "Small Garage Door", 26 | "doorSwitchPin": 6, 27 | "doorSwitchPressTimeInMs": 1500, 28 | "doorSwitchValue": 1, 29 | "closedDoorSensorPin": 21, 30 | "closedDoorSensorValue": 1, 31 | "doorPollInMs": 4000, 32 | "doorOpensInSeconds": 14 33 | }], 34 | 35 | "platforms": [] 36 | } -------------------------------------------------------------------------------- /scripts/var/lib/homebridge/garage-door-gpio: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | PATH=/sbin:/bin:/usr/sbin:/usr/bin 3 | DESC="Enable GPIO for the Garage Door Module" 4 | NAME="garage-door-gpio" 5 | SCRIPTNAME=/etc/init.d/$NAME 6 | 7 | . /lib/lsb/init-functions 8 | 9 | function export_pin { 10 | if [ ! -e /sys/class/gpio/gpio${1} ] ; then 11 | echo $1 > /sys/class/gpio/export 12 | if [ "$3" == "low" ] ; then 13 | echo 1 > /sys/class/gpio/gpio${1}/active_low 14 | else 15 | echo 0 > /sys/class/gpio/gpio${1}/active_low 16 | fi 17 | echo $2 > /sys/class/gpio/gpio${1}/direction 18 | if [ "$2" == "out" ] ; then 19 | echo $4 > /sys/class/gpio/gpio${1}/value 20 | fi 21 | fi 22 | } 23 | 24 | function unexport_pin { 25 | if [ -e /sys/class/gpio/gpio${1} ] ; then 26 | echo $1 > /sys/class/gpio/unexport 27 | fi 28 | } 29 | 30 | if [ "$1" == "start" ] ; then 31 | log_daemon_msg "Enabling Garage Door GPIO pins for door 0" 32 | export_pin 5 out low 0 33 | export_pin 20 in high 0 34 | log_end_msg 0 35 | 36 | log_daemon_msg "Enabling Garage Door GPIO pins for door 1" 37 | export_pin 6 out low 0 38 | export_pin 21 in high 0 39 | log_end_msg 0 40 | else [ "$1" == "stop" ] 41 | log_daemon_msg "Disabling Garage Door GPIO pins for door 0" 42 | unexport_pin 5 43 | unexport_pin 20 44 | 45 | log_daemon_msg "Disabling Garage Door GPIO pins for door 1" 46 | unexport_pin 6 47 | unexport_pin 21 48 | fi --------------------------------------------------------------------------------