diff --git a/build-package b/build-package index b1ce608..95969eb 100755 --- a/build-package +++ b/build-package @@ -1,3 +1,3 @@ #!/bin/bash -VERSION=3.2.0 +VERSION=3.3.0 dpkg-deb --root-owner-group -b debian "foodoord_${VERSION}_all.deb" diff --git a/debian/DEBIAN/control b/debian/DEBIAN/control index cfac5f1..e7f64fd 100755 --- a/debian/DEBIAN/control +++ b/debian/DEBIAN/control @@ -1,6 +1,6 @@ Package: foodoord -Version: 3.2.0 -Maintainer: Bandie +Version: 3.3.0 +Maintainer: Bandie , Tobi Architecture: all Description: Control the doors of the club, ja! Depends: dash, git, python3, pip, tmux diff --git a/debian/DEBIAN/postinst b/debian/DEBIAN/postinst index 47017bf..b779922 100755 --- a/debian/DEBIAN/postinst +++ b/debian/DEBIAN/postinst @@ -27,7 +27,7 @@ done echo "##################" echo "Installing dependencies via pip: pifacecommon pifacedigitalio" -pip install pifacecommon pifacedigitalio +pip install pifacecommon pifacedigitalio paho-mqtt echo "Enabling and starting systemd-Services" systemctl daemon-reload diff --git a/debian/usr/sbin/foodoord_oben b/debian/usr/sbin/foodoord_oben index 01f7955..3ccdbcf 100755 --- a/debian/usr/sbin/foodoord_oben +++ b/debian/usr/sbin/foodoord_oben @@ -8,26 +8,49 @@ import signal import stat import subprocess import sys +import threading import time from configparser import ConfigParser from dataclasses import dataclass +import paho.mqtt.client as mqtt import pifacedigitalio -# Definitions for output -LED_RED = 6 -LED_GREEN = 7 -RELAYS_LOCK = 0 -RELAYS_UNLOCK = 1 -# Definitions for input -DOOR_BELL = 0 -REED_RELAYS = 1 # not implemented yet +class FoodoorMQTT: + def __init__(self, area): + self.area = area + self.client = mqtt.Client() + self.client.on_connect = self.on_connect + self.client.on_message = self.on_message + + self._connect_lock = threading.Condition() + + def connect(self): + try: + self.client.connect("mqtt.chaospott.de") + self.client.loop_start() + with self._connect_lock: + self._connect_lock.wait() + except Exception as e: + print(f"Verbindungsfehler zu MQTT-Server: {e}") + + def disconnect(self): + self.client.loop_stop() + + def on_connect(self, client, userdata, flags, rc): + with self._connect_lock: + self._connect_lock.notify() + + def on_message(self, client, userdata, msg): + print(f"MQTT-Server Message: {msg.topic} {msg.payload}") + + def send_state(self, locked: bool): + self.client.publish(f"foobar/{self.area}/foodoor/status", { + False: "open", + True: "closed", + }[locked], qos=0, retain=True) -# Definitions for LED color -RED = 1 -GREEN = 2 -ORANGE = 3 # Read config parser = ConfigParser() @@ -36,95 +59,108 @@ parser.read('/etc/foodoord.conf') @dataclass class API: + location: str api_url: str consumer_key: str consumer_secret: str + def update_state(self, locked): + subprocess.check_call([ + "/usr/bin/curl", "-XPOST", + "--header", "Content-Type: application/json", + "--data", + json.dumps({"consumer_key": self.consumer_key, "consumer_secret": self.consumer_secret, self.location: locked}), + self.api_url + ]) -APIv1 = API( + +APIv1 = API("aerie", parser.get('doorstatus', 'status_url'), parser.get('doorstatus', 'key'), parser.get('doorstatus', 'secret'), ) -APIv2 = API( +APIv2 = API("aerie", parser.get('doorstatusv2', 'status_url'), parser.get('doorstatusv2', 'key'), parser.get('doorstatusv2', 'secret'), ) -def update_api(locked): - try: - # API v1 - subprocess.check_call([ - "/usr/bin/curl", "-XPOST", - "--header", "Content-Type: application/json", - "--data", - json.dumps({"consumer_key": APIv1.consumer_key, "consumer_secret": APIv1.consumer_secret, "aerie": locked}), - APIv1.api_url - ]) - except: - pass - try: - # API v2 - subprocess.check_call([ - "/usr/bin/curl", "-XPOST", - "--header", "Content-Type: application/json", - "--data", - json.dumps({"consumer_key": APIv2.consumer_key, "consumer_secret": APIv2.consumer_secret, "aerie": locked}), - APIv2.api_url - ]) - except: - pass - - -def set_led(color): - if color == RED: - pifacedigital.leds[LED_RED].turn_on() - pifacedigital.leds[LED_GREEN].turn_off() - elif color == GREEN: - pifacedigital.leds[LED_GREEN].turn_on() - pifacedigital.leds[LED_RED].turn_off() - elif color == ORANGE: - pifacedigital.leds[LED_RED].turn_on() - pifacedigital.leds[LED_GREEN].turn_on() - - class Foodoord: + # Definitions for LED color + RED = 0b1 + GREEN = 0b10 + ORANGE = RED | GREEN + + # Definitions for output + LEDS = { + RED: 6, + GREEN: 7, + } + RELAYS_LOCK = 0 + RELAYS_UNLOCK = 1 + + # Definitions for input + DOOR_BELL = 0 + CLOSE_BUTTON = 1 + def __init__(self): self.status_open = False + self.mqtt = FoodoorMQTT("oben") self.listener = pifacedigitalio.InputEventListener() - self.listener.register(0, pifacedigitalio.IODIR_RISING_EDGE, self.doorbell, settle_time=10) - self.listener.register(1, pifacedigitalio.IODIR_RISING_EDGE, self.close_button, settle_time=5) + self.listener.register(self.DOOR_BELL, pifacedigitalio.IODIR_RISING_EDGE, self.doorbell, settle_time=10) + self.listener.register(self.CLOSE_BUTTON, pifacedigitalio.IODIR_RISING_EDGE, self.close_button, settle_time=5) def signal_handler(self, _signal, _frame): self.listener.deactivate() os.remove("/var/run/foodoord.pipe") - update_api(True) - set_led(RED) + self.update_api(True) + self.set_led(self.RED) sys.exit(0) + def update_api(self, locked): + try: + self.mqtt.send_state(locked) + except: + pass + try: + APIv1.update_state(locked) + except: + pass + try: + APIv2.update_state(locked) + except: + pass + + def set_led(self, color): + for led, gpio in self.LEDS.items(): + if color & led: + pifacedigital.leds[gpio].turn_on() + else: + pifacedigital.leds[gpio].turn_off() + def doorbell(self, event): if self.status_open: - pifacedigital.relays[RELAYS_UNLOCK].toggle() + pifacedigital.relays[self.RELAYS_UNLOCK].toggle() time.sleep(2) - pifacedigital.relays[RELAYS_UNLOCK].toggle() + pifacedigital.relays[self.RELAYS_UNLOCK].toggle() def close_button(self, _event): self.status_open = False - update_api(True) - set_led(RED) + self.update_api(True) + self.set_led(self.RED) def main(self): + self.mqtt.connect() self.listener.activate() pifacedigital = pifacedigitalio.PiFaceDigital() signal.signal(signal.SIGTERM, self.signal_handler) # Start settings - pifacedigital.leds[LED_RED].turn_on() + self.set_led(self.RED) # Setting up FiFo to get sshd-output try: @@ -140,23 +176,23 @@ class Foodoord: pipe_cmd = ssh_input.readline().strip() if pipe_cmd == "close" and self.status_open: - pifacedigital.relays[RELAYS_LOCK].toggle() + pifacedigital.relays[self.RELAYS_LOCK].toggle() time.sleep(1) - pifacedigital.relays[RELAYS_LOCK].toggle() + pifacedigital.relays[self.RELAYS_LOCK].toggle() self.status_open = False - update_api(True) - set_led(RED) + self.update_api(True) + self.set_led(self.RED) elif pipe_cmd == "open": - pifacedigital.relays[RELAYS_UNLOCK].toggle() + pifacedigital.relays[self.RELAYS_UNLOCK].toggle() time.sleep(2) - pifacedigital.relays[RELAYS_UNLOCK].toggle() + pifacedigital.relays[self.RELAYS_UNLOCK].toggle() if not self.status_open: - update_api(False) + self.update_api(False) self.status_open = True - set_led(GREEN) + self.set_led(self.GREEN) time.sleep(0.1) diff --git a/debian/usr/sbin/foodoord_unten b/debian/usr/sbin/foodoord_unten index 41ec018..24de04d 100755 --- a/debian/usr/sbin/foodoord_unten +++ b/debian/usr/sbin/foodoord_unten @@ -6,27 +6,53 @@ import json import os import stat import subprocess +import threading import time from configparser import ConfigParser from dataclasses import dataclass import RPi.GPIO as gpio +import paho.mqtt.client as mqtt + + +class FoodoorMQTT: + def __init__(self, area): + self.area = area + self.client = mqtt.Client() + self.client.on_connect = self.on_connect + self.client.on_message = self.on_message + + self._connect_lock = threading.Condition() + + def connect(self): + try: + self.client.connect("mqtt.chaospott.de") + self.client.loop_start() + with self._connect_lock: + self._connect_lock.wait() + except Exception as e: + print(f"Verbindungsfehler zu MQTT-Server: {e}") + + def disconnect(self): + self.client.loop_stop() + + def on_connect(self, client, userdata, flags, rc): + with self._connect_lock: + self._connect_lock.notify() + + def on_message(self, client, userdata, msg): + print(f"MQTT-Server Message: {msg.topic} {msg.payload}") + + def send_state(self, locked: bool): + self.client.publish(f"foobar/{self.area}/foodoor/status", { + False: "open", + True: "closed", + }[locked], qos=0, retain=True) + # Definitions for output -LED_RED = 6 -LED_GREEN = 7 -RELAYS_LOCK = 0 -RELAYS_UNLOCK = 1 PIN_OPEN = 24 PIN_CLOSE = 27 -# Definitions for input -DOOR_BELL = 0 -REED_RELAYS = 1 # not implemented yet - -# Definitions for LED color -RED = 1 -GREEN = 2 -ORANGE = 3 # Read config parser = ConfigParser() @@ -35,17 +61,28 @@ parser.read('/etc/foodoord.conf') @dataclass class API: + location: str api_url: str consumer_key: str consumer_secret: str + def update_state(self, locked): + subprocess.check_call([ + "/usr/bin/curl", "-XPOST", + "--header", "Content-Type: application/json", + "--data", + json.dumps({"consumer_key": self.consumer_key, "consumer_secret": self.consumer_secret, self.location: locked}), + self.api_url + ]) -APIv1 = API( + +MQTT = FoodoorMQTT("unten") +APIv1 = API("cellar", parser.get('doorstatus', 'status_url'), parser.get('doorstatus', 'key'), parser.get('doorstatus', 'secret'), ) -APIv2 = API( +APIv2 = API("cellar", parser.get('doorstatusv2', 'status_url'), parser.get('doorstatusv2', 'key'), parser.get('doorstatusv2', 'secret'), @@ -54,33 +91,23 @@ APIv2 = API( def write_state(state): try: - with open("/tmp/door_state", "w") as handle: - handle.write(state) + with open("/tmp/door_state", "w") as f: + f.write(state) except: pass def update_api(locked): try: - # API v1 - subprocess.check_call([ - "/usr/bin/curl", "-XPOST", - "--header", "Content-Type: application/json", - "--data", - json.dumps({"consumer_key": APIv1.consumer_key, "consumer_secret": APIv1.consumer_secret, "cellar": locked}), - APIv1.api_url - ]) + MQTT.send_state(locked) except: pass try: - # API v2 - subprocess.check_call([ - "/usr/bin/curl", "-XPOST", - "--header", "Content-Type: application/json", - "--data", - json.dumps({"consumer_key": APIv2.consumer_key, "consumer_secret": APIv2.consumer_secret, "cellar": locked}), - APIv2.api_url - ]) + APIv1.update_state(locked) + except: + pass + try: + APIv2.update_state(locked) except: pass @@ -99,6 +126,8 @@ def main(): except OSError: pass + MQTT.connect() + ssh_input = open("/var/run/foodoord.pipe", "r") while True: # Read sshd output from pipe