Initial public commit.
authorLuke Hoersten <luke@hoersten.org>
Fri, 19 Jan 2018 11:48:25 -0600
changeset 0 30d8bcb1ebb4
child 1 bc6a0f25a229
Initial public commit.
.hgignore
README.md
ansible.cfg
hap.yaml
inventory/raspberrypi
roles/automationhat/tasks/main.yaml
roles/base/defaults/main.yaml
roles/base/tasks/main.yml
roles/base/templates/wpa_supplicant.conf.j2
roles/hap-nodejs/defaults/main.yaml
roles/hap-nodejs/files/Door_accessory.js
roles/hap-nodejs/files/doord.py
roles/hap-nodejs/handlers/main.yaml
roles/hap-nodejs/meta/main.yaml
roles/hap-nodejs/tasks/main.yaml
roles/hap-nodejs/templates/hap-nodejs.service.j2
roles/nodejs/tasks/main.yaml
--- /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$
--- /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: "<github user>"
+    nodejs_version: "8.9.0"
+    nodejs_dir: "node-v{{nodejs_version}}-linux-{{ansible_architecture}}"
+    wpa_networks:
+      - ssid: "<your ssid>"
+        psk: "<your wifi password>"
--- /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/
--- /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
--- /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
--- /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"
--- /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
--- /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"
--- /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 %}
--- /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"
--- /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() {});
--- /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()
--- /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
--- /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
--- /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"
--- /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
--- /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"