diff --git a/.gitmodules b/.gitmodules index e1d58ff..9324a37 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "buildroot"] path = buildroot url = https://github.com/buildroot/buildroot.git +[submodule "door_control"] + path = door_control + url = ssh://irgit@imaginaerraum.de:2201/apo/DoorControl.git diff --git a/br_external/Config.in b/br_external/Config.in new file mode 100644 index 0000000..6cbca1d --- /dev/null +++ b/br_external/Config.in @@ -0,0 +1,2 @@ +source "$BR2_EXTERNAL_DOOR_PI_PATH/package/poll_desfire/Config.in" +source "$BR2_EXTERNAL_DOOR_PI_PATH/package/libfreefare_git/Config.in" diff --git a/br_external/external.desc b/br_external/external.desc new file mode 100644 index 0000000..f356f48 --- /dev/null +++ b/br_external/external.desc @@ -0,0 +1 @@ +name: DOOR_PI diff --git a/br_external/external.mk b/br_external/external.mk new file mode 100644 index 0000000..76ba2a5 --- /dev/null +++ b/br_external/external.mk @@ -0,0 +1 @@ +include $(sort $(wildcard $(BR2_EXTERNAL_DOOR_PI_PATH)/package/*/*.mk)) diff --git a/br_external/package/libfreefare_git/Config.in b/br_external/package/libfreefare_git/Config.in new file mode 100644 index 0000000..e765dd7 --- /dev/null +++ b/br_external/package/libfreefare_git/Config.in @@ -0,0 +1,12 @@ +config BR2_PACKAGE_LIBFREEFARE_GIT + bool "libfreefare (git)" + depends on BR2_TOOLCHAIN_HAS_THREADS # libusb + select BR2_PACKAGE_OPENSSL + select BR2_PACKAGE_LIBNFC + help + Library for high level manipulation of MIFARE cards. + + http://nfc-tools.org/index.php?title=Libfreefare + +comment "libfreefare needs a toolchain w/ threads" + depends on !BR2_TOOLCHAIN_HAS_THREADS diff --git a/br_external/package/libfreefare_git/libfreefare_git.mk b/br_external/package/libfreefare_git/libfreefare_git.mk new file mode 100644 index 0000000..02c7e3a --- /dev/null +++ b/br_external/package/libfreefare_git/libfreefare_git.mk @@ -0,0 +1,18 @@ +################################################################################ +# +# libfreefare +# +################################################################################ + +LIBFREEFARE_GIT_VERSION = e95406c +# Do not use the github helper here, the generated tarball is *NOT* +# the same as the one uploaded by upstream for the release. +LIBFREEFARE_GIT_SITE = $(call github,nfc-tools,libfreefare,$(LIBFREEFARE_GIT_VERSION)) +LIBFREEFARE_GIT_DEPENDENCIES = host-pkgconf libnfc openssl +LIBFREEFARE_GIT_INSTALL_STAGING = YES +LIBFREEFARE_GIT_LICENSE = LGPL-3.0+ with exception +LIBFREEFARE_GIT_LICENSE_FILES = COPYING +LIBFREEFARE_GIT_CONF_ENV += LIBS=`$(PKG_CONFIG_HOST_BINARY) --libs openssl` +LIBFREEFARE_GIT_AUTORECONF = YES + +$(eval $(autotools-package)) diff --git a/br_external/package/poll_desfire/Config.in b/br_external/package/poll_desfire/Config.in new file mode 100644 index 0000000..53c065c --- /dev/null +++ b/br_external/package/poll_desfire/Config.in @@ -0,0 +1,7 @@ +config BR2_PACKAGE_POLL_DESFIRE + bool "poll_desfire" + select BR2_PACKAGE_LIBFREEFARE_GIT + help + poll desfire cards + + https://git.imaginaerraum.de/apo/PollDesfire diff --git a/br_external/package/poll_desfire/poll_desfire.mk b/br_external/package/poll_desfire/poll_desfire.mk new file mode 100644 index 0000000..d73463c --- /dev/null +++ b/br_external/package/poll_desfire/poll_desfire.mk @@ -0,0 +1,19 @@ +POLL_DESFIRE_VERSION = 0.0.1 +POLL_DESFIRE_SOURCE = poll_desfire_v$(POLL_DESFIRE_VERSION).tar.gz +POLL_DESFIRE_SITE = https://git.imaginaerraum.de/apo/PollDesfire/archive +POLL_DESFIRE_DEPENDENCIES = libfreefare_git host-pkgconf +POLL_DESFIRE_LICENSE = GPL-3.0 + +define POLL_DESFIRE_BUILD_CMDS +$(MAKE) $(TARGET_CONFIGURE_OPTS) -C $(@D) all +endef + +define POLL_DESFIRE_INSTALL_STAGING_CMDS + $(MAKE) $(TARGET_CONFIGURE_OPTS) -C $(@D) PREFIX=$(STAGING_DIR) install +endef + +define POLL_DESFIRE_INSTALL_TARGET_CMDS + $(MAKE) $(TARGET_CONFIGURE_OPTS) -C $(@D) PREFIX=$(TARGET_DIR) install +endef + +$(eval $(generic-package)) diff --git a/door_control b/door_control new file mode 160000 index 0000000..e48f0f6 --- /dev/null +++ b/door_control @@ -0,0 +1 @@ +Subproject commit e48f0f63385f5990440524c66ba925466f8e292f diff --git a/root_overlay/bin/poll_desfire b/root_overlay/bin/poll_desfire deleted file mode 100755 index f199e2d..0000000 Binary files a/root_overlay/bin/poll_desfire and /dev/null differ diff --git a/root_overlay/bin/test.py b/root_overlay/bin/test.py deleted file mode 100644 index 9557c91..0000000 --- a/root_overlay/bin/test.py +++ /dev/null @@ -1,340 +0,0 @@ -#!/usr/bin/python -import os, serial, socket, subprocess, select, datetime, sys -import paho.mqtt.client as mqcl -import argparse - -OPEN_THRESHOLD = 80 -CLOSED_THRESHOLD = 100 -CLOSED_WANT = 160 - -parser = argparse.ArgumentParser() -parser.add_argument("--serial_port", default="/dev/serial/by-id/usb-Imaginaerraum.de_DoorControl_43363220195053573A002C0-if01") -parser.add_argument("--nfc_fifo", default="/tmp/nfc_fifo") -parser.add_argument("--control_socket", default="/tmp/nfc.sock") -parser.add_argument("--valid_tokens", default="/etc/door_tokens") -parser.add_argument("--log_file", default="/tmp/nfc.log") -parser.add_argument("--state_timeout", type=float, default=10) -parser.add_argument("--repeat_time", type=float, default=5) -parser.add_argument("--mqtt_host", default="10.10.21.2") - -config = parser.parse_args() - -mqttc = mqcl.Client() - -def timestamp(): - return datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") - -# Log data to stdout and the log file -def log(*args): - data = "%s %s" % (timestamp(), " ".join(str(i) for i in args)) - lgf.write(data + "\n") - lgf.flush() - print(data) - -def mqtt(topic, msg, persistent = True): - mqttc.publish("door/" + topic, msg, qos=2, retain=persistent) - -# Opens the socket that can control the daemon -# Commands are TBD -def open_control_socket(): - global control_socket - global config - global comm_channels - - if os.path.exists(config.control_socket): - os.unlink(config.control_socket) - - control_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - control_socket.bind(config.control_socket) - control_socket.listen(5) - comm_channels = [] - -# Opens the serial port to talk to the lock actuator -# Might return a failure. In that case, it will be tried again later -def open_serial_port(): - global serial_port - global config - - try: - serial_port = serial.Serial(config.serial_port, timeout=2) - except: - serial_port = None - pass - -# Detected tokens are passed in through a FIFO -def open_nfc_fifo(): - global nfc_fifo - global config - - nfc_fifo = open(config.nfc_fifo, "r") - -# Refresh valid tokens from the configured file -def read_valid_tokens(): - global valid - global config - - try: - log("Loading tokens") - valid = {} - lines =[ s.strip() for s in open(config.valid_tokens, "r").readlines() ] - for l in lines: - l = l.split(' ', 1) - if len(l) > 1: - if l[0] in valid: - log("Warning: Overwriting token %s" % (l[0],)) - log("Got token: %s (%s)" % (l[0], l[1])) - valid[l[0]] = l[1] - else: - log("Got unnamed token: %s" % (l[0],)) - valid[l[0]] = None - except: - valid = {} - log("Error reading token file") - raise - -# Opens the log file for writing -def open_logfile(): - global lgf - global config - - lgf = open(config.log_file, "a+") - -# Actions -IDLE, CLOSE, OPEN_THEN_CLOSE, OPEN, CLOSE_THEN_OPEN = range(5) -state_names = { - OPEN: "open", - CLOSE: "close" -} -action_names = { - IDLE: "idling", - OPEN: "waiting for open door", - CLOSE: "waiting for closed door", - OPEN_THEN_CLOSE: "waiting for open door, then closing again", - CLOSE_THEN_OPEN: "waiting for closed door, then opening again" -} -# Current door state -state = None -state_pos = 0 -# Current target action -action = CLOSE -# Start time of the current action -start_time = None -# How often the action was repeated -repeats = 0 - -# Read the state of the door from the serial port -def poll_door_state(): - global state - global state_pos - global serial_port - serial_port.reset_input_buffer() - serial_port.write(b"R") - data = serial_port.readline().strip().split() - if data[0] == b'pos:': - data = int(data[1]) - if state_pos != data: - mqtt("position/value", data, True) - state_pos = data - changed = False - if data < OPEN_THRESHOLD and state != OPEN: - state = OPEN - changed = True - mqtt("state/value", "open", True) - elif data > CLOSED_THRESHOLD and state != CLOSE: - state = CLOSE - changed = True - mqtt("state/value", "closed", True) - return state - -# Check the door state and send commands through a state machine -def handle_door_state(): - global action, target, serial_port, start_time, repeats - - # Commands associated with each target state - target_state_cmd = { - OPEN: b'O', - CLOSE: b'C' - } - - old_state = state - - # If no serial port, try to open it or return - if not serial_port: - try: - open_serial_port() - except: - return - - # Get new state - poll_door_state() - - # Idle + change = key? - if action == IDLE: - if state != old_state: - log("Door changed unexpectedly:", state_names[state]) - start_time = datetime.datetime.now() - if start_time and (datetime.datetime.now() - start_time).total_seconds() >= config.state_timeout: - start_time = None - if state_pos >= CLOSED_THRESHOLD and state_pos < CLOSED_WANT: - log("Closing door a bit more") - serial_port.write(target_state_cmd[CLOSE]) - return - - # Target state, next action, timeout action - actions = { - OPEN: (OPEN, IDLE, CLOSE_THEN_OPEN ), - CLOSE: (CLOSE, IDLE, OPEN_THEN_CLOSE ), - OPEN_THEN_CLOSE: (OPEN, CLOSE, CLOSE ), - CLOSE_THEN_OPEN: (CLOSE, OPEN, OPEN ) - } - - if start_time == None: - start_time = datetime.datetime.now() - serial_port.write(target_state_cmd[actions[action][0]]) - - # Target state reached - if state == actions[action][0]: - # Select next action, reset start time and repetitions - action = actions[action][1] - start_time = datetime.datetime.now() - repeats = 0 - # On idle, we're done - if action == IDLE: - log("Reached target position:", state_names[state]) - return - - # Execution time - t = (datetime.datetime.now() - start_time).total_seconds() - if t >= config.state_timeout: - # Timeout -> switch to timeout action - action = actions[action][2] - start_time = None - repeats = 0 - log("Timeout. Switching to", action_names[action]) - elif t >= (1 + repeats) * config.repeat_time: - # Repeat every couple of seconds - repeats += 1 - serial_port.write(target_state_cmd[actions[action][0]]) - log("Repeating command:", target_state_cmd[actions[action][0]]) - -def open_door(): - global action - global start_time - log("Opening the door") - action = OPEN - start_time = None - mqtt("state/target", "open", True) - handle_door_state() - -def close_door(): - global action - global start_time - log("Closing the door") - action = CLOSE - start_time = None - mqtt("state/target", "closed", True) - handle_door_state() - -def toggle_door_state(): - global state, action - if state == CLOSE: - open_door() - else: - close_door() - -def handle_nfc_token(token = None): - global valid - - if not token: - token = nfc_fifo.readline() - if token == "": - open_nfc_fifo() - - token = token.strip() - if token in valid: - name = valid[token] - if name: - name = "%s (%s)" % (token, name) - else: - name = token - log("Valid token: %s" % name) - toggle_door_state() - else: - log("Invalid token:", token) - mqtt("token/last_invalid", "%s;%s" % (timestamp(), token)) - -class LineBuffer(object): - def __init__(self, f, handler): - self.data = b'' - self.f = f - self.handler = handler - - def update(self): - data = self.f.recv(1024) - if not data: - return False - self.data += data - d = self.data.split(b'\n') - d, self.data = d[:-1], d[-1] - for i in d: - self.handler(self.f, i) - return True - -def handle_cmd(comm, data): - cmd = data.decode('utf8').split() - cmd, args = cmd[0], cmd[1:] - log("Got command:", data) - - send = lambda x: comm.send(x.encode('utf8')) - - if cmd == 'fake': - log("Faking token", args[0]) - send("Handling token\n") - handle_nfc_token(args[0]) - elif cmd == 'open': - log("Control socket opening door") - send("Opening door") - open_door() - elif cmd == 'close': - log("Control socket closing door") - send("Closing door") - close_door() - elif cmd == 'rld': - log("Reloading tokens") - send("Reloading tokens") - read_valid_tokens() - elif cmd == 'stat': - send("Door status is %s, position is %d. Current action: %s (%g seconds ago)\n" % ( - state_names.get(state, "None"), - state_pos, - action_names[action], - (datetime.datetime.now() - start_time).total_seconds())) - -open_logfile() -read_valid_tokens() -open_nfc_fifo() -open_serial_port() -open_control_socket() - -mqttc.on_connect = lambda client, userdata, flags, rc: log("Connected to mqtt host with result %s" % (str(rc), )) -mqttc.connect_async(config.mqtt_host, keepalive = 60) -mqttc.loop_start() - -buffers = {} -while True: - readable = select.select([ nfc_fifo, control_socket ] + comm_channels, [], [], 1)[0] - - for c in readable: - if c == nfc_fifo: - handle_nfc_token() - elif c == control_socket: - log("Got connection") - sock = control_socket.accept()[0] - buffers[sock] = LineBuffer(sock, handle_cmd) - comm_channels += [sock] - else: - if not buffers[c].update(): - log("Lost connection") - del buffers[c] - comm_channels.remove(c) - handle_door_state()