Add spacestate support

This commit is contained in:
Jeroen Stroeve 2024-08-24 15:23:55 +02:00
parent fd0e6bfcc0
commit 02be0a1921
4 changed files with 198 additions and 65 deletions

2
pi-config/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
toegang
config/settings

View file

@ -4,6 +4,9 @@ dooropen.subject=bitlair/doorduino/dooropen
lockstate.subject=bitlair/doorduino/lockstate lockstate.subject=bitlair/doorduino/lockstate
server=mqtt.bitlair.nl server=mqtt.bitlair.nl
mqtt-simple=/usr/local/bin/mqtt-simple mqtt-simple=/usr/local/bin/mqtt-simple
client-id=doorpi
state-bitlair.subject=bitlair/state
state-djo.subject=bitlair/state/djo
[serial] [serial]
device=/dev/ttyS2 device=/dev/ttyS2

View file

@ -16,6 +16,84 @@ import configparser
import re import re
import threading import threading
import syslog import syslog
import csv
import logging
from paho.mqtt import client as mqtt_client
def connect_mqtt(config, broker, port, client_id):
# def on_connect(client, userdata, flags, rc):
# For paho-mqtt 2.0.0, you need to add the properties parameter.
def on_connect(client, userdata, flags, rc, properties):
if rc == 0:
print("Connected to MQTT Broker!")
subscribe(client, config.get('mqtt', 'state-bitlair.subject'))
subscribe(client, config.get('mqtt', 'state-djo.subject'))
else:
print("Failed to connect, return code %d\n", rc)
# Set Connecting Client ID
# For paho-mqtt 2.0.0, you need to set callback_api_version.
client = mqtt_client.Client(client_id=client_id, callback_api_version=mqtt_client.CallbackAPIVersion.VERSION2)
# client.username_pw_set(username, password)
client.on_connect = on_connect
client.connect(broker, port)
return client
FIRST_RECONNECT_DELAY = 1
RECONNECT_RATE = 2
MAX_RECONNECT_COUNT = 12
MAX_RECONNECT_DELAY = 60
def on_disconnect(client, userdata, rc):
logging.info("Disconnected with result code: %s", rc)
reconnect_count, reconnect_delay = 0, FIRST_RECONNECT_DELAY
while reconnect_count < MAX_RECONNECT_COUNT:
logging.info("Reconnecting in %d seconds...", reconnect_delay)
time.sleep(reconnect_delay)
try:
client.reconnect()
logging.info("Reconnected successfully!")
return
except Exception as err:
logging.error("%s. Reconnect failed. Retrying...", err)
reconnect_delay *= RECONNECT_RATE
reconnect_delay = min(reconnect_delay, MAX_RECONNECT_DELAY)
reconnect_count += 1
logging.info("Reconnect failed after %s attempts. Exiting...", reconnect_count)
states = {}
def update_spacestate():
global ser
open = False
for key in states:
if states[key] == "open":
open = True
try:
if open:
print("Send spacestate open")
ser.write(b"\n")
ser.write(b"spacestate open\n");
else:
print("Send spacestate closed")
ser.write(b"\n")
ser.write(b"spacestate closed\n")
except serial.SerialException:
print("Serial connection error")
time.sleep(2)
def subscribe(client: mqtt_client, topic):
def on_message(client, userdata, msg):
states[msg.topic] = msg.payload.decode()
print(f"Received `{msg.payload.decode()}` from `{msg.topic}` topic")
update_spacestate()
client.subscribe(topic)
client.on_message = on_message
def read_configuration(configdir): def read_configuration(configdir):
if (configdir == ''): if (configdir == ''):
@ -32,7 +110,7 @@ def read_configuration(configdir):
sys.exit(2) sys.exit(2)
config = configparser.ConfigParser() config = configparser.ConfigParser()
config.readfp(configfile.open()) config.read_file(configfile.open())
expected_config_options = { 'mqtt': [ 'doorbell.subject', 'dooropen.subject', 'lockstate.subject', 'server', 'mqtt-simple' ], expected_config_options = { 'mqtt': [ 'doorbell.subject', 'dooropen.subject', 'lockstate.subject', 'server', 'mqtt-simple' ],
'serial': [ 'device' ] } 'serial': [ 'device' ] }
@ -45,46 +123,36 @@ def read_configuration(configdir):
return config return config
def mqtt_thread(config, subject, value, persistent): def mqtt_send_thread(config, subject, value, persistent):
if persistent: if persistent:
subprocess.call([config.get('mqtt', 'mqtt-simple'), "-h", config.get('mqtt', 'server'), "-r", "-p", subject, "-m", value]) subprocess.call([config.get('mqtt', 'mqtt-simple'), "-h", config.get('mqtt', 'server'), "-r", "-p", subject, "-m", value])
else: else:
subprocess.call([config.get('mqtt', 'mqtt-simple'), "-h", config.get('mqtt', 'server'), "-p", subject, "-m", value]) subprocess.call([config.get('mqtt', 'mqtt-simple'), "-h", config.get('mqtt', 'server'), "-p", subject, "-m", value])
def mqtt(config, subject, value, persistent=False): def mqtt(config, subject, value, persistent=False):
threading.Thread(target = mqtt_thread, args = (config, subject, value, persistent)).start() threading.Thread(target = mqtt_send_thread, args = (config, subject, value, persistent)).start()
def log(message): def log(message):
print("LOG " + message) print("LOG " + message)
syslog.syslog(message) syslog.syslog(message)
def main(argv): def mqtt_thread():
global users global client
global config
client = connect_mqtt(config, config.get('mqtt', 'server'), 1883, config.get('mqtt', 'client-id'))
configdir = '' client.loop_forever()
syslog.openlog('doorduino')
try:
opts, args = getopt.getopt(argv,"c:",["config="])
except getopt.GetoptError:
print('doorduino.py -c <configdir>')
sys.exit(2)
for opt, arg in opts:
if opt == "-c" or opt == "--config":
configdir = arg
config = read_configuration(configdir)
buttons = []
def serial_monitor_thread():
global ser
global config
global buttons
while True: while True:
try: try:
#cdc = next(list_ports.grep(config.get('serial', 'device'))) ser = serial.Serial(config.get('serial', 'device'), 115200, rtscts=False, dsrdtr=True)
#ser = serial.Serial(cdc[0])
ser = serial.Serial(config.get('serial', 'device'), 115200)
time.sleep(2); time.sleep(2);
# ser.write(b"R\n");
print("Doorduino started"); print("Doorduino started");
while True: while True:
@ -115,47 +183,14 @@ def main(argv):
print("lock closed") print("lock closed")
log("lock closed") log("lock closed")
mqtt(config, config.get('mqtt','lockstate.subject'), 'closed', True) mqtt(config, config.get('mqtt','lockstate.subject'), 'closed', True)
elif action[:7] == "button:":
# if action == 'B': # print("got button ")
# print("Arduino ready") # print(action[8:])
# ser.write(b"R\n") if not action[8:] in buttons:
# elif action == 'A': buttons.append(action[8:])
# print("Authenticating", value) elif action == "DEBUG: Board started":
# try: print("Arduino was reset, sending spacestate")
# if users[value]: update_spacestate()
# ser.write(b"U");
# if users[value]['maintenance']:
# ser.write(b'T')
# else:
# ser.write(b'F')
#
# ser.write(users[value]['price'].encode('utf-8'))
# ser.write(users[value]['revbank'].encode('utf-8'))
# ser.write(b"\n")
#
# log("Laser unlock by " + users[value]['revbank'] + " (" + value + ")")
#
# active_user = users[value]
# except KeyError:
# log("Laser unlock attempt by unknown iButton " + value)
# ser.write(b"FNiet gevonden\n");
# threading.Thread(target = git_update, args = (config.get('git', 'git'), configdir)).start()
#
# elif action == 'W':
# mqtt(config, config.get('mqtt','water.subject'), value, False);
# elif action == 'T':
# mqtt(config, config.get('mqtt', 'temperature.subject'), value, False);
# elif action == 'S':
# log("Laser job started for " + active_user['revbank'])
# mqtt(config, config.get('mqtt', 'laseractive.subject'), '1', True)
# elif action == 'E':
# log("Laser job finished for " + active_user['revbank'] + " in " + value + "ms")
# mqtt(config, config.get('mqtt', 'laseractive.subject'), '0', True)
# payment(config, active_user, float(value))
# elif action == 'M':
# log("Maintenance state entered by " + active_user['revbank'])
# elif action == 'L':
# log("Left maintenance state by " + active_user['revbank'])
except serial.SerialException: except serial.SerialException:
print("Serial connection error") print("Serial connection error")
@ -164,6 +199,97 @@ def main(argv):
print("No device found") print("No device found")
time.sleep(2) time.sleep(2)
def git_update(git_binary, git_dir):
log("Updating git")
subprocess.call([git_binary, "-C", git_dir, "pull"])
def git_thread():
global ser
global buttons
print("GIT init")
git_update("git", "toegang")
git_buttons = {}
with open('toegang/toegang.csv', newline='') as csvfile:
data = csvfile.read()
data = data.replace(' ', '')
reader = csv.DictReader(data.splitlines(), delimiter=',')
for row in reader:
ibutton = row['ibutton'].split(':')
git_buttons[ibutton[0].lower()] = ibutton[1].lower()
# print(row['naam'] + " " + row['ibutton'])
if len(git_buttons) < 25:
print("Something wrong, not enough buttons in git")
return
buttons = []
ser.write(b"\n")
ser.write(b"list_buttons\n");
time.sleep(10)
if len(buttons) < 5:
print("Something wrong, not enough buttons in doorduino")
return
print(buttons)
for button in git_buttons:
if button not in buttons:
print("should add " + button)
ser.write(b"\n")
ser.write(b"add_button "+button.encode('ascii')+b" "+git_buttons[button].encode('ascii')+b"\n")
time.sleep(2)
# else:
# print("already there " + button)
for button in buttons:
if button not in git_buttons:
print("should remove " + button)
ser.write(b"\n")
ser.write(b"remove_button "+button.encode('ascii')+b"\n")
time.sleep(2)
# else:
# print("should be there " + button)
def threadwrap(threadfunc):
def wrapper():
while True:
try:
threadfunc()
except BaseException as e:
print('{!r}; restarting thread'.format(e))
else:
print('exited normally, bad thread; restarting')
return wrapper
def main(argv):
global config
configdir = ''
syslog.openlog('doorduino')
try:
opts, args = getopt.getopt(argv,"c:",["config="])
except getopt.GetoptError:
print('doorduino.py -c <configdir>')
sys.exit(2)
for opt, arg in opts:
if opt == "-c" or opt == "--config":
configdir = arg
config = read_configuration(configdir)
threading.Thread(target = threadwrap(serial_monitor_thread)).start()
time.sleep(5) # Give doorduino time to start before sending spacestate data
threading.Thread(target = threadwrap(mqtt_thread)).start()
# threading.Thread(target = threadwrap(git_thread)).start()
git_thread()
if __name__ == "__main__": if __name__ == "__main__":
main(sys.argv[1:]) main(sys.argv[1:])

View file

@ -0,0 +1,2 @@
paho-mqtt
paramiko