mirror of
https://github.com/binary-kitchen/doorlockd
synced 2024-11-19 13:33:13 +01:00
Ralf Ramsauer
8b49c2f332
We had a bug: doorstate didn't reconnect to the broker if it temporarily disappeared. Fix this by using start_loop() (which starts a thread that observes the broker and automatically reconnects) instead of the synchronous loop() method. Thomas Basler <github.thomas@familie-basler.net> Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
193 lines
5.1 KiB
Python
Executable File
193 lines
5.1 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
"""
|
|
Doorlockd -- Binary Kitchen's smart door opener
|
|
|
|
Copyright (c) Binary Kitchen e.V., 2019
|
|
|
|
Author:
|
|
Ralf Ramsauer <ralf@binary-kitchen.de>
|
|
|
|
This work is licensed under the terms of the GNU GPL, version 2. See
|
|
the LICENSE file in the top-level directory.
|
|
|
|
This program is distributed in the hope that it will be useful, but WITHOUT
|
|
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
details.
|
|
"""
|
|
|
|
import gpiod
|
|
import os
|
|
import sys
|
|
|
|
from subprocess import Popen
|
|
from threading import Timer, Thread, Event
|
|
|
|
import paho.mqtt.client as mqtt
|
|
|
|
from pydoorlock.Config import Config, sounds_prefix
|
|
|
|
prog = sys.argv[0]
|
|
|
|
global is_locked
|
|
is_locked = None
|
|
|
|
class AudioPlayer(Thread):
|
|
def __init__(self, filename):
|
|
super(AudioPlayer, self).__init__()
|
|
self._filename = filename
|
|
self._event = Event()
|
|
self._shutdown = False
|
|
self._process = None
|
|
self._state = False
|
|
|
|
def set(self, state):
|
|
self._state = state
|
|
self._event.set()
|
|
|
|
def shutdown(self):
|
|
self._state = False
|
|
self._shutdown = True
|
|
self._event.set()
|
|
|
|
def run(self):
|
|
while not self._shutdown:
|
|
self._event.wait(1)
|
|
|
|
if self._event.is_set():
|
|
print('Came by event')
|
|
self._event.clear()
|
|
|
|
if self._process:
|
|
if self._process.poll() is not None:
|
|
print('Cleaning up...')
|
|
self._process.wait()
|
|
self._process = None
|
|
self._state = False
|
|
elif not self._state:
|
|
print('Killing...')
|
|
self._process.terminate()
|
|
self._process.wait()
|
|
self._process = None
|
|
self._state = False
|
|
else:
|
|
if self._state:
|
|
print('Starting...')
|
|
self._process = Popen(['mpg123', self._filename])
|
|
|
|
def mqtt_on_connect(client, userdata, flags, rc):
|
|
global mqtt_connected
|
|
|
|
if rc == 0:
|
|
print("MqTT: Connection returned result: " + mqtt.connack_string(rc))
|
|
client.subscribe(topic_alarm)
|
|
client.subscribe(topic_lockstate)
|
|
mqtt_connected = True
|
|
else:
|
|
print("MqTT: Bad connection Returned code=", rc)
|
|
|
|
def mqtt_on_disconnect(client, userdata, rc):
|
|
global mqtt_connected
|
|
|
|
if rc != 0:
|
|
print("Unexpected MQTT disconnection")
|
|
mqtt_connected = False
|
|
|
|
def mqtt_on_lockstate(client, userdata, message):
|
|
global is_locked
|
|
is_locked = message.payload.decode('utf-8') == 'locked'
|
|
print('is_locked: %s' % is_locked)
|
|
|
|
def mqtt_on_alarm(client, userdata, message):
|
|
alarm_set = message.payload.decode('utf-8') == '1'
|
|
print('MQTT: alarm: %u' % alarm_set)
|
|
player_alarm.set(alarm_set)
|
|
|
|
def publish_doorstate():
|
|
global door_open
|
|
|
|
message = 'open' if door_open else 'closed'
|
|
mqttc.publish(topic_doorstate, message, retain=True)
|
|
|
|
|
|
cfg = Config('dooralarm')
|
|
topic_alarm = cfg.str('TOPIC_ALARM')
|
|
topic_doorstate = cfg.str('TOPIC_DOORSTATE')
|
|
topic_lockstate = cfg.str('TOPIC_LOCKSTATE')
|
|
doorstate_alarm_timeout = int(cfg.str('DOORSTATE_ALARM_TIMEOUT'))
|
|
|
|
f_alarm = os.path.join(sounds_prefix, 'alarm.mp3')
|
|
f_door_call = os.path.join(sounds_prefix, 'door_call.mp3')
|
|
|
|
mqttc = mqtt.Client(client_id=prog)
|
|
mqttc.username_pw_set(cfg.str('MQTT_USERNAME'), cfg.str('MQTT_PASSWORD'))
|
|
mqttc.on_connect = mqtt_on_connect
|
|
mqttc.on_disconnect = mqtt_on_disconnect
|
|
mqttc.message_callback_add(topic_alarm, mqtt_on_alarm)
|
|
mqttc.message_callback_add(topic_lockstate, mqtt_on_lockstate)
|
|
mqttc.connect(cfg.str('MQTT_HOST'))
|
|
|
|
chip = gpiod.Chip(cfg.str('GPIO_CHIP'))
|
|
pin = cfg.int('GPIO_PIN')
|
|
|
|
line = chip.get_line(pin)
|
|
line.request(consumer=prog, type=gpiod.LINE_REQ_EV_BOTH_EDGES)
|
|
|
|
player_alarm = AudioPlayer(f_alarm)
|
|
player_alarm.start()
|
|
|
|
player_door_call = AudioPlayer(f_door_call)
|
|
player_door_call.start()
|
|
|
|
global door_open
|
|
door_open = line.get_value() == 1
|
|
|
|
door_alarm_set = False
|
|
|
|
global mqtt_connected
|
|
mqtt_connected = False
|
|
|
|
mqttc.loop_start()
|
|
|
|
while True:
|
|
# Synchronous loop: GPIO
|
|
ev_line = line.event_wait(sec=1)
|
|
if ev_line:
|
|
event = line.event_read()
|
|
door_open = event.type == gpiod.LineEvent.RISING_EDGE
|
|
print('door_open: %s' % door_open)
|
|
publish_doorstate()
|
|
|
|
if not mqtt_connected:
|
|
print('No MQTT connection!')
|
|
if door_alarm_set:
|
|
player_door_call.set(False)
|
|
door_alarm_set = False
|
|
|
|
player_alarm.set(False)
|
|
continue
|
|
|
|
# Door State stuff
|
|
if is_locked is not None:
|
|
if is_locked is False:
|
|
if door_alarm_set:
|
|
player_door_call.set(False)
|
|
door_alarm_set = False
|
|
elif door_open is True:
|
|
if not door_alarm_set:
|
|
door_alarm_set = True
|
|
player_door_call.set(True)
|
|
elif door_open is False:
|
|
if door_alarm_set:
|
|
player_door_call.set(False)
|
|
door_alarm_set = False
|
|
|
|
mqttc.loop_stop()
|
|
|
|
player_alarm.shutdown()
|
|
player_alarm.join()
|
|
|
|
player_door_call.shutdown()
|
|
player_door_call.join()
|