# HG changeset patch # User Luke Hoersten # Date 1516384105 21600 # Node ID 30d8bcb1ebb47ede4d996c7f4982ffcfba858e8a Initial public commit. diff -r 000000000000 -r 30d8bcb1ebb4 .hgignore --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.hgignore Fri Jan 19 11:48:25 2018 -0600 @@ -0,0 +1,5 @@ +syntax: regexp +~$ +\#.*\#$ +\.\# +\.DS_Store$ diff -r 000000000000 -r 30d8bcb1ebb4 README.md --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/README.md Fri Jan 19 11:48:25 2018 -0600 @@ -0,0 +1,12 @@ +# Raspberry Pi Homekit Door Accessory + +## Ansible Inventory + +`inventory/host_vars/raspberrypi.local.yaml` + + github_user: "" + nodejs_version: "8.9.0" + nodejs_dir: "node-v{{nodejs_version}}-linux-{{ansible_architecture}}" + wpa_networks: + - ssid: "" + psk: "" diff -r 000000000000 -r 30d8bcb1ebb4 ansible.cfg --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ansible.cfg Fri Jan 19 11:48:25 2018 -0600 @@ -0,0 +1,2 @@ +[defaults] +inventory = inventory/ diff -r 000000000000 -r 30d8bcb1ebb4 hap.yaml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hap.yaml Fri Jan 19 11:48:25 2018 -0600 @@ -0,0 +1,8 @@ +--- + +- hosts: raspberrypi + roles: + - base + # - automationhat + - hap-nodejs + - homebridge diff -r 000000000000 -r 30d8bcb1ebb4 inventory/raspberrypi --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/inventory/raspberrypi Fri Jan 19 11:48:25 2018 -0600 @@ -0,0 +1,2 @@ +[raspberrypi] +raspberrypi.local ansible_user=pi diff -r 000000000000 -r 30d8bcb1ebb4 roles/automationhat/tasks/main.yaml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/roles/automationhat/tasks/main.yaml Fri Jan 19 11:48:25 2018 -0600 @@ -0,0 +1,13 @@ +--- + +- name: download automationhat install script + get_url: + url: "https://get.pimoroni.com/automationhat" + dest: "/tmp/automationhat.sh" + mode: 0750 + +- name: install automationhat + shell: "/tmp/automationhat.sh" + args: + chdir: "/tmp" + executable: "/bin/bash" diff -r 000000000000 -r 30d8bcb1ebb4 roles/base/defaults/main.yaml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/roles/base/defaults/main.yaml Fri Jan 19 11:48:25 2018 -0600 @@ -0,0 +1,6 @@ +--- + +ubuntu_base_apt_packages: + - emacs-nox + - python3 + - htop diff -r 000000000000 -r 30d8bcb1ebb4 roles/base/tasks/main.yml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/roles/base/tasks/main.yml Fri Jan 19 11:48:25 2018 -0600 @@ -0,0 +1,21 @@ +--- + +- name: setup wifi + become: yes + template: + src: "wpa_supplicant.conf.j2" + dest: "/etc/wpa_supplicant/wpa_supplicant.conf" + mode: 0644 + +- name: update apt package cache + become: yes + apt: upgrade="dist" update_cache="yes" cache_valid_time="3600" + +- name: install extra apt packages + become: yes + apt: name="{{item}}" + with_items: "{{ubuntu_base_apt_packages}}" + +- name: authorize admin ssh keys + become: yes + authorized_key: "user=pi key=https://github.com/{{github_user}}.keys" diff -r 000000000000 -r 30d8bcb1ebb4 roles/base/templates/wpa_supplicant.conf.j2 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/roles/base/templates/wpa_supplicant.conf.j2 Fri Jan 19 11:48:25 2018 -0600 @@ -0,0 +1,12 @@ +# {{ansible_managed}} + +country=US +ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev +update_config=1 +{% for network in wpa_networks %} + +network={ + ssid="{{network.ssid}}" + psk="{{network.psk}}" +} +{% endfor %} diff -r 000000000000 -r 30d8bcb1ebb4 roles/hap-nodejs/defaults/main.yaml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/roles/hap-nodejs/defaults/main.yaml Fri Jan 19 11:48:25 2018 -0600 @@ -0,0 +1,13 @@ +--- + +hap_ubuntu_apt_packages: + - "libavahi-compat-libdnssd-dev" + +hap_npm_packages: + - "python-shell" + +hap_user: "pi" +hap_restart_sec: 10 +hap_src: "https://github.com/KhaosT/HAP-NodeJS/archive/master.zip" +hap_home: "/home/{{hap_user}}/" +hap_dest: "{{hap_home}}/HAP-NodeJS-master" diff -r 000000000000 -r 30d8bcb1ebb4 roles/hap-nodejs/files/Door_accessory.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/roles/hap-nodejs/files/Door_accessory.js Fri Jan 19 11:48:25 2018 -0600 @@ -0,0 +1,115 @@ +var Accessory = require('../').Accessory; +var Service = require('../').Service; +var Characteristic = require('../').Characteristic; +var uuid = require('../').uuid; +var PythonShell = require('python-shell'); + +var door = exports.accessory = new Accessory('Door', uuid.generate('hap-nodejs:accessories:door')); +door.username = 'C1:5D:3A:EA:54:AB'; +door.pincode = '031-45-154'; + +door.getService(Service.AccessoryInformation) + .setCharacteristic(Characteristic.Manufacturer, 'Raspberry Pi') + .setCharacteristic(Characteristic.Model, 'Zero W') + .setCharacteristic(Characteristic.SerialNumber, 'A1S2NASF88EW'); + +var DOOR = { + lockTimeout: 10000, // milliseconds - 10 sec + + pyshell: new PythonShell('doord.py', { + mode: 'text', + pythonPath: '/usr/bin/python3', + pythonOptions: ['-u'], + scriptPath: 'python/' + }), + + lock: function() { + console.log('locking door'); + this.pyshell.send('lock'); + }, + + unlock: function() { + console.log('unlocking door'); + this.pyshell.send('unlock'); + }, + + identify: function() { + console.log('identify door'); + }, + + listenDoorbell: function(doorbellOnCallback, doorbellOffCallback) { + this.pyshell.on('message', function (message) { + console.log(message); + switch(message) { + case 'doorbell_on': + doorbellOnCallback(); + break; + case 'doorbell_off': + doorbellOffCallback(); + break; + } + }); + } +}; + +door.on('identify', function(paired, callback) { + DOOR.identify(); + callback(); +}); + +door.addService(Service.Doorbell, 'Doorbell'); +door.addService(Service.CameraRTPStreamManagement, 'Psudo-Camera'); +door.addService(Service.Speaker, 'Psudo-Speaker'); +door.addService(Service.Microphone, 'Psudo-Microphone'); + +door.addService(Service.LockMechanism, 'Door') + .setCharacteristic(Characteristic.LockTargetState, Characteristic.LockTargetState.SECURED) // force initial state + .setCharacteristic(Characteristic.LockCurrentState, Characteristic.LockCurrentState.SECURED) + .getCharacteristic(Characteristic.LockTargetState) + .on('set', function(value, callback) { + setDoorTargetState(value); + callback(); + }); + +function setDoorTargetState(value) { + switch(value) { + case Characteristic.LockTargetState.UNSECURED: + unlockDoor(); + break; + case Characteristic.LockTargetState.SECURED: + lockDoor(); + break; + } +} + +function unlockDoor() { + DOOR.unlock(); + door.getService(Service.LockMechanism) + .setCharacteristic(Characteristic.LockCurrentState, + Characteristic.LockCurrentState.UNSECURED); + scheduleUnlockTimeout(); +} + +function lockDoor() { + DOOR.lock(); + door.getService(Service.LockMechanism) + .setCharacteristic(Characteristic.LockCurrentState, + Characteristic.LockCurrentState.SECURED); +} + +function scheduleUnlockTimeout() { + setTimeout(function() { + console.log('unlock timeout door'); + door.getService(Service.LockMechanism) + .setCharacteristic(Characteristic.LockTargetState, + Characteristic.LockTargetState.SECURED); + }, DOOR.lockTimeout); +} + +DOOR.listenDoorbell( + function() { + door.getService(Service.Doorbell) + .setCharacteristic(Characteristic.ProgrammableSwitchEvent, + Characteristic.ProgrammableSwitchEvent.SINGLE_PRESS); + }, + function() {}); diff -r 000000000000 -r 30d8bcb1ebb4 roles/hap-nodejs/files/doord.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/roles/hap-nodejs/files/doord.py Fri Jan 19 11:48:25 2018 -0600 @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 + +import time +import automationhat +import sys + +import queue +import threading + + +def main(): + command_queue = queue.LifoQueue() + read_thread = threading.Thread(target=read_loop, args=[command_queue]) + read_thread.start() + run_loop(command_queue) + + +def read_loop(command_queue): + while True: + command_queue.put_nowait(sys.stdin.readline().rstrip('\n')) + + +def run_loop(command_queue): + thread_local = threading.local() + thread_local.doorbell_on_state = False + + while True: + run_command(command_queue) + read_doorbell(thread_local) + + +def run_command(command_queue): + try: + command = command_queue.get(timeout=0.5) + except queue.Empty: + pass + else: + automationhat.relay.on() if command == "unlock" else automationhat.relay.off() + + +def read_doorbell(thread_local): + analog_value = automationhat.analog.one.read() + doorbell_on_state = 6.0 < analog_value and analog_value < 6.3 + + if doorbell_on_state != thread_local.doorbell_on_state: + thread_local.doorbell_on_state = doorbell_on_state + print("doorbell_on") if doorbell_on_state else print("doorbell_off") + + +if __name__ == "__main__": + main() diff -r 000000000000 -r 30d8bcb1ebb4 roles/hap-nodejs/handlers/main.yaml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/roles/hap-nodejs/handlers/main.yaml Fri Jan 19 11:48:25 2018 -0600 @@ -0,0 +1,5 @@ +--- + +- name: restart hap-nodejs service + systemd: name="hap-nodejs" state="restarted" daemon_reload="yes" + become: yes diff -r 000000000000 -r 30d8bcb1ebb4 roles/hap-nodejs/meta/main.yaml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/roles/hap-nodejs/meta/main.yaml Fri Jan 19 11:48:25 2018 -0600 @@ -0,0 +1,4 @@ +--- + +dependencies: + - role: nodejs diff -r 000000000000 -r 30d8bcb1ebb4 roles/hap-nodejs/tasks/main.yaml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/roles/hap-nodejs/tasks/main.yaml Fri Jan 19 11:48:25 2018 -0600 @@ -0,0 +1,45 @@ +--- + +- name: install homekit apt packages + become: yes + apt: name="{{item}}" + with_items: "{{hap_ubuntu_apt_packages}}" + +- name: download and unarchive HAP-NodeJS + unarchive: src="{{hap_src}}" dest="{{hap_home}}" remote_src="yes" creates="{{hap_dest}}" + +- name: install npm packages + npm: name="{{item}}" path="{{hap_dest}}" + with_items: "{{hap_npm_packages}}" + +- name: clean up accessory examples + file: path="{{hap_dest}}/accessories" state="absent" + +- name: create accessories dir + file: path="{{hap_dest}}/accessories" state="directory" + +- name: create python dir + file: path="{{hap_dest}}/python" state="directory" + +- name: install doord.py + copy: src="doord.py" dest="{{hap_dest}}/python/doord.py" + notify: restart hap-nodejs service + +- name: install door accessory + copy: src="Door_accessory.js" dest="{{hap_dest}}/accessories/Door_accessory.js" + notify: restart hap-nodejs service + +- name: build HAP-NodeJS + command: "npm install" + args: + chdir: "{{hap_dest}}" + changed_when: false + +- name: configure systemd service + become: yes + template: src="hap-nodejs.service.j2" dest="/lib/systemd/system/hap-nodejs.service" + notify: restart hap-nodejs service + +- name: ensure hap-nodejs is started + become: yes + systemd: name="hap-nodejs.service" enabled="yes" state="started" diff -r 000000000000 -r 30d8bcb1ebb4 roles/hap-nodejs/templates/hap-nodejs.service.j2 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/roles/hap-nodejs/templates/hap-nodejs.service.j2 Fri Jan 19 11:48:25 2018 -0600 @@ -0,0 +1,16 @@ +# {{ansible_managed}} + +[Unit] +Description=HAP-NodeJS + +[Service] +User={{hap_user}} +Group={{hap_user}} +Restart=always +RestartSec={{hap_restart_sec}} + +WorkingDirectory={{hap_dest}} +ExecStart=/usr/bin/node {{hap_dest}}/Core.js + +[Install] +WantedBy=default.target diff -r 000000000000 -r 30d8bcb1ebb4 roles/nodejs/tasks/main.yaml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/roles/nodejs/tasks/main.yaml Fri Jan 19 11:48:25 2018 -0600 @@ -0,0 +1,38 @@ +--- + +# https://github.com/nfarina/homebridge/wiki/Running-HomeBridge-on-a-Raspberry-Pi + +- name: download and unarchive nodejs + become: yes + unarchive: + src: "https://nodejs.org/dist/v{{nodejs_version}}/{{nodejs_dir}}.tar.xz" + dest: "/opt/" + remote_src: yes + creates: "/opt/{{nodejs_dir}}" + +# sudo update-alternatives --install "/usr/bin/node" "node" "/opt/node/bin/node" 1 +- name: update node alterantives + become: yes + alternatives: + link: "/usr/bin/{{item}}" + name: "{{item}}" + path: "/opt/{{nodejs_dir}}/bin/{{item}}" + priority: "1" + with_items: + - "node" + - "npm" + +- name: npm install global deps + become: yes + npm: name="{{item}}" global="yes" + with_items: + - "npm" + - "node-gyp" + +- name: update node-gyp alterantives + become: yes + alternatives: + link: "/usr/bin/node-gyp" + name: "node-gyp" + path: "/opt/{{nodejs_dir}}/bin/node-gyp" + priority: "1"