├── .gitignore ├── presence_server.sh ├── presence.sh ├── yarn.lock ├── package.json ├── config-sample.json ├── readme.md └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /presence_server.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | /usr/sbin/arp | /bin/grep ":" | /usr/bin/awk '{ print $3 }' > /var/lib/misc/presence.wifi 3 | -------------------------------------------------------------------------------- /presence.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | while true 3 | do 4 | all="" 5 | for interface in `iw dev | grep Interface | cut -f 2 -s -d" "` 6 | do 7 | # for each interface, get mac addresses of connected stations/clients 8 | maclist=`iw dev $interface station dump | grep Station | cut -f 2 -s -d" "` 9 | 10 | # for each mac address in that list... 11 | for mac in $maclist 12 | do 13 | all="$mac\n$all" 14 | done 15 | done 16 | echo $all > /var/lib/misc/presence.wifi 17 | sleep 5 18 | done 19 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | co@^4.6.0: 6 | version "4.6.0" 7 | resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" 8 | 9 | lodash@^4.17.15: 10 | version "4.17.15" 11 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" 12 | integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "homebridge-wifipresence", 3 | "version": "1.0.4", 4 | "description": "Occupancy detection via wifi mac address", 5 | "main": "index.js", 6 | "keywords": [ 7 | "homebridge-plugin", 8 | "wifi", 9 | "homekit" 10 | ], 11 | "author": "Maythee Anegboonlap ", 12 | "license": "ISC", 13 | "dependencies": { 14 | "co": "^4.6.0", 15 | "lodash": "^4.17.15" 16 | }, 17 | "engines": { 18 | "node": ">=0.12.0", 19 | "homebridge": ">=0.4.9" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /config-sample.json: -------------------------------------------------------------------------------- 1 | { 2 | "bridge": { 3 | "name": "Homebridge", 4 | "username": "CD:22:3D:E3:CE:30", 5 | "port": 51826, 6 | "pin": "031-45-154" 7 | }, 8 | 9 | "description": "This is an example configuration for the WifiPresence homebridge plugin", 10 | 11 | "accessories": [ 12 | { 13 | "accessory": "WifiPresence", 14 | "name": "Main Wifi", 15 | "room": "Living room", 16 | "clients": ["MAC ADDRESS1"], 17 | "presenceFile": "/var/lib/misc/presence.wifi" 18 | } 19 | ], 20 | 21 | "platforms": [ 22 | 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # HomeBridge Wifi Presence 2 | 3 | Detect presence in the room via WiFi. This plugin uses MAC addresses to detect when somebody is in a room or not, depending on which network they are connected to. 4 | 5 | ## Setup 6 | 7 | Install plugin `npm install -g homebridge-wifipresence` and add accessories to homebridge config. 8 | 9 | ```json 10 | { 11 | "bridge": { 12 | "name": "Homebridge", 13 | "username": "CD:22:3D:E3:CE:30", 14 | "port": 51826, 15 | "pin": "031-45-154" 16 | }, 17 | 18 | "description": "This is an example configuration for the WifiPresence homebridge plugin", 19 | 20 | "accessories": [ 21 | { 22 | "accessory": "WifiPresence", 23 | "name": "Main Wifi", 24 | "room": "Living room", 25 | "mac": ["MAC ADDRESS1", "MAC ADDRESS2", ..., "MAC ADDRESSX"], 26 | "presenceFile": "/var/lib/misc/presence.wifi" 27 | } 28 | ], 29 | 30 | "platforms": [ 31 | 32 | ] 33 | } 34 | ``` 35 | 36 | `MAC ADDRESSX` is the device WiFi MAC Address that you want to monitor. Once this MAC Address is connected to your network, Homebridge will then trigger the presence sensor to make the room occupied. You can add more than 1 MAC address, and they should be written in lower case (i.e.: `aa:bb:cc:dd:ee:ff`). 37 | 38 | `presenceFile` is the path to list of MAC addresses. Currently, the default path is `/var/lib/misc/presence.wifi`, the same as in [presence.sh](presence.sh) scripts. 39 | 40 | ### Presence scripts 41 | Two presence scripts are provided: 42 | 43 | - `presence.sh`: To run on the router/access point to gather device MAC addresses from WiFi interfaces. 44 | - `presence_server.sh`: To run on the server where Homebridge is running, for when you do not have access or cannot run scripts on the router/access point. 45 | 46 | ## License 47 | 48 | ISC 49 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash') 2 | const fs = require('fs') 3 | const co = require('co') 4 | 5 | const DEFAULT_PRESENCE_PATH = '/var/lib/misc/presence.wifi' 6 | let Service, Characteristic 7 | 8 | const OCCUPIED = 1 9 | const NON_OCCUPIED = 0 10 | 11 | // Stops execution for n milliseconds 12 | function msleep(n) { 13 | Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, n); 14 | } 15 | 16 | class WifiPresenceAccessory { 17 | constructor (log, config) { 18 | this.log = log 19 | this.config = config 20 | this.name = config.name 21 | this.room = config.room 22 | this.presenceFile = config.presenceFile || DEFAULT_PRESENCE_PATH 23 | this.mac = config.mac 24 | 25 | this.service = new Service.OccupancySensor(this.name) 26 | this.service 27 | .getCharacteristic(Characteristic.OccupancyDetected) 28 | .on('get', callback => this.isOccupied(callback)) 29 | this.currentStatus = -1 30 | 31 | fs.watch(this.presenceFile, { persistent: false }, (type, filename) => { 32 | if (type === 'change') { 33 | // Sleep for 5 seconds to fix 34 | // Occupied statue: 0 35 | // Occupied statue: 1 36 | // pattern 37 | msleep(5000) 38 | this.isOccupied() 39 | } 40 | }) 41 | } 42 | 43 | isOccupied (callback = _.noop) { 44 | const accessory = this 45 | co(function* () { 46 | const content = yield accessory.readPresenceFile() 47 | const allMACs = content.trim().split('\n') 48 | const status = _.intersection(allMACs, accessory.mac).length > 0 ? OCCUPIED : NON_OCCUPIED 49 | accessory.log(`Occupied status: ${status}`) 50 | 51 | if (accessory.currentStatus !== status) { 52 | accessory.service.setCharacteristic(Characteristic.OccupancyDetected, status) 53 | accessory.currentStatus = status 54 | } 55 | 56 | callback(null, accessory.currentStatus) 57 | }) 58 | .catch(error => callback(error, 0)) 59 | } 60 | 61 | readPresenceFile () { 62 | return new Promise((resolve, reject) => { 63 | fs.readFile(this.presenceFile, { encoding: 'utf8' }, (error, data) => { 64 | if (error) return reject(error) 65 | return resolve(data) 66 | }) 67 | }) 68 | } 69 | 70 | getServices () { 71 | return [this.service, this.getInformationService()] 72 | } 73 | 74 | getInformationService () { 75 | var informationService = new Service.AccessoryInformation() 76 | informationService 77 | .setCharacteristic(Characteristic.Name, this.name) 78 | .setCharacteristic(Characteristic.Manufacturer, 'Wifi presence 1.0') 79 | .setCharacteristic(Characteristic.Model, '1.0.0') 80 | .setCharacteristic(Characteristic.SerialNumber, 'NUC6i3') 81 | return informationService 82 | } 83 | 84 | identify (callback) { 85 | this.log('Identify request') 86 | callback() 87 | } 88 | } 89 | 90 | module.exports = function (homebridge) { 91 | Service = homebridge.hap.Service 92 | Characteristic = homebridge.hap.Characteristic 93 | 94 | homebridge.registerAccessory( 95 | 'homebridge-wifi-presence', 96 | 'WifiPresence', 97 | WifiPresenceAccessory 98 | ) 99 | } 100 | --------------------------------------------------------------------------------