Compare commits
217 Commits
Author | SHA1 | Date | |
---|---|---|---|
e50fe08267 | |||
ae989da661 | |||
96fc3ba973 | |||
1c403437b3 | |||
18264b0449 | |||
b086696879 | |||
5419d4587a | |||
15ff2c0293 | |||
ebb0ea0527 | |||
94ee9de2ee | |||
aa5999203c | |||
1f27ebafdb | |||
b7000388bc | |||
ed239fb284 | |||
f1c267dc94 | |||
8c0ba51e9b | |||
58e93f4e15 | |||
8b49c2f332 | |||
71e55ec523 | |||
c8503f46e8 | |||
7500a16fc1 | |||
07a0ac5307 | |||
8bc75bc0f6 | |||
9828534eae | |||
1e5f7c3ec4 | |||
6fc923a371 | |||
caab43f4d3 | |||
95b6550e49 | |||
a774262a3c | |||
bdfcf29075 | |||
263fc0c687 | |||
e27f6d02da | |||
becb21bbbc | |||
6725555af6 | |||
b5154faecd | |||
e69c314f97 | |||
0511fac330 | |||
5645ba22c9 | |||
209ccb3ba3 | |||
f264fe803b | |||
a942d2a71c | |||
eaf2537d83 | |||
90ea795c1b | |||
af647c0ae4 | |||
11998b9f8b | |||
b3715dc42f | |||
95e578baf6 | |||
2ffa0e1b67 | |||
bc23a8c122 | |||
4a0c096331 | |||
f294617e7f | |||
4609fa52f5 | |||
92b791a835 | |||
9e94d858ec | |||
d6d90e9f70 | |||
aa3369baa8 | |||
bb65e38640 | |||
ae358d403c | |||
00f74d69f9 | |||
8d37886ee1 | |||
5b296b777d | |||
2cdf9d1b27 | |||
badeb1945b | |||
faecb6b98f | |||
aaaad9b6ef | |||
d1855bf77d | |||
55205ad247 | |||
7e26fa0cd6 | |||
147c4cff0c | |||
69cbf48bae | |||
8b49549876 | |||
b9a1bfc566 | |||
197397027e | |||
128cc2ced2 | |||
82fd80b02d | |||
305f89b70b | |||
b9dae15799 | |||
be33bb71ce | |||
6a5987e7cb | |||
3b2544d892 | |||
8623d3a78a | |||
6370e54ff8 | |||
2aea44496f | |||
f5603272d2 | |||
45724f37c8 | |||
f6eabcd8cf | |||
0bf9679eef | |||
3dc71db095 | |||
0b91b84180 | |||
7f84e8e1a0 | |||
e8717c9e9f | |||
1a331ac4fb | |||
5d60f1fc45 | |||
a20c0a43b5 | |||
b7ba8ebe18 | |||
2ee9457908 | |||
3df6515808 | |||
e0fd93a700 | |||
ce3d4911ec | |||
c70bd29b61 | |||
9b47b260f3 | |||
4c7886dc61 | |||
03648bf821 | |||
f857fffac6 | |||
f226f44190 | |||
f7725bba54 | |||
4f95021fcf | |||
d5215ea0fc | |||
e3491aaa45 | |||
d5a41a596d | |||
9f3062340c | |||
95c35e7c06 | |||
179180dc4e | |||
60633bbc49 | |||
e5fb445287 | |||
c2c6e00651 | |||
3e8d8a98e3 | |||
31afb91f8d | |||
da08f768a1 | |||
9567ca938f | |||
5fd19def77 | |||
698a96e46b | |||
29d722453d | |||
25595f18f2 | |||
0f6c837eb8 | |||
2307df2a0b | |||
174c498b22 | |||
8face4bccc | |||
4f034def73 | |||
23166516f1 | |||
6977c46c5c | |||
20c86eb659 | |||
9a29f02449 | |||
bd63a57d31 | |||
af7b71304b | |||
201cc2ef9b | |||
d52de425ca | |||
e56622128e | |||
201411a65f | |||
|
8f0589cbf2 | ||
|
8058fda326 | ||
|
84d61c04db | ||
|
6a018b7290 | ||
0ae60848cf | |||
04ef5219d3 | |||
47adac2e0f | |||
71bc36a557 | |||
dc724b4abb | |||
b0f4e82914 | |||
010563dab4 | |||
8f8db8950b | |||
f72f731b59 | |||
d82272f53a | |||
c0e89c1be9 | |||
910ec8ba5b | |||
4d1682d1bd | |||
6e2655e6e5 | |||
82d09ddede | |||
96de37d27d | |||
153f066ef7 | |||
be0ab6b58a | |||
d17f5c964a | |||
90528bb7d9 | |||
6961821c04 | |||
79599e2a45 | |||
8cdf528032 | |||
67879a659d | |||
bccd9432af | |||
9cf149c12f | |||
56ac823852 | |||
a6e117ffd9 | |||
93a34b7755 | |||
78a0cc300f | |||
1268e28a5d | |||
84e9440ed6 | |||
3e8fce5b81 | |||
5fe83e59f9 | |||
a21beefe74 | |||
ae0d4f5aa2 | |||
746b68eaf1 | |||
1b59b273d8 | |||
ef198b74c0 | |||
1c13e58f02 | |||
8bf57eaf54 | |||
eeafa6350f | |||
|
020ac1b38b | ||
|
9ae9304551 | ||
|
d04ba6ef63 | ||
|
d1d0d5b160 | ||
|
4ccf8e4f24 | ||
|
48d0fcaf6f | ||
|
42f168ff19 | ||
|
ee481009e1 | ||
|
951e1de987 | ||
|
c058e1e4d0 | ||
|
7e522e59af | ||
|
573d9e7c65 | ||
|
f713ef6124 | ||
|
3721259f3e | ||
|
660a7297ee | ||
|
8503a2114f | ||
|
62ac4f26e0 | ||
|
474d941918 | ||
|
5355749c6b | ||
|
367afb4092 | ||
|
34417b8ddb | ||
|
f5c67445e9 | ||
|
40d871b2d2 | ||
|
99feb14036 | ||
|
88926613e7 | ||
|
b15e7960cb | ||
|
61c5ce5e5e | ||
|
9a38ace795 | ||
|
eb19aaa47f | ||
|
76d0491518 | ||
c4a78e48e5 | |||
|
9c549d1d27 |
12
.gitignore
vendored
|
@ -1 +1,11 @@
|
|||
CMakeLists.txt.user
|
||||
*.hex
|
||||
*.elf
|
||||
*.o
|
||||
arch/pkg
|
||||
arch/src
|
||||
arch/doorlockd
|
||||
arch/*.xz
|
||||
arch/BKCA.crt
|
||||
|
||||
pydoorlock/Protocol.py
|
||||
pydoorlock/__pycache__/
|
||||
|
|
38
Makefile
Normal file
|
@ -0,0 +1,38 @@
|
|||
DESTDIR ?= /
|
||||
PREFIX ?= /usr
|
||||
SYSCONFDIR ?= /etc
|
||||
|
||||
USR = $(DESTDIR)/$(PREFIX)
|
||||
BIN = $(USR)/bin/
|
||||
ETC = $(DESTDIR)/etc/
|
||||
SHARE = $(USR)/share/
|
||||
SYSTEMD_UNITS = $(ETC)/systemd/system/
|
||||
|
||||
all: pydoorlock/Protocol.py
|
||||
|
||||
package:
|
||||
sed -i -r -e "s@(^SYSCONFDIR = ').*('$$)@\1$(SYSCONFDIR)\2@" pydoorlock/Config.py
|
||||
sed -i -r -e "s@(^PREFIX = ').*('$$)@\1$(PREFIX)\2@" pydoorlock/Config.py
|
||||
sed -i -r -e "s@(^__version__ = ').*('$$)@\1$(shell cat VERSION)\2@" doorlockd
|
||||
|
||||
pydoorlock/Protocol.py: avr-code/protocol.h
|
||||
./scripts/gen_protocol.sh $^ > $@
|
||||
|
||||
install:
|
||||
mkdir -p $(BIN)
|
||||
mkdir -p $(SHARE)
|
||||
mkdir -p $(SYSTEMD_UNITS)
|
||||
mkdir -p $(ETC)
|
||||
|
||||
install doorlockd doorstate $(BIN)
|
||||
install doorlockd-passwd $(BIN)
|
||||
install -m 0644 etc/doorlockd.cfg $(ETC)
|
||||
install -m 0644 systemd/doorlockd.service systemd/doorstate.service $(SYSTEMD_UNITS)
|
||||
|
||||
python setup.py install --root="$(DESTDIR)" --optimize=1
|
||||
|
||||
cp -av share/* $(SHARE)
|
||||
|
||||
clean:
|
||||
rm -rf pydoorlock/__pycache__
|
||||
rm -f pydoorlock/Protocol.py
|
3
TODO
Normal file
|
@ -0,0 +1,3 @@
|
|||
- automatically setup doorlock user
|
||||
- automatically deploy nginx
|
||||
- unclutter stuff for X (hide cursor)
|
37
arch/PKGBUILD
Normal file
|
@ -0,0 +1,37 @@
|
|||
# Author: Ralf Ramsauer <ralf@binary-kitchen.de>
|
||||
|
||||
pkgname=doorlockd
|
||||
pkgver=2.1
|
||||
pkgrel=1
|
||||
pkgdesc="Binary Kitchen's doorlockd"
|
||||
arch=('any')
|
||||
url="https://github.com/Binary-Kitchen/${pkgname}"
|
||||
license=(GPL)
|
||||
depends=('python3'
|
||||
'python-pyserial'
|
||||
'python-ldap'
|
||||
'python-pip'
|
||||
'alsa-utils'
|
||||
'libgpiod'
|
||||
'mosquitto'
|
||||
'mpg123'
|
||||
'python-flask-wtf'
|
||||
'python-paho-mqtt'
|
||||
'chromium'
|
||||
'xf86-video-fbdev'
|
||||
'fluxbox'
|
||||
'nginx'
|
||||
'slim')
|
||||
source=("git+https://github.com/Binary-Kitchen/${pkgname}.git#branch=next")
|
||||
sha256sums=('SKIP')
|
||||
|
||||
build() {
|
||||
cd "$srcdir/$pkgname/"
|
||||
make
|
||||
}
|
||||
|
||||
package() {
|
||||
cd "$srcdir/$pkgname/"
|
||||
make PREFIX=/usr sysconfdir=/etc package
|
||||
make PREFIX=/usr sysconfdir=/etc DESTDIR="$pkgdir" install
|
||||
}
|
|
@ -1,608 +1,49 @@
|
|||
# Hey Emacs, this is a -*- makefile -*-
|
||||
#----------------------------------------------------------------------------
|
||||
# WinAVR Makefile Template written by Eric B. Weddington, Jörg Wunsch, et al.
|
||||
# Doorlock AVR - The AVR part of doorlockd
|
||||
#
|
||||
# Released to the Public Domain
|
||||
# Copyright (c) Binary Kitchen e.V., 2018
|
||||
#
|
||||
# Additional material for this makefile was written by:
|
||||
# Peter Fleury
|
||||
# Tim Henigan
|
||||
# Colin O'Flynn
|
||||
# Reiner Patommel
|
||||
# Markus Pfaff
|
||||
# Sander Pool
|
||||
# Frederik Rouleau
|
||||
# Carlos Lamas
|
||||
# Authors:
|
||||
# Ralf Ramsauer <ralf@binary-kitchen.de>
|
||||
#
|
||||
#----------------------------------------------------------------------------
|
||||
# On command line:
|
||||
#
|
||||
# make all = Make software.
|
||||
#
|
||||
# make clean = Clean out built project files.
|
||||
#
|
||||
# make coff = Convert ELF to AVR COFF.
|
||||
#
|
||||
# make extcoff = Convert ELF to AVR Extended COFF.
|
||||
#
|
||||
# make program = Download the hex file to the device, using avrdude.
|
||||
# Please customize the avrdude settings below first!
|
||||
#
|
||||
# make debug = Start either simulavr or avarice as specified for debugging,
|
||||
# with avr-gdb or avr-insight as the front end for debugging.
|
||||
#
|
||||
# make filename.s = Just compile filename.c into the assembler code only.
|
||||
#
|
||||
# make filename.i = Create a preprocessed source file for use in submitting
|
||||
# bug reports to the GCC project.
|
||||
#
|
||||
# To rebuild project do "make clean" then "make all".
|
||||
#----------------------------------------------------------------------------
|
||||
# This work is licensed under the terms of the GNU GPL, version 2. See
|
||||
# the COPYING file in the top-level directory.
|
||||
|
||||
|
||||
# MCU name
|
||||
MCU = attiny2313
|
||||
TARGET = doorlock
|
||||
|
||||
MCU ?= attiny2313a
|
||||
F_OSC ?= 4000000
|
||||
UART_BAUD ?= 9600
|
||||
AVRDUDE_MCU ?= t2313
|
||||
|
||||
# Processor frequency.
|
||||
# This will define a symbol, F_CPU, in all source code files equal to the
|
||||
# processor frequency. You can then use this symbol in your source code to
|
||||
# calculate timings. Do NOT tack on a 'UL' at the end, this will be done
|
||||
# automatically to create a 32-bit value in your source code.
|
||||
# Typical values are:
|
||||
# F_CPU = 1000000
|
||||
# F_CPU = 1843200
|
||||
# F_CPU = 2000000
|
||||
# F_CPU = 3686400
|
||||
# F_CPU = 4000000
|
||||
# F_CPU = 7372800
|
||||
# F_CPU = 8000000
|
||||
# F_CPU = 11059200
|
||||
# F_CPU = 14745600
|
||||
# F_CPU = 16000000
|
||||
# F_CPU = 18432000
|
||||
# F_CPU = 20000000
|
||||
PROGRAMMER=linuxspi
|
||||
PORT=/dev/spidev0.0:/dev/gpiochip0:25
|
||||
SPEED=125000
|
||||
|
||||
F_CPU = 14745600
|
||||
#F_CPU = 8000000
|
||||
OBJS = main.o uart.o
|
||||
|
||||
# Output format. (can be srec, ihex, binary)
|
||||
FORMAT = ihex
|
||||
|
||||
|
||||
# Target file name (without extension).
|
||||
TARGET = main
|
||||
|
||||
|
||||
# Object files directory
|
||||
OBJDIR = obj
|
||||
|
||||
|
||||
# List C source files here. (C dependencies are automatically generated.)
|
||||
SRC = main.c uart.c
|
||||
|
||||
|
||||
# List C++ source files here. (C dependencies are automatically generated.)
|
||||
CPPSRC =
|
||||
|
||||
|
||||
# List Assembler source files here.
|
||||
# Make them always end in a capital .S. Files ending in a lowercase .s
|
||||
# will not be considered source files but generated files (assembler
|
||||
# output from the compiler), and will be deleted upon "make clean"!
|
||||
# Even though the DOS/Win* filesystem matches both .s and .S the same,
|
||||
# it will preserve the spelling of the filenames, and gcc itself does
|
||||
# care about how the name is spelled on its command-line.
|
||||
ASRC =
|
||||
|
||||
|
||||
# Optimization level, can be [0, 1, 2, 3, s].
|
||||
# 0 = turn off optimization. s = optimize for size.
|
||||
# (Note: 3 is not always the best optimization level. See avr-libc FAQ.)
|
||||
OPT = s
|
||||
|
||||
|
||||
# Debugging format.
|
||||
# Native formats for AVR-GCC's -g are dwarf-2 [default] or stabs.
|
||||
# AVR Studio 4.10 requires dwarf-2.
|
||||
# AVR [Extended] COFF format requires stabs, plus an avr-objcopy run.
|
||||
DEBUG =
|
||||
|
||||
|
||||
# List any extra directories to look for include files here.
|
||||
# Each directory must be seperated by a space.
|
||||
# Use forward slashes for directory separators.
|
||||
# For a directory that has spaces, enclose it in quotes.
|
||||
EXTRAINCDIRS =
|
||||
|
||||
|
||||
# Compiler flag to set the C Standard level.
|
||||
# c89 = "ANSI" C
|
||||
# gnu89 = c89 plus GCC extensions
|
||||
# c99 = ISO C99 standard (not yet fully implemented)
|
||||
# gnu99 = c99 plus GCC extensions
|
||||
CSTANDARD = -std=c11
|
||||
|
||||
|
||||
# Place -D or -U options here for C sources
|
||||
CDEFS = -DF_CPU=$(F_CPU)UL
|
||||
|
||||
|
||||
# Place -D or -U options here for C++ sources
|
||||
CPPDEFS = -DF_CPU=$(F_CPU)UL
|
||||
#CPPDEFS += -D__STDC_LIMIT_MACROS
|
||||
#CPPDEFS += -D__STDC_CONSTANT_MACROS
|
||||
|
||||
|
||||
|
||||
#---------------- Compiler Options C ----------------
|
||||
# -g*: generate debugging information
|
||||
# -O*: optimization level
|
||||
# -f...: tuning, see GCC manual and avr-libc documentation
|
||||
# -Wall...: warning level
|
||||
# -Wa,...: tell GCC to pass this to the assembler.
|
||||
# -adhlns...: create assembler listing
|
||||
CFLAGS = -g$(DEBUG)
|
||||
CFLAGS += $(CDEFS)
|
||||
CFLAGS += -O$(OPT)
|
||||
#CFLAGS += -mint8
|
||||
#CFLAGS += -mshort-calls
|
||||
CFLAGS += -funsigned-char
|
||||
CFLAGS += -funsigned-bitfields
|
||||
CFLAGS += -fpack-struct
|
||||
CFLAGS += -fshort-enums
|
||||
#CFLAGS += -fno-unit-at-a-time
|
||||
CFLAGS += -Wall
|
||||
CFLAGS += -Wstrict-prototypes
|
||||
CFLAGS += -Wundef
|
||||
#CFLAGS += -Wunreachable-code
|
||||
#CFLAGS += -Wsign-compare
|
||||
CFLAGS += -Wa,-adhlns=$(<:%.c=$(OBJDIR)/%.lst)
|
||||
CFLAGS += $(patsubst %,-I%,$(EXTRAINCDIRS))
|
||||
CFLAGS += $(CSTANDARD)
|
||||
|
||||
|
||||
#---------------- Compiler Options C++ ----------------
|
||||
# -g*: generate debugging information
|
||||
# -O*: optimization level
|
||||
# -f...: tuning, see GCC manual and avr-libc documentation
|
||||
# -Wall...: warning level
|
||||
# -Wa,...: tell GCC to pass this to the assembler.
|
||||
# -adhlns...: create assembler listing
|
||||
CPPFLAGS = -g$(DEBUG)
|
||||
CPPFLAGS += $(CPPDEFS)
|
||||
CPPFLAGS += -O$(OPT)
|
||||
#CPPFLAGS += -mint8
|
||||
#CPPFLAGS += -mshort-calls
|
||||
CPPFLAGS += -funsigned-char
|
||||
CPPFLAGS += -funsigned-bitfields
|
||||
CPPFLAGS += -fpack-struct
|
||||
CPPFLAGS += -fshort-enums
|
||||
CPPFLAGS += -fno-exceptions
|
||||
#CPPFLAGS += -fno-unit-at-a-time
|
||||
CPPFLAGS += -Wall
|
||||
#CPPFLAGS += -Wstrict-prototypes
|
||||
CFLAGS += -Wundef
|
||||
#CPPFLAGS += -Wunreachable-code
|
||||
#CPPFLAGS += -Wsign-compare
|
||||
CPPFLAGS += -Wa,-adhlns=$(<:%.cpp=$(OBJDIR)/%.lst)
|
||||
CPPFLAGS += $(patsubst %,-I%,$(EXTRAINCDIRS))
|
||||
#CPPFLAGS += $(CSTANDARD)
|
||||
|
||||
|
||||
#---------------- Assembler Options ----------------
|
||||
# -Wa,...: tell GCC to pass this to the assembler.
|
||||
# -ahlms: create listing
|
||||
# -gstabs: have the assembler create line number information; note that
|
||||
# for use in COFF files, additional information about filenames
|
||||
# and function names needs to be present in the assembler source
|
||||
# files -- see avr-libc docs [FIXME: not yet described there]
|
||||
ASFLAGS = -Wa,-adhlns=$(<:%.S=$(OBJDIR)/%.lst),-gstabs
|
||||
|
||||
|
||||
#---------------- Library Options ----------------
|
||||
# Minimalistic printf version
|
||||
PRINTF_LIB_MIN = -Wl,-u,vfprintf -lprintf_min
|
||||
|
||||
# Floating point printf version (requires MATH_LIB = -lm below)
|
||||
PRINTF_LIB_FLOAT = -Wl,-u,vfprintf -lprintf_flt
|
||||
|
||||
# If this is left blank, then it will use the Standard printf version.
|
||||
PRINTF_LIB =
|
||||
#PRINTF_LIB = $(PRINTF_LIB_MIN)
|
||||
#PRINTF_LIB = $(PRINTF_LIB_FLOAT)
|
||||
|
||||
|
||||
# Minimalistic scanf version
|
||||
SCANF_LIB_MIN = -Wl,-u,vfscanf -lscanf_min
|
||||
|
||||
# Floating point + %[ scanf version (requires MATH_LIB = -lm below)
|
||||
SCANF_LIB_FLOAT = -Wl,-u,vfscanf -lscanf_flt
|
||||
|
||||
# If this is left blank, then it will use the Standard scanf version.
|
||||
SCANF_LIB =
|
||||
#SCANF_LIB = $(SCANF_LIB_MIN)
|
||||
#SCANF_LIB = $(SCANF_LIB_FLOAT)
|
||||
|
||||
|
||||
MATH_LIB = -lm
|
||||
|
||||
|
||||
|
||||
#---------------- External Memory Options ----------------
|
||||
|
||||
# 64 KB of external RAM, starting after internal RAM (ATmega128!),
|
||||
# used for variables (.data/.bss) and heap (malloc()).
|
||||
#EXTMEMOPTS = -Wl,-Tdata=0x801100,--defsym=__heap_end=0x80ffff
|
||||
|
||||
# 64 KB of external RAM, starting after internal RAM (ATmega128!),
|
||||
# only used for heap (malloc()).
|
||||
#EXTMEMOPTS = -Wl,--defsym=__heap_start=0x801100,--defsym=__heap_end=0x80ffff
|
||||
|
||||
EXTMEMOPTS =
|
||||
|
||||
|
||||
|
||||
#---------------- Linker Options ----------------
|
||||
# -Wl,...: tell GCC to pass this to linker.
|
||||
# -Map: create map file
|
||||
# --cref: add cross reference to map file
|
||||
LDFLAGS = -Wl,-Map=$(TARGET).map,--cref
|
||||
LDFLAGS += $(EXTMEMOPTS)
|
||||
LDFLAGS += $(PRINTF_LIB) $(SCANF_LIB) $(MATH_LIB)
|
||||
#LDFLAGS += -T linker_script.x
|
||||
|
||||
|
||||
|
||||
#---------------- Programming Options (avrdude) ----------------
|
||||
|
||||
# Programming hardware: alf avr910 avrisp bascom bsd
|
||||
# dt006 pavr picoweb pony-stk200 sp12 stk200 stk500
|
||||
#
|
||||
# Type: avrdude -c ?
|
||||
# to get a full listing.
|
||||
#
|
||||
AVRDUDE_PROGRAMMER = avr910
|
||||
|
||||
# com1 = serial port. Use lpt1 to connect to parallel port.
|
||||
AVRDUDE_PORT = /dev/ttyUSB0 # programmer connected to serial device
|
||||
|
||||
AVRDUDE_WRITE_FLASH = -U flash:w:$(TARGET).hex
|
||||
#AVRDUDE_WRITE_EEPROM = -U eeprom:w:$(TARGET).eep
|
||||
|
||||
|
||||
# Uncomment the following if you want avrdude's erase cycle counter.
|
||||
# Note that this counter needs to be initialized first using -Yn,
|
||||
# see avrdude manual.
|
||||
#AVRDUDE_ERASE_COUNTER = -y
|
||||
|
||||
# Uncomment the following if you do /not/ wish a verification to be
|
||||
# performed after programming the device.
|
||||
#AVRDUDE_NO_VERIFY = -V
|
||||
|
||||
# Increase verbosity level. Please use this when submitting bug
|
||||
# reports about avrdude. See <http://savannah.nongnu.org/projects/avrdude>
|
||||
# to submit bug reports.
|
||||
#AVRDUDE_VERBOSE = -v -v
|
||||
|
||||
AVRDUDE_FLAGS = -p $(MCU) -P $(AVRDUDE_PORT) -c $(AVRDUDE_PROGRAMMER)
|
||||
AVRDUDE_FLAGS += $(AVRDUDE_NO_VERIFY)
|
||||
AVRDUDE_FLAGS += $(AVRDUDE_VERBOSE)
|
||||
AVRDUDE_FLAGS += $(AVRDUDE_ERASE_COUNTER)
|
||||
|
||||
|
||||
|
||||
#---------------- Debugging Options ----------------
|
||||
|
||||
# For simulavr only - target MCU frequency.
|
||||
DEBUG_MFREQ = $(F_CPU)
|
||||
|
||||
# Set the DEBUG_UI to either gdb or insight.
|
||||
# DEBUG_UI = gdb
|
||||
DEBUG_UI = insight
|
||||
|
||||
# Set the debugging back-end to either avarice, simulavr.
|
||||
DEBUG_BACKEND = avarice
|
||||
#DEBUG_BACKEND = simulavr
|
||||
|
||||
# GDB Init Filename.
|
||||
GDBINIT_FILE = __avr_gdbinit
|
||||
|
||||
# When using avarice settings for the JTAG
|
||||
JTAG_DEV = /dev/com1
|
||||
|
||||
# Debugging port used to communicate between GDB / avarice / simulavr.
|
||||
DEBUG_PORT = 4242
|
||||
|
||||
# Debugging host used to communicate between GDB / avarice / simulavr, normally
|
||||
# just set to localhost unless doing some sort of crazy debugging when
|
||||
# avarice is running on a different computer.
|
||||
DEBUG_HOST = localhost
|
||||
|
||||
|
||||
|
||||
#============================================================================
|
||||
|
||||
|
||||
# Define programs and commands.
|
||||
SHELL = sh
|
||||
CC = avr-gcc
|
||||
OBJCOPY = avr-objcopy
|
||||
OBJDUMP = avr-objdump
|
||||
SIZE = avr-size
|
||||
NM = avr-nm
|
||||
AVRDUDE = avrdude
|
||||
REMOVE = rm -f
|
||||
REMOVEDIR = rm -rf
|
||||
COPY = cp
|
||||
WINSHELL = cmd
|
||||
|
||||
CFLAGS := -g -O2 -mmcu=$(MCU)
|
||||
CFLAGS += -Wall -Wextra -Wstrict-prototypes
|
||||
CFLAGS += -DF_OSC=$(F_OSC) -DF_CPU=F_OSC -DUART_BAUD=$(UART_BAUD)UL -DMCU=$(MCU)
|
||||
|
||||
# Define Messages
|
||||
# English
|
||||
MSG_ERRORS_NONE = Errors: none
|
||||
MSG_BEGIN = -------- begin --------
|
||||
MSG_END = -------- end --------
|
||||
MSG_SIZE_BEFORE = Size before:
|
||||
MSG_SIZE_AFTER = Size after:
|
||||
MSG_COFF = Converting to AVR COFF:
|
||||
MSG_EXTENDED_COFF = Converting to AVR Extended COFF:
|
||||
MSG_FLASH = Creating load file for Flash:
|
||||
MSG_EEPROM = Creating load file for EEPROM:
|
||||
MSG_EXTENDED_LISTING = Creating Extended Listing:
|
||||
MSG_SYMBOL_TABLE = Creating Symbol Table:
|
||||
MSG_LINKING = Linking:
|
||||
MSG_COMPILING = Compiling C:
|
||||
MSG_COMPILING_CPP = Compiling C++:
|
||||
MSG_ASSEMBLING = Assembling:
|
||||
MSG_CLEANING = Cleaning project:
|
||||
MSG_CREATING_LIBRARY = Creating library:
|
||||
all: $(TARGET).hex
|
||||
|
||||
$(TARGET).elf: $(OBJS)
|
||||
$(CC) $(CFLAGS) -o $(TARGET).elf $^
|
||||
|
||||
$(TARGET).hex: $(TARGET).elf
|
||||
$(OBJCOPY) -O ihex -R .eeprom $^ $@
|
||||
|
||||
program: $(TARGET).hex
|
||||
$(AVRDUDE) -p $(AVRDUDE_MCU) -c $(PROGRAMMER) -P $(PORT) -U flash:w:$^
|
||||
|
||||
# Define all object files.
|
||||
OBJ = $(SRC:%.c=$(OBJDIR)/%.o) $(CPPSRC:%.cpp=$(OBJDIR)/%.o) $(ASRC:%.S=$(OBJDIR)/%.o)
|
||||
|
||||
# Define all listing files.
|
||||
LST = $(SRC:%.c=$(OBJDIR)/%.lst) $(CPPSRC:%.cpp=$(OBJDIR)/%.lst) $(ASRC:%.S=$(OBJDIR)/%.lst)
|
||||
|
||||
|
||||
# Compiler flags to generate dependency files.
|
||||
GENDEPFLAGS = -MD -MP -MF .dep/$(@F).d
|
||||
|
||||
|
||||
# Combine all necessary flags and optional flags.
|
||||
# Add target processor to flags.
|
||||
ALL_CFLAGS = -mmcu=$(MCU) -I. $(CFLAGS) $(GENDEPFLAGS)
|
||||
ALL_CPPFLAGS = -mmcu=$(MCU) -I. -x c++ $(CPPFLAGS) $(GENDEPFLAGS)
|
||||
ALL_ASFLAGS = -mmcu=$(MCU) -I. -x assembler-with-cpp $(ASFLAGS)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# Default target.
|
||||
all: begin gccversion sizebefore build sizeafter end
|
||||
|
||||
# Change the build target to build a HEX file or a library.
|
||||
build: elf hex eep lss sym
|
||||
#build: lib
|
||||
|
||||
|
||||
elf: $(TARGET).elf
|
||||
hex: $(TARGET).hex
|
||||
eep: $(TARGET).eep
|
||||
lss: $(TARGET).lss
|
||||
sym: $(TARGET).sym
|
||||
LIBNAME=lib$(TARGET).a
|
||||
lib: $(LIBNAME)
|
||||
|
||||
|
||||
|
||||
# Eye candy.
|
||||
# AVR Studio 3.x does not check make's exit code but relies on
|
||||
# the following magic strings to be generated by the compile job.
|
||||
begin:
|
||||
@echo
|
||||
@echo $(MSG_BEGIN)
|
||||
|
||||
end:
|
||||
@echo $(MSG_END)
|
||||
@echo
|
||||
|
||||
|
||||
# Display size of file.
|
||||
HEXSIZE = $(SIZE) --target=$(FORMAT) $(TARGET).hex
|
||||
ELFSIZE = $(SIZE) -A $(TARGET).elf
|
||||
AVRMEM = avr-mem.sh $(TARGET).elf $(MCU)
|
||||
|
||||
sizebefore:
|
||||
@if test -f $(TARGET).elf; then echo; echo $(MSG_SIZE_BEFORE); $(ELFSIZE); \
|
||||
$(AVRMEM) 2>/dev/null; echo; fi
|
||||
|
||||
sizeafter:
|
||||
@if test -f $(TARGET).elf; then echo; echo $(MSG_SIZE_AFTER); $(ELFSIZE); \
|
||||
$(AVRMEM) 2>/dev/null; echo; fi
|
||||
|
||||
|
||||
|
||||
# Display compiler version information.
|
||||
gccversion :
|
||||
@$(CC) --version
|
||||
|
||||
|
||||
|
||||
# Program the device.
|
||||
program: $(TARGET).hex $(TARGET).eep
|
||||
$(AVRDUDE) $(AVRDUDE_FLAGS) $(AVRDUDE_WRITE_FLASH) $(AVRDUDE_WRITE_EEPROM)
|
||||
|
||||
|
||||
# Generate avr-gdb config/init file which does the following:
|
||||
# define the reset signal, load the target file, connect to target, and set
|
||||
# a breakpoint at main().
|
||||
gdb-config:
|
||||
@$(REMOVE) $(GDBINIT_FILE)
|
||||
@echo define reset >> $(GDBINIT_FILE)
|
||||
@echo SIGNAL SIGHUP >> $(GDBINIT_FILE)
|
||||
@echo end >> $(GDBINIT_FILE)
|
||||
@echo file $(TARGET).elf >> $(GDBINIT_FILE)
|
||||
@echo target remote $(DEBUG_HOST):$(DEBUG_PORT) >> $(GDBINIT_FILE)
|
||||
ifeq ($(DEBUG_BACKEND),simulavr)
|
||||
@echo load >> $(GDBINIT_FILE)
|
||||
endif
|
||||
@echo break main >> $(GDBINIT_FILE)
|
||||
|
||||
debug: gdb-config $(TARGET).elf
|
||||
ifeq ($(DEBUG_BACKEND), avarice)
|
||||
@echo Starting AVaRICE - Press enter when "waiting to connect" message displays.
|
||||
@$(WINSHELL) /c start avarice --jtag $(JTAG_DEV) --erase --program --file \
|
||||
$(TARGET).elf $(DEBUG_HOST):$(DEBUG_PORT)
|
||||
@$(WINSHELL) /c pause
|
||||
|
||||
else
|
||||
@$(WINSHELL) /c start simulavr --gdbserver --device $(MCU) --clock-freq \
|
||||
$(DEBUG_MFREQ) --port $(DEBUG_PORT)
|
||||
endif
|
||||
@$(WINSHELL) /c start avr-$(DEBUG_UI) --command=$(GDBINIT_FILE)
|
||||
|
||||
|
||||
|
||||
|
||||
# Convert ELF to COFF for use in debugging / simulating in AVR Studio or VMLAB.
|
||||
COFFCONVERT = $(OBJCOPY) --debugging
|
||||
COFFCONVERT += --change-section-address .data-0x800000
|
||||
COFFCONVERT += --change-section-address .bss-0x800000
|
||||
COFFCONVERT += --change-section-address .noinit-0x800000
|
||||
COFFCONVERT += --change-section-address .eeprom-0x810000
|
||||
|
||||
|
||||
|
||||
coff: $(TARGET).elf
|
||||
@echo
|
||||
@echo $(MSG_COFF) $(TARGET).cof
|
||||
$(COFFCONVERT) -O coff-avr $< $(TARGET).cof
|
||||
|
||||
|
||||
extcoff: $(TARGET).elf
|
||||
@echo
|
||||
@echo $(MSG_EXTENDED_COFF) $(TARGET).cof
|
||||
$(COFFCONVERT) -O coff-ext-avr $< $(TARGET).cof
|
||||
|
||||
|
||||
|
||||
# Create final output files (.hex, .eep) from ELF output file.
|
||||
%.hex: %.elf
|
||||
@echo
|
||||
@echo $(MSG_FLASH) $@
|
||||
$(OBJCOPY) -O $(FORMAT) -R .eeprom $< $@
|
||||
|
||||
%.eep: %.elf
|
||||
@echo
|
||||
@echo $(MSG_EEPROM) $@
|
||||
-$(OBJCOPY) -j .eeprom --set-section-flags=.eeprom="alloc,load" \
|
||||
--change-section-lma .eeprom=0 -O $(FORMAT) $< $@
|
||||
|
||||
# Create extended listing file from ELF output file.
|
||||
%.lss: %.elf
|
||||
@echo
|
||||
@echo $(MSG_EXTENDED_LISTING) $@
|
||||
$(OBJDUMP) -h -S $< > $@
|
||||
|
||||
# Create a symbol table from ELF output file.
|
||||
%.sym: %.elf
|
||||
@echo
|
||||
@echo $(MSG_SYMBOL_TABLE) $@
|
||||
$(NM) -n $< > $@
|
||||
|
||||
|
||||
|
||||
# Create library from object files.
|
||||
.SECONDARY : $(TARGET).a
|
||||
.PRECIOUS : $(OBJ)
|
||||
%.a: $(OBJ)
|
||||
@echo
|
||||
@echo $(MSG_CREATING_LIBRARY) $@
|
||||
$(AR) $@ $(OBJ)
|
||||
|
||||
|
||||
# Link: create ELF output file from object files.
|
||||
.SECONDARY : $(TARGET).elf
|
||||
.PRECIOUS : $(OBJ)
|
||||
%.elf: $(OBJ)
|
||||
@echo
|
||||
@echo $(MSG_LINKING) $@
|
||||
$(CC) $(ALL_CFLAGS) $^ --output $@ $(LDFLAGS)
|
||||
|
||||
|
||||
# Compile: create object files from C source files.
|
||||
$(OBJDIR)/%.o : %.c
|
||||
@echo
|
||||
@echo $(MSG_COMPILING) $<
|
||||
$(CC) -c $(ALL_CFLAGS) $< -o $@
|
||||
|
||||
|
||||
# Compile: create object files from C++ source files.
|
||||
$(OBJDIR)/%.o : %.cpp
|
||||
@echo
|
||||
@echo $(MSG_COMPILING_CPP) $<
|
||||
$(CC) -c $(ALL_CPPFLAGS) $< -o $@
|
||||
|
||||
|
||||
# Compile: create assembler files from C source files.
|
||||
%.s : %.c
|
||||
$(CC) -S $(ALL_CFLAGS) $< -o $@
|
||||
|
||||
|
||||
# Compile: create assembler files from C++ source files.
|
||||
%.s : %.cpp
|
||||
$(CC) -S $(ALL_CPPFLAGS) $< -o $@
|
||||
|
||||
|
||||
# Assemble: create object files from assembler source files.
|
||||
$(OBJDIR)/%.o : %.S
|
||||
@echo
|
||||
@echo $(MSG_ASSEMBLING) $<
|
||||
$(CC) -c $(ALL_ASFLAGS) $< -o $@
|
||||
|
||||
|
||||
# Create preprocessed source for use in sending a bug report.
|
||||
%.i : %.c
|
||||
$(CC) -E -mmcu=$(MCU) -I. $(CFLAGS) $< -o $@
|
||||
|
||||
|
||||
# Target: clean project.
|
||||
clean: begin clean_list end
|
||||
|
||||
clean_list :
|
||||
@echo
|
||||
@echo $(MSG_CLEANING)
|
||||
$(REMOVE) $(TARGET).hex
|
||||
$(REMOVE) $(TARGET).eep
|
||||
$(REMOVE) $(TARGET).cof
|
||||
$(REMOVE) $(TARGET).elf
|
||||
$(REMOVE) $(TARGET).map
|
||||
$(REMOVE) $(TARGET).sym
|
||||
$(REMOVE) $(TARGET).lss
|
||||
$(REMOVEDIR) $(OBJDIR)
|
||||
$(REMOVE) $(SRC:.c=.s)
|
||||
$(REMOVE) $(SRC:.c=.d)
|
||||
$(REMOVEDIR) .dep
|
||||
|
||||
|
||||
# Create object files directory
|
||||
$(shell mkdir $(OBJDIR) 2>/dev/null)
|
||||
|
||||
|
||||
# Include the dependency files.
|
||||
-include $(shell mkdir .dep 2>/dev/null) $(wildcard .dep/*)
|
||||
|
||||
|
||||
# Listing of phony targets.
|
||||
.PHONY : all begin finish end sizebefore sizeafter gccversion \
|
||||
build elf hex eep lss sym coff extcoff \
|
||||
clean clean_list program debug gdb-config
|
||||
|
||||
|
||||
|
||||
fuse:
|
||||
$(AVRDUDE) -p $(AVRDUDE_MCU) -c $(PROGRAMMER) -P $(PORT) -b $(SPEED) -U lfuse:w:0xfd:m -U hfuse:w:0xdf:m -U efuse:w:0xff:m
|
||||
|
||||
clean:
|
||||
rm -f $(OBJS)
|
||||
rm -f $(TARGET).elf $(TARGET).hex
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
RS232 (female)
|
||||
|
||||
Innenschalter:
|
||||
1 - GND <-> gelbgruen
|
||||
2 - Schalter Notauf (active LOW) <-> Ader 1
|
||||
3 - Schalter Notzu (active LOW) <-> Ader 2
|
||||
|
||||
Tuersystem:
|
||||
1 - GND <-> Ader 1
|
||||
2 - Notsperre <-> Ader 3
|
||||
3 - Bolzen (12VDC) <-> gelbgruen
|
||||
4 - Schnapper (12VAC) <-> Ader 2
|
||||
|
||||
Stromversorgung:
|
||||
1 - Status
|
||||
2 - +12VDC
|
||||
3 - 12VAC
|
||||
4 - +5VDC
|
||||
5 - GND
|
||||
|
106
avr-code/io.h
|
@ -1,106 +0,0 @@
|
|||
#ifndef IO_H
|
||||
#define IO_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <avr/io.h>
|
||||
|
||||
/*
|
||||
* Macros
|
||||
*/
|
||||
#define PIN(x) (*(&x - 2)) // Address Of Data Direction Register Of Port x
|
||||
#define DDR(x) (*(&x - 1)) // Address Of Input Register Of Port x
|
||||
|
||||
/*
|
||||
* Outputs
|
||||
*/
|
||||
#define PORT_BOLZEN PORTB
|
||||
#define PIN_BOLZEN PB0
|
||||
|
||||
#define PORT_SCHNAPPER PORTB
|
||||
#define PIN_SCHNAPPER PB1
|
||||
|
||||
#define PORT_STATUS PORTB
|
||||
#define PIN_STATUS PB2
|
||||
|
||||
/*
|
||||
* Inputs
|
||||
*/
|
||||
#define PORT_BUTTON_LOCK PORTD
|
||||
#define PIN_BUTTON_LOCK PD2
|
||||
|
||||
#define PORT_BUTTON_UNLOCK PORTD
|
||||
#define PIN_BUTTON_UNLOCK PD3
|
||||
|
||||
#define PORT_EMERGENCY_UNLOCK PORTD
|
||||
#define PIN_EMERGENCY_UNLOCK PD4
|
||||
|
||||
static inline bool is_emergency_unlock(void)
|
||||
{
|
||||
return !(PIN(PORT_EMERGENCY_UNLOCK) & (1<<PIN_EMERGENCY_UNLOCK));
|
||||
}
|
||||
|
||||
static inline bool is_button_unlock(void)
|
||||
{
|
||||
return !(PIN(PORT_BUTTON_UNLOCK) & (1<<PIN_BUTTON_UNLOCK));
|
||||
}
|
||||
|
||||
static inline bool is_button_lock(void)
|
||||
{
|
||||
return !(PIN(PORT_BUTTON_LOCK) & (1<<PIN_BUTTON_LOCK));
|
||||
}
|
||||
|
||||
|
||||
static inline void schnapper_off(void)
|
||||
{
|
||||
PORT_SCHNAPPER &= ~(1<<PIN_SCHNAPPER);
|
||||
}
|
||||
|
||||
static inline void schnapper_on(void)
|
||||
{
|
||||
PORT_SCHNAPPER |= (1<<PIN_SCHNAPPER);
|
||||
}
|
||||
|
||||
static inline void bolzen_off(void)
|
||||
{
|
||||
PORT_BOLZEN &= ~(1<<PIN_BOLZEN);
|
||||
}
|
||||
|
||||
static inline void bolzen_on(void)
|
||||
{
|
||||
PORT_BOLZEN |= (1<<PIN_BOLZEN);
|
||||
}
|
||||
|
||||
static inline void status_off(void)
|
||||
{
|
||||
PORT_STATUS &= ~(1<<PIN_STATUS);
|
||||
}
|
||||
|
||||
static inline void status_on(void)
|
||||
{
|
||||
PORT_STATUS |= (1<<PIN_STATUS);
|
||||
}
|
||||
|
||||
static inline void io_init(void)
|
||||
{
|
||||
// Set output directions
|
||||
DDR(PORT_SCHNAPPER) |= (1<<PIN_SCHNAPPER);
|
||||
schnapper_off();
|
||||
|
||||
DDR(PORT_SCHNAPPER) |= (1<<PIN_BOLZEN);
|
||||
bolzen_off();
|
||||
|
||||
DDR(PORT_STATUS) |= (1<<PIN_STATUS);
|
||||
status_off();
|
||||
|
||||
// Set input directions and activate pull-ups
|
||||
DDR(PORT_BUTTON_UNLOCK) &= ~(1<<PIN_BUTTON_UNLOCK);
|
||||
PORT_BUTTON_UNLOCK |= (1<<PIN_BUTTON_UNLOCK);
|
||||
|
||||
DDR(PORT_BUTTON_LOCK) &= ~(1<<PIN_BUTTON_LOCK);
|
||||
PORT_BUTTON_LOCK |= (1<<PIN_BUTTON_LOCK);
|
||||
|
||||
DDR(PORT_EMERGENCY_UNLOCK) &= ~(1<<PIN_EMERGENCY_UNLOCK);
|
||||
PORT_EMERGENCY_UNLOCK |= (1<<PIN_EMERGENCY_UNLOCK);
|
||||
}
|
||||
|
||||
#endif
|
318
avr-code/main.c
|
@ -1,30 +1,53 @@
|
|||
/* 2015, Ralf Ramsauer
|
||||
* ralf@binary-kitchen.de
|
||||
/*
|
||||
* doorlock-avr, AVR code of Binary Kitchen's doorlock
|
||||
*
|
||||
* Copyright (c) Binary Kitchen, 2018
|
||||
*
|
||||
* Authors:
|
||||
* Ralf Ramsauer <ralf@binary-kitchen.de>
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2. See
|
||||
* the COPYING file in the top-level directory.
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include "uart.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdbool.h>
|
||||
#include <avr/io.h>
|
||||
#include <avr/interrupt.h>
|
||||
#include <util/delay.h>
|
||||
|
||||
#include "io.h"
|
||||
#include "uart.h"
|
||||
#include "protocol.h"
|
||||
|
||||
#include "../doorcmds.h"
|
||||
#define RED 0x1
|
||||
#define GREEN 0x2
|
||||
#define YELLOW 0x4
|
||||
|
||||
static volatile enum {LOCKED, UNLOCKED} state = LOCKED;
|
||||
static volatile bool schnapper = false;
|
||||
#define SET_CONDITIONAL(predicate, port, pin) \
|
||||
if ((predicate)) \
|
||||
port |= (1 << pin); \
|
||||
else \
|
||||
port &= ~(1 << pin);
|
||||
|
||||
static inline void timer_init(void)
|
||||
/* can either be red, green, or yellow */
|
||||
static unsigned char state = RED;
|
||||
|
||||
enum state_source {
|
||||
BUTTON,
|
||||
COMM,
|
||||
TIMEOUT,
|
||||
EMERGENCY,
|
||||
};
|
||||
|
||||
static inline void set_schnapper(bool state)
|
||||
{
|
||||
// Config the timer
|
||||
// The 16bit Timer1 is used for resetting the lock state,
|
||||
// if the UART stops receiving the unlock command
|
||||
TIMSK |= (1<<TOIE1);
|
||||
TIFR |= (1<<TOV1);
|
||||
TCCR1A = 0;
|
||||
TCCR1B = (1<<CS12);
|
||||
SET_CONDITIONAL(state, PORTB, PB0);
|
||||
}
|
||||
|
||||
static inline void set_bolzen(bool state)
|
||||
{
|
||||
SET_CONDITIONAL(state, PORTB, PB1);
|
||||
}
|
||||
|
||||
static inline void reset_timeout(void)
|
||||
|
@ -32,155 +55,176 @@ static inline void reset_timeout(void)
|
|||
TCNT1 = 0;
|
||||
}
|
||||
|
||||
static inline void extint_init(void)
|
||||
static void set_leds(void)
|
||||
{
|
||||
// Configure external interrupts
|
||||
// External interrupts are used for Button Unlock an Lock
|
||||
MCUCR = (1<<ISC11)|(1<<ISC01);
|
||||
GIMSK |= (1<<INT0)|(1<<INT1);
|
||||
}
|
||||
static unsigned int counter = 0;
|
||||
bool pwm_cycle = ++counter % 20;
|
||||
|
||||
void uart_handler(const unsigned char c)
|
||||
{
|
||||
char retval = c;
|
||||
switch ((char)c) {
|
||||
case DOOR_CMD_UNLOCK:
|
||||
state = UNLOCKED;
|
||||
reset_timeout();
|
||||
break;
|
||||
|
||||
case DOOR_CMD_LOCK:
|
||||
state = LOCKED;
|
||||
break;
|
||||
|
||||
case DOOR_CMD_PING:
|
||||
break;
|
||||
|
||||
case DOOR_CMD_SCHNAPER:
|
||||
if (state == UNLOCKED)
|
||||
schnapper = true;
|
||||
else
|
||||
retval = '?';
|
||||
break;
|
||||
|
||||
case DOOR_CMD_STATUS:
|
||||
retval = (state == LOCKED) ? 'l' : 'u';
|
||||
break;
|
||||
|
||||
default:
|
||||
retval = '?';
|
||||
break;
|
||||
if (pwm_cycle) {
|
||||
PORTD &= ~((1 << PD5) | (1 << PD6));
|
||||
PORTB &= ~(1 << PB4);
|
||||
}
|
||||
|
||||
uart_putc(retval);
|
||||
switch (state) {
|
||||
case RED:
|
||||
PORTD |= (1 << PD5);
|
||||
break;
|
||||
case YELLOW:
|
||||
PORTD |= (1 << PD6);
|
||||
break;
|
||||
case GREEN:
|
||||
PORTB |= (1 << PB4);
|
||||
break;
|
||||
}
|
||||
|
||||
if (pwm_cycle)
|
||||
return;
|
||||
|
||||
switch (state) {
|
||||
case RED:
|
||||
PORTD ^= (1 << PD6);
|
||||
PORTB ^= (1 << PB4);
|
||||
break;
|
||||
case YELLOW:
|
||||
PORTD ^= (1 << PD5);
|
||||
PORTB ^= (1 << PB4);
|
||||
break;
|
||||
case GREEN:
|
||||
PORTD ^= (1 << PD5);
|
||||
PORTD ^= (1 << PD6);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void update_state(unsigned char new_state, enum state_source source)
|
||||
{
|
||||
char ret = 0;
|
||||
reset_timeout();
|
||||
|
||||
if (new_state == state)
|
||||
return;
|
||||
state = new_state;
|
||||
|
||||
switch (state) {
|
||||
case RED:
|
||||
set_bolzen(true);
|
||||
set_schnapper(false);
|
||||
ret = AVR_STATE_SWITCH_RED;
|
||||
break;
|
||||
case YELLOW:
|
||||
set_bolzen(false);
|
||||
set_schnapper(false);
|
||||
ret = AVR_STATE_SWITCH_YELLOW;
|
||||
break;
|
||||
case GREEN:
|
||||
set_bolzen(false);
|
||||
set_schnapper(true);
|
||||
ret = AVR_STATE_SWITCH_GREEN;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (source) {
|
||||
case BUTTON:
|
||||
uart_putc(toupper(ret));
|
||||
break;
|
||||
case EMERGENCY:
|
||||
uart_putc(AVR_EMERGENCY);
|
||||
break;
|
||||
case TIMEOUT:
|
||||
uart_putc(ret);
|
||||
break;
|
||||
case COMM:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ISR(USART_RX_vect)
|
||||
{
|
||||
unsigned char c = UDR;
|
||||
|
||||
switch (c) {
|
||||
case AVR_STATE_SWITCH_RED:
|
||||
update_state(RED, COMM);
|
||||
break;
|
||||
case AVR_STATE_SWITCH_YELLOW:
|
||||
update_state(YELLOW, COMM);
|
||||
break;
|
||||
case AVR_STATE_SWITCH_GREEN:
|
||||
update_state(GREEN, COMM);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Timeroverflow interrupts occurs each 1.137 seconds
|
||||
// UART receive interrupts is used to prevent timer overflows
|
||||
ISR(TIMER1_OVF_vect)
|
||||
{
|
||||
state = LOCKED;
|
||||
reset_timeout();
|
||||
update_state(RED, TIMEOUT);
|
||||
}
|
||||
|
||||
// Button Lock
|
||||
ISR(INT0_vect)
|
||||
static inline void timer_init(void)
|
||||
{
|
||||
cli();
|
||||
|
||||
// This code is used to prevent spurious interrupts
|
||||
_delay_ms(50);
|
||||
if (!is_button_lock())
|
||||
goto out;
|
||||
|
||||
uart_putc(DOOR_BUTTON_LOCK);
|
||||
state = LOCKED;
|
||||
|
||||
out:
|
||||
sei();
|
||||
TIMSK |= (1 << TOIE1);
|
||||
TIFR |= (1 << TOV1);
|
||||
TCCR1A = 0;
|
||||
TCCR1B = (1 << CS12);
|
||||
}
|
||||
|
||||
// Button Unlock
|
||||
ISR(INT1_vect)
|
||||
static inline void setup_ports(void)
|
||||
{
|
||||
cli();
|
||||
PORTB = 0;
|
||||
DDRB = (1 << PB4) | (1 << PB1) | (1 << PB0);
|
||||
|
||||
// This code is used to prevent spurious interrupts
|
||||
_delay_ms(50);
|
||||
if (!is_button_unlock())
|
||||
goto out;
|
||||
PORTD = 0;
|
||||
DDRD = (1 << PD5) | (1 << PD6);
|
||||
}
|
||||
|
||||
uart_putc(DOOR_BUTTON_UNLOCK);
|
||||
static inline bool is_emergency(void)
|
||||
{
|
||||
return !(PINB & (1 << PB3));
|
||||
}
|
||||
|
||||
if (state == LOCKED) {
|
||||
bolzen_off();
|
||||
_delay_ms(3000);
|
||||
}
|
||||
static inline bool is_door_open(void)
|
||||
{
|
||||
return !(PINB & (1 << PB2));
|
||||
}
|
||||
|
||||
out:
|
||||
sei();
|
||||
static unsigned char get_keys(void)
|
||||
{
|
||||
unsigned char ret = 0;
|
||||
|
||||
if (!(PIND & (1 << PD2))) ret |= RED;
|
||||
if (!(PIND & (1 << PD3))) ret |= YELLOW;
|
||||
if (!(PIND & (1 << PD4))) ret |= GREEN;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
// Disable all interrupts
|
||||
cli();
|
||||
// Init IO
|
||||
io_init();
|
||||
unsigned char i;
|
||||
|
||||
// Wait a bit to settle down
|
||||
_delay_ms(1000);
|
||||
|
||||
// Init Uart
|
||||
uart_init();
|
||||
uart_set_recv_handler(uart_handler);
|
||||
|
||||
// Init Timer
|
||||
setup_ports();
|
||||
timer_init();
|
||||
uart_init();
|
||||
|
||||
set_bolzen(true);
|
||||
reset_timeout();
|
||||
|
||||
// Init external interrupts
|
||||
extint_init();
|
||||
|
||||
// Enable all interrupts
|
||||
sei();
|
||||
|
||||
for(;;) {
|
||||
if (state == LOCKED) {
|
||||
bolzen_on();
|
||||
status_off();
|
||||
schnapper = false;
|
||||
|
||||
// Check if someone used the emergency unlock
|
||||
if (is_emergency_unlock()) {
|
||||
|
||||
// If so, wait 200ms and double check
|
||||
_delay_ms(200);
|
||||
if (is_emergency_unlock()) {
|
||||
uart_putc(DOOR_EMERGENCY_UNLOCK);
|
||||
cli();
|
||||
|
||||
bolzen_off();
|
||||
schnapper_on();
|
||||
|
||||
_delay_ms(3000);
|
||||
|
||||
schnapper_off();
|
||||
bolzen_on();
|
||||
|
||||
sei();
|
||||
}
|
||||
}
|
||||
} else if (state == UNLOCKED) {
|
||||
bolzen_off();
|
||||
status_on();
|
||||
if (schnapper == true) {
|
||||
schnapper = false;
|
||||
schnapper_on();
|
||||
_delay_ms(2000);
|
||||
schnapper_off();
|
||||
}
|
||||
for (;;) {
|
||||
if (is_emergency()) {
|
||||
update_state(GREEN, EMERGENCY);
|
||||
} else {
|
||||
i = get_keys();
|
||||
if (i & GREEN)
|
||||
update_state(GREEN, BUTTON);
|
||||
else if (i & YELLOW)
|
||||
update_state(YELLOW, BUTTON);
|
||||
else if (i & RED)
|
||||
update_state(RED, BUTTON);
|
||||
}
|
||||
set_leds();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
16
avr-code/protocol.h
Normal file
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* doorlock-avr, AVR code of Binary Kitchen's doorlock
|
||||
*
|
||||
* Copyright (c) Binary Kitchen, 2019
|
||||
*
|
||||
* Authors:
|
||||
* Ralf Ramsauer <ralf@binary-kitchen.de>
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2. See
|
||||
* the COPYING file in the top-level directory.
|
||||
*/
|
||||
|
||||
#define AVR_STATE_SWITCH_RED 'r'
|
||||
#define AVR_STATE_SWITCH_YELLOW 'y'
|
||||
#define AVR_STATE_SWITCH_GREEN 'g'
|
||||
#define AVR_EMERGENCY 'E'
|
|
@ -1,27 +1,30 @@
|
|||
/*
|
||||
* doorlock-avr, AVR code of Binary Kitchen's doorlock
|
||||
*
|
||||
* Copyright (c) Binary Kitchen, 2018
|
||||
*
|
||||
* Authors:
|
||||
* Ralf Ramsauer <ralf@binary-kitchen.de>
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2. See
|
||||
* the COPYING file in the top-level directory.
|
||||
*/
|
||||
|
||||
#include <stddef.h>
|
||||
#include <avr/interrupt.h>
|
||||
#include <avr/io.h>
|
||||
|
||||
#include "uart.h"
|
||||
|
||||
#define UBRR_VAL ((F_CPU+BAUD*8)/(BAUD*16)-1)
|
||||
#define BAUD_REAL (F_CPU/(16*(UBRR_VAL+1)))
|
||||
#define BAUD_ERROR ((BAUD_REAL*1000)/BAUD)
|
||||
|
||||
#if ((BAUD_ERROR<990) || (BAUD_ERROR>1010))
|
||||
#define UBRR_VAL ((F_OSC+UART_BAUD*8)/(UART_BAUD*16)-1)
|
||||
#define BAUD_REAL (F_OSC/(16*(UBRR_VAL+1)))
|
||||
#define BAUD_ERROR ((BAUD_REAL*1000)/UART_BAUD)
|
||||
|
||||
#if ((BAUD_ERROR<985) || (BAUD_ERROR>1010))
|
||||
#warn BAUD_ERROR
|
||||
#error Choose another crystal. Baud error too high.
|
||||
#endif
|
||||
|
||||
static void (*recv_handler)(unsigned char c) = NULL;
|
||||
|
||||
ISR(USART_RX_vect)
|
||||
{
|
||||
cli();
|
||||
if(recv_handler)
|
||||
recv_handler(UDR);
|
||||
sei();
|
||||
}
|
||||
|
||||
void uart_init()
|
||||
{
|
||||
// Enable receive and transmit
|
||||
|
@ -31,12 +34,7 @@ void uart_init()
|
|||
UCSRC = (1<<UCSZ1)|(1<<UCSZ0);
|
||||
|
||||
UBRRH = UBRR_VAL >> 8;
|
||||
UBRRL = UBRR_VAL & 0xFF;
|
||||
}
|
||||
|
||||
void uart_set_recv_handler(void (*handler)(unsigned char c))
|
||||
{
|
||||
recv_handler = handler;
|
||||
UBRRL = UBRR_VAL & 0xFF;
|
||||
}
|
||||
|
||||
void uart_putc(const char c)
|
||||
|
|
|
@ -1,7 +1,14 @@
|
|||
#ifndef UART_H
|
||||
#define UART_H
|
||||
|
||||
#define BAUD 9600UL
|
||||
/*
|
||||
* doorlock-avr, AVR code of Binary Kitchen's doorlock
|
||||
*
|
||||
* Copyright (c) Binary Kitchen, 2018
|
||||
*
|
||||
* Authors:
|
||||
* Ralf Ramsauer <ralf@binary-kitchen.de>
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2. See
|
||||
* the COPYING file in the top-level directory.
|
||||
*/
|
||||
|
||||
void uart_set_recv_handler(void (*handler)(unsigned char c));
|
||||
|
||||
|
@ -9,5 +16,3 @@ void uart_init(void);
|
|||
|
||||
void uart_putc(const char c);
|
||||
void uart_puts(const char* str);
|
||||
|
||||
#endif
|
||||
|
|
BIN
cad/cable_guide.par
Normal file
BIN
cad/case.asm
Normal file
BIN
cad/case.cfg
Normal file
BIN
cad/case_bottom.par
Normal file
BIN
cad/case_bottom.stl
Normal file
BIN
cad/case_top.par
Normal file
BIN
cad/case_top.stl
Normal file
BIN
cad/din_en_iso_10642_m3x12.par
Normal file
BIN
cad/switch.par
Normal file
BIN
cad/switch_nut.par
Normal file
14
doorcmds.h
|
@ -1,14 +0,0 @@
|
|||
#ifndef DOORCMD_H
|
||||
#define DOORCMD_H
|
||||
|
||||
#define DOOR_CMD_UNLOCK 'u'
|
||||
#define DOOR_CMD_LOCK 'l'
|
||||
#define DOOR_CMD_SCHNAPER 's'
|
||||
#define DOOR_CMD_PING 'p'
|
||||
#define DOOR_CMD_STATUS 'i'
|
||||
|
||||
#define DOOR_BUTTON_UNLOCK 'U'
|
||||
#define DOOR_BUTTON_LOCK 'L'
|
||||
#define DOOR_EMERGENCY_UNLOCK 'E'
|
||||
|
||||
#endif
|
78
doorlockd
Executable file
|
@ -0,0 +1,78 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
"""
|
||||
Doorlockd -- Binary Kitchen's smart door opener
|
||||
|
||||
Copyright (c) Binary Kitchen e.V., 2018-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 logging
|
||||
import sys
|
||||
|
||||
from configparser import ConfigParser
|
||||
from os.path import abspath, join
|
||||
|
||||
from pydoorlock.Authenticator import Authenticator
|
||||
from pydoorlock.WebApp import webapp_run, emit_doorstate
|
||||
from pydoorlock.Doorlock import DoorlockResponse, DoorHandler
|
||||
from pydoorlock.Config import Config, root_prefix, sounds_prefix
|
||||
|
||||
__author__ = 'Ralf Ramsauer'
|
||||
__copyright = 'Copyright (c) Ralf Ramsauer, 2018-2019'
|
||||
__license__ = 'GPLv2'
|
||||
__email__ = 'ralf@binary-kitchen.de'
|
||||
__status__ = 'Development'
|
||||
__maintainer__ = 'Ralf Ramsauer'
|
||||
__version__ = '0.0'
|
||||
|
||||
log_level = logging.DEBUG
|
||||
date_fmt = '%Y-%m-%d %H:%M:%S'
|
||||
log_fmt = '%(asctime)-15s %(levelname)-8s %(message)s'
|
||||
log = logging.getLogger()
|
||||
|
||||
cfg = Config('doorlockd')
|
||||
|
||||
|
||||
class Logic:
|
||||
def __init__(self, cfg, sounds_prefix, scripts_prefix, callback):
|
||||
self.auth = Authenticator(cfg)
|
||||
self.door_handler = DoorHandler(cfg, sounds_prefix, scripts_prefix)
|
||||
self.door_handler.register_callback(callback)
|
||||
|
||||
def request(self, state, credentials):
|
||||
err = self.auth.try_auth(credentials)
|
||||
if err != DoorlockResponse.Success:
|
||||
return err
|
||||
|
||||
return self.door_handler.request(state)
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
return self.door_handler.state
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
logging.basicConfig(level=log_level, stream=sys.stdout,
|
||||
format=log_fmt, datefmt=date_fmt)
|
||||
log.info('Starting doorlockd')
|
||||
|
||||
scripts_prefix = join(root_prefix, 'scripts')
|
||||
logic = Logic(cfg, sounds_prefix, scripts_prefix, emit_doorstate)
|
||||
|
||||
static_folder = abspath(join(root_prefix, 'static'))
|
||||
template_folder = abspath(join(root_prefix, 'templates'))
|
||||
webapp_run(cfg, logic, __status__, __version__, template_folder,
|
||||
static_folder)
|
||||
|
||||
sys.exit(0)
|
40
doorlockd-passwd
Executable file
|
@ -0,0 +1,40 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
"""
|
||||
Doorlockd -- Binary Kitchen's smart door opener
|
||||
|
||||
Copyright (c) Binary Kitchen e.V., 2018
|
||||
|
||||
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 getpass
|
||||
import hashlib
|
||||
import uuid
|
||||
import sys
|
||||
|
||||
if len(sys.argv) != 3:
|
||||
print('Usage: %s db username' % sys.argv[0])
|
||||
quit(-1)
|
||||
|
||||
username = sys.argv[2]
|
||||
try:
|
||||
password = getpass.getpass()
|
||||
except Exception as error:
|
||||
print('ERROR', error)
|
||||
quit(-1)
|
||||
|
||||
salt = uuid.uuid4().hex
|
||||
password = hashlib.sha256(salt.encode() + password.encode()).hexdigest() + ':' + salt
|
||||
|
||||
with open(sys.argv[1], 'a') as file:
|
||||
file.write('%s %s\n' % (username, password))
|
|
@ -1,124 +0,0 @@
|
|||
cmake_minimum_required(VERSION 2.8)
|
||||
project(doorlockd)
|
||||
|
||||
option(USE_COLORIZED_LOGS "Colorized logging" ON)
|
||||
|
||||
set(DOORLOCK_VERSION_MAJOR 1)
|
||||
set(DOORLOCK_VERSION_MINOR 2)
|
||||
set(DOORLOCK_VERSION_PATCH 1)
|
||||
|
||||
set(DOORLOCK_VERSION "${DOORLOCK_VERSION_MAJOR}.${DOORLOCK_VERSION_MINOR}-${DOORLOCK_VERSION_PATCH}")
|
||||
|
||||
# Instruct CMake to run moc automatically when needed.
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
set(CMAKE_AUTOUIC ON)
|
||||
|
||||
MESSAGE(STATUS "doorlockd version: ${DOORLOCK_VERSION}")
|
||||
|
||||
# Get the current working branch
|
||||
execute_process(
|
||||
COMMAND git rev-parse --abbrev-ref HEAD
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||
OUTPUT_VARIABLE GIT_BRANCH
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
set(GIT_BRANCH "\"${GIT_BRANCH}\"")
|
||||
|
||||
# Get the latest abbreviated commit hash of the working branch
|
||||
execute_process(
|
||||
COMMAND git log -1 --format=%h
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||
OUTPUT_VARIABLE GIT_COMMIT_HASH
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
set(GIT_COMMIT_HASH "\"${GIT_COMMIT_HASH}\"")
|
||||
|
||||
add_definitions(-std=c++11)
|
||||
|
||||
configure_file (
|
||||
"${PROJECT_SOURCE_DIR}/config.h.in"
|
||||
"${PROJECT_BINARY_DIR}/config.h"
|
||||
)
|
||||
|
||||
include_directories(${PROJECT_BINARY_DIR})
|
||||
include_directories(${Boost_INCLUDE_DIRS})
|
||||
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "-O0 -ggdb -Wall -pedantic -Weffc++ -Wextra")
|
||||
set(CMAKE_CXX_FLAGS "-O2 -Wall -pedantic -Wextra -Weffc++")
|
||||
set(CMAKE_C_FLAGS_DEBUG "-O0 -ggdb -Wall -pedantic -Wextra")
|
||||
set(CMAKE_C_FLAGS "-O2 -Wall -pedantic -Wextra")
|
||||
|
||||
find_package(Boost 1.55.0 COMPONENTS program_options system REQUIRED)
|
||||
|
||||
set(JSON_INCLUDE_DIR "/usr/include/jsoncpp" CACHE PATH "path to jsoncpp includes")
|
||||
include_directories(${JSON_INCLUDE_DIR})
|
||||
|
||||
find_package (Threads)
|
||||
|
||||
find_package(Qt5Widgets)
|
||||
|
||||
set(LIBDOORLOCK_SRCS
|
||||
lib/clientmessage.cpp
|
||||
lib/clientmessage.h
|
||||
lib/door.cpp
|
||||
lib/door.h
|
||||
lib/doormessage.cpp
|
||||
lib/doormessage.h
|
||||
lib/logger.cpp
|
||||
lib/logger.h
|
||||
lib/logic.cpp
|
||||
lib/logic.h
|
||||
lib/request.cpp
|
||||
lib/request.h
|
||||
lib/response.cpp
|
||||
lib/response.h
|
||||
lib/util.cpp
|
||||
lib/util.h)
|
||||
|
||||
set(DOORLOCKD_SRCS
|
||||
daemon/doorlockd.cpp)
|
||||
|
||||
set(DOORLOCK_CLIENT_SRCS
|
||||
client/qrwidget.cpp
|
||||
client/qrwidget.h
|
||||
client/doorlock-client.cpp
|
||||
client/mainwindow.h
|
||||
client/mainwindow.cpp
|
||||
client/mainwindow.ui
|
||||
client/wave.h
|
||||
client/wave.cpp)
|
||||
|
||||
add_library(doorlock STATIC ${LIBDOORLOCK_SRCS})
|
||||
target_link_libraries(doorlock jsoncpp ldap ${Boost_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
|
||||
|
||||
add_executable(doorlockd ${DOORLOCKD_SRCS})
|
||||
target_link_libraries(doorlockd doorlock)
|
||||
|
||||
add_executable(doorlock-client ${DOORLOCK_CLIENT_SRCS})
|
||||
target_link_libraries(doorlock-client doorlock qrencode ao sndfile Qt5::Widgets)
|
||||
target_include_directories(doorlock-client PRIVATE ${CMAKE_SOURCE_DIR})
|
||||
|
||||
install(TARGETS doorlockd DESTINATION sbin/)
|
||||
install(TARGETS doorlock-client DESTINATION bin/)
|
||||
|
||||
install(DIRECTORY images/ DESTINATION share/doorlockd/images
|
||||
FILES_MATCHING PATTERN "images/*.png"
|
||||
PERMISSIONS WORLD_READ OWNER_READ GROUP_READ)
|
||||
|
||||
install(DIRECTORY sounds/ DESTINATION share/doorlockd/sounds
|
||||
FILES_MATCHING PATTERN "sounds/*.wav"
|
||||
PERMISSIONS WORLD_READ OWNER_READ GROUP_READ)
|
||||
|
||||
install(FILES scripts/doorlockd.service DESTINATION /etc/systemd/system/)
|
||||
|
||||
install(DIRECTORY scripts/ DESTINATION etc/doorlockd/
|
||||
FILES_MATCHING PATTERN "scripts/pre_*lock"
|
||||
PERMISSIONS WORLD_EXECUTE WORLD_READ OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE OWNER_WRITE)
|
||||
|
||||
install(DIRECTORY scripts/ DESTINATION etc/doorlockd/
|
||||
FILES_MATCHING PATTERN "scripts/post_*lock"
|
||||
PERMISSIONS WORLD_EXECUTE WORLD_READ OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE OWNER_WRITE)
|
||||
|
||||
install(DIRECTORY scripts/ DESTINATION etc/doorlockd/
|
||||
FILES_MATCHING PATTERN "scripts/emergency_unlock"
|
||||
PERMISSIONS WORLD_EXECUTE WORLD_READ OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE OWNER_WRITE)
|
|
@ -1,217 +0,0 @@
|
|||
#include <exception>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
#include <ao/ao.h>
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/program_options.hpp>
|
||||
|
||||
#include <QApplication>
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "../lib/clientmessage.h"
|
||||
#include "../lib/logger.h"
|
||||
#include "../lib/response.h"
|
||||
|
||||
#include "mainwindow.h"
|
||||
|
||||
// Info about doorlock-client version
|
||||
const static std::string version =
|
||||
"doorlock-client-" DOORLOCK_VERSION;
|
||||
const static std::string gitversion =
|
||||
DOORLOCK_GIT_BRANCH "-" DOORLOCK_GIT_COMMIT_HASH;
|
||||
|
||||
static Logger &l = Logger::get();
|
||||
|
||||
namespace po = boost::program_options;
|
||||
namespace ba = boost::asio;
|
||||
using ba::ip::tcp;
|
||||
|
||||
static ba::io_service io_service;
|
||||
|
||||
const static std::string subscriptionCommand =
|
||||
"{ \"command\": \"subscribe\"}";
|
||||
|
||||
// The receive buffer length of the TCP socket
|
||||
constexpr static int SOCKET_BUFFERLENGTH = 2048;
|
||||
|
||||
static volatile bool run = true;
|
||||
|
||||
static std::unique_ptr<MainWindow> mainWindow = nullptr;
|
||||
|
||||
static void onDoorlockUpdate(const Clientmessage &msg)
|
||||
{
|
||||
const auto& doormessage = msg.doormessage();
|
||||
l("Received message", LogLevel::info);
|
||||
l((std::string)" token: " + msg.token(),
|
||||
LogLevel::info);
|
||||
l((std::string)" open: " + std::to_string(msg.isOpen()),
|
||||
LogLevel::info);
|
||||
l((std::string)" button lock: " + std::to_string(doormessage.isLockButton),
|
||||
LogLevel::info);
|
||||
l((std::string)" button unlock: " + std::to_string(doormessage.isUnlockButton),
|
||||
LogLevel::info);
|
||||
l((std::string)" emergency open: " + std::to_string(doormessage.isEmergencyUnlock),
|
||||
LogLevel::info);
|
||||
if (mainWindow) {
|
||||
mainWindow->setClientmessage(msg);
|
||||
}
|
||||
}
|
||||
|
||||
static int doorlock_client(const std::string &hostname,
|
||||
const unsigned short port)
|
||||
{
|
||||
int retval = 0;
|
||||
|
||||
try {
|
||||
tcp::resolver resolver(io_service);
|
||||
tcp::socket socket(io_service);
|
||||
tcp::resolver::query query(hostname, std::to_string(port));
|
||||
tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);
|
||||
tcp::resolver::iterator end;
|
||||
boost::system::error_code error = ba::error::host_not_found;
|
||||
std::vector<char> data;
|
||||
|
||||
while (error && endpoint_iterator != end) {
|
||||
socket.close();
|
||||
socket.connect(*endpoint_iterator++, error);
|
||||
}
|
||||
if (error)
|
||||
throw boost::system::system_error(error);
|
||||
|
||||
// After connection is established, send the subscription command
|
||||
socket.write_some(ba::buffer(subscriptionCommand), error);
|
||||
if (error)
|
||||
throw boost::system::system_error(error);
|
||||
|
||||
data.resize(SOCKET_BUFFERLENGTH);
|
||||
|
||||
std::function<void(void)> receiveMessage = [&] () {
|
||||
socket.async_read_some(ba::buffer(data),
|
||||
[&] (const boost::system::error_code &ec,
|
||||
const size_t length)
|
||||
{
|
||||
if (ec) {
|
||||
throw boost::system::system_error(ec);
|
||||
}
|
||||
|
||||
const auto message = Clientmessage::fromString(
|
||||
std::string(data.begin(), data.begin()+length));
|
||||
onDoorlockUpdate(message);
|
||||
|
||||
receiveMessage();
|
||||
});
|
||||
};
|
||||
|
||||
receiveMessage();
|
||||
io_service.run();
|
||||
}
|
||||
catch(const Response &err) {
|
||||
l(err.message, LogLevel::error);
|
||||
retval = -1;
|
||||
}
|
||||
catch(const std::exception &err) {
|
||||
l(LogLevel::error, err.what());
|
||||
retval = -1;
|
||||
}
|
||||
|
||||
io_service.reset();
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
std::string hostname;
|
||||
unsigned short port;
|
||||
|
||||
l((std::string)"Hello, this is " + version + " built on " + gitversion,
|
||||
LogLevel::info);
|
||||
|
||||
try {
|
||||
po::options_description desc("doorlockd (" + version + " built on " + gitversion + ")");
|
||||
desc.add_options()
|
||||
("help,h",
|
||||
"print help")
|
||||
("port,p",
|
||||
po::value<unsigned short>(&port)->default_value(DEFAULT_PORT),
|
||||
"Port")
|
||||
("host,c",
|
||||
po::value<std::string>(&hostname)->default_value("localhost"),
|
||||
"IP or name of host running doorlockd");
|
||||
|
||||
po::variables_map vm;
|
||||
po::store(po::command_line_parser(argc, argv).options(desc).run(), vm);
|
||||
|
||||
if (vm.count("help"))
|
||||
{
|
||||
std::cout << desc << std::endl;
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
po::notify(vm);
|
||||
}
|
||||
catch(const std::exception &e)
|
||||
{
|
||||
l(LogLevel::error, e.what());
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
l(LogLevel::notice, "Starting doorlock-client");
|
||||
|
||||
QApplication app(argc, argv);
|
||||
app.setOrganizationName("Binary Kitchen");
|
||||
app.setApplicationName("doorlock-client");
|
||||
|
||||
ao_initialize();
|
||||
|
||||
try {
|
||||
mainWindow = std::unique_ptr<MainWindow>(new MainWindow);
|
||||
mainWindow->showFullScreen();
|
||||
}
|
||||
catch(const std::exception &e)
|
||||
{
|
||||
l(LogLevel::error, e.what());
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
// Start the TCP client as thread
|
||||
std::thread clientThread = std::thread([&] () {
|
||||
// If the TCP client returns, an error has occured
|
||||
// In normal operation, it never returns
|
||||
while (run) {
|
||||
doorlock_client(hostname, port);
|
||||
if (run) {
|
||||
l(LogLevel::error, "client aborted, retrying in 5 seconds");
|
||||
// Todo: Write message to QT frontend
|
||||
|
||||
sleep(5);
|
||||
}
|
||||
}
|
||||
|
||||
// This will stop the Qapplication
|
||||
mainWindow->hide();
|
||||
mainWindow->close();
|
||||
});
|
||||
|
||||
// This routine will never return in normal operation
|
||||
app.exec();
|
||||
|
||||
run = false;
|
||||
|
||||
// Stop the IO service
|
||||
io_service.stop();
|
||||
|
||||
clientThread.join();
|
||||
|
||||
if (mainWindow)
|
||||
mainWindow.reset();
|
||||
|
||||
ao_shutdown();
|
||||
|
||||
l(LogLevel::notice, "Stopping doorlock-client");
|
||||
return 0;
|
||||
}
|
|
@ -1,75 +0,0 @@
|
|||
#include "config.h"
|
||||
|
||||
#include "mainwindow.h"
|
||||
#include "ui_mainwindow.h"
|
||||
|
||||
MainWindow::MainWindow(QWidget *parent) :
|
||||
QWidget(parent),
|
||||
ui(new Ui::MainWindow),
|
||||
_soundLock(Wave::fromFile(SOUND_LOCK)),
|
||||
_soundUnlock(Wave::fromFile(SOUND_UNLOCK)),
|
||||
_soundEmergencyUnlock(Wave::fromFile(SOUND_EMERGENCY_UNLOCK)),
|
||||
_soundZonk(Wave::fromFile(SOUND_ZONK)),
|
||||
_soundLockButton(Wave::fromFile(SOUND_LOCK_BUTTON)),
|
||||
_soundUnlockButton(Wave::fromFile(SOUND_UNLOCK_BUTTON))
|
||||
{
|
||||
ui->setupUi(this);
|
||||
_LED(false);
|
||||
}
|
||||
|
||||
MainWindow::~MainWindow()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void MainWindow::setClientmessage(const Clientmessage &msg)
|
||||
{
|
||||
ui->qrwidget->setQRData(msg.token());
|
||||
ui->tokenLabel->setText(QString::fromStdString(msg.token()));
|
||||
QString statusMessage("");
|
||||
|
||||
const auto &doormsg = msg.doormessage();
|
||||
|
||||
_LED(msg.isOpen());
|
||||
|
||||
if (_oldMessage.isOpen()
|
||||
&& !msg.isOpen()
|
||||
&& !doormsg.isLockButton) {
|
||||
// regular close
|
||||
statusMessage = "Bye bye. See you next time!";
|
||||
_soundLock.playAsync();
|
||||
} else if (!_oldMessage.isOpen() && msg.isOpen()) {
|
||||
// regular open
|
||||
statusMessage = "Come in! Happy hacking!";
|
||||
_soundUnlock.playAsync();
|
||||
} else {
|
||||
// no change
|
||||
}
|
||||
|
||||
if (doormsg.isEmergencyUnlock) {
|
||||
_soundEmergencyUnlock.playAsync();
|
||||
statusMessage = "!! EMERGENCY UNLOCK !!";
|
||||
} else if (doormsg.isLockButton) {
|
||||
_soundLockButton.playAsync();
|
||||
statusMessage = "!! LOCK BUTTON !!";
|
||||
} else if (doormsg.isUnlockButton) {
|
||||
statusMessage = "!! UNLOCK BUTTON !!";
|
||||
if (msg.isOpen()) {
|
||||
_soundZonk.playAsync();
|
||||
} else {
|
||||
_soundUnlockButton.playAsync();
|
||||
}
|
||||
}
|
||||
|
||||
ui->message->setText(statusMessage);
|
||||
|
||||
_oldMessage = msg;
|
||||
}
|
||||
|
||||
void MainWindow::_LED(const bool on)
|
||||
{
|
||||
if (on)
|
||||
ui->LED->setPixmap(QPixmap(IMAGE_LED_GREEN));
|
||||
else
|
||||
ui->LED->setPixmap(QPixmap(IMAGE_LED_RED));
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
#ifndef MAINWINDOW_H
|
||||
#define MAINWINDOW_H
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
#include "../lib/clientmessage.h"
|
||||
#include "wave.h"
|
||||
|
||||
namespace Ui {
|
||||
class MainWindow;
|
||||
}
|
||||
|
||||
class MainWindow : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit MainWindow(QWidget *parent = 0);
|
||||
|
||||
MainWindow(const MainWindow &rhs);
|
||||
MainWindow &operator =(const MainWindow &rhs);
|
||||
|
||||
~MainWindow();
|
||||
|
||||
void setClientmessage(const Clientmessage &msg);
|
||||
|
||||
private:
|
||||
Ui::MainWindow *ui;
|
||||
|
||||
Clientmessage _oldMessage = { };
|
||||
|
||||
const Wave _soundLock;
|
||||
const Wave _soundUnlock;
|
||||
const Wave _soundEmergencyUnlock;
|
||||
const Wave _soundZonk;
|
||||
const Wave _soundLockButton;
|
||||
const Wave _soundUnlockButton;
|
||||
|
||||
void _LED(const bool on);
|
||||
};
|
||||
|
||||
#endif // MAINWINDOW_H
|
|
@ -1,143 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>MainWindow</class>
|
||||
<widget class="QWidget" name="MainWindow">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>800</width>
|
||||
<height>400</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0" alignment="Qt::AlignHCenter">
|
||||
<widget class="QLabel" name="welcomeLabel">
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>36</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Willkommen in der Binary Kitchen!</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<property name="sizeConstraint">
|
||||
<enum>QLayout::SetFixedSize</enum>
|
||||
</property>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRWidget" name="qrwidget" native="true"/>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item alignment="Qt::AlignHCenter">
|
||||
<widget class="QLabel" name="LED">
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item alignment="Qt::AlignHCenter">
|
||||
<widget class="QLabel" name="message">
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>28</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="tokenLabel">
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>18</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="Line" name="line">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>QRWidget</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>client/qrwidget.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
|
@ -1,63 +0,0 @@
|
|||
#include <exception>
|
||||
#include <memory>
|
||||
|
||||
#include <QPainter>
|
||||
#include <QDebug>
|
||||
|
||||
#include <qrencode.h>
|
||||
|
||||
#include "qrwidget.h"
|
||||
|
||||
QRWidget::QRWidget(QWidget* parent) :
|
||||
QWidget(parent),
|
||||
_data(" ")
|
||||
{
|
||||
}
|
||||
|
||||
void QRWidget::setQRData(const std::string &data)
|
||||
{
|
||||
_data = data;
|
||||
update();
|
||||
}
|
||||
|
||||
void QRWidget::paintEvent(QPaintEvent*)
|
||||
{
|
||||
QPainter painter(this);
|
||||
|
||||
std::unique_ptr<QRcode, void(*)(QRcode*)> qr(
|
||||
QRcode_encodeString(_data.c_str(), 1, QR_ECLEVEL_L, QR_MODE_8, 1),
|
||||
[] (QRcode* ptr) {
|
||||
if (ptr)
|
||||
QRcode_free(ptr);
|
||||
});
|
||||
|
||||
if (!qr)
|
||||
throw std::runtime_error("Error generating QR Code");
|
||||
|
||||
QColor fg("black");
|
||||
QColor bg("white");
|
||||
|
||||
painter.setBrush(bg);
|
||||
painter.setPen(Qt::NoPen);
|
||||
painter.drawRect(0,0,width(),height());
|
||||
painter.setBrush(fg);
|
||||
|
||||
const int s=qr->width>0?qr->width:1;
|
||||
const double w=width();
|
||||
const double h=height();
|
||||
const double aspect=w/h;
|
||||
const double scale=((aspect>1.0)?h:w)/s;
|
||||
|
||||
for(int y=0;y<s;y++) {
|
||||
const int yy=y*s;
|
||||
for(int x=0;x<s;x++) {
|
||||
const int xx=yy+x;
|
||||
const unsigned char b=qr->data[xx];
|
||||
if(b &0x01) {
|
||||
const double rx1=x*scale, ry1=y*scale;
|
||||
QRectF r(rx1, ry1, scale, scale);
|
||||
painter.drawRects(&r,1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
#ifndef QRWIDGET_H
|
||||
#define QRWIDGET_H
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
class QRWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit QRWidget(QWidget *parent = nullptr);
|
||||
void setQRData(const std::string &data);
|
||||
|
||||
private:
|
||||
std::string _data;
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *);
|
||||
};
|
||||
|
||||
#endif
|
|
@ -1,95 +0,0 @@
|
|||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <stdexcept>
|
||||
#include <thread>
|
||||
|
||||
#include <sndfile.h>
|
||||
|
||||
#include "wave.h"
|
||||
|
||||
Wave::Wave(int bits,
|
||||
int channels,
|
||||
int rate,
|
||||
Raw data) :
|
||||
_format(new ao_sample_format),
|
||||
_data(data)
|
||||
{
|
||||
_format->bits = bits;
|
||||
_format->rate = rate;
|
||||
_format->channels = channels;
|
||||
_format->byte_format = AO_FMT_LITTLE;
|
||||
_format->matrix = nullptr;
|
||||
|
||||
int default_driver;
|
||||
default_driver = ao_default_driver_id();
|
||||
_device = ao_open_live(default_driver,
|
||||
_format.get(),
|
||||
nullptr);
|
||||
}
|
||||
|
||||
Wave::~Wave()
|
||||
{
|
||||
if (_device != nullptr)
|
||||
ao_close(_device);
|
||||
}
|
||||
|
||||
Wave Wave::fromFile(const std::string &filename)
|
||||
{
|
||||
SF_INFO sfinfo;
|
||||
SNDFILE *file = sf_open(filename.c_str(), SFM_READ, &sfinfo);
|
||||
if (file == nullptr)
|
||||
throw std::runtime_error("Unable to open soundfile " + filename);
|
||||
|
||||
size_t rawSize = sfinfo.channels * sfinfo.frames * sizeof(short);
|
||||
Raw data;
|
||||
data.resize(rawSize);
|
||||
|
||||
sf_read_raw(file, &data.front(), data.size());
|
||||
sf_close(file);
|
||||
|
||||
int bits;
|
||||
switch (sfinfo.format & SF_FORMAT_SUBMASK) {
|
||||
case SF_FORMAT_PCM_16:
|
||||
bits = 16;
|
||||
break;
|
||||
case SF_FORMAT_PCM_24:
|
||||
bits = 24;
|
||||
break;
|
||||
case SF_FORMAT_PCM_32:
|
||||
bits = 32;
|
||||
break;
|
||||
case SF_FORMAT_PCM_S8:
|
||||
bits = 8;
|
||||
break;
|
||||
case SF_FORMAT_PCM_U8:
|
||||
bits = 8;
|
||||
break;
|
||||
default:
|
||||
bits = 16;
|
||||
break;
|
||||
}
|
||||
|
||||
return Wave(bits,
|
||||
sfinfo.channels,
|
||||
sfinfo.samplerate,
|
||||
data);
|
||||
}
|
||||
|
||||
void Wave::play() const
|
||||
{
|
||||
std::lock_guard<std::mutex> l(_playMutex);
|
||||
if (_device == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
ao_play(_device,
|
||||
(char*)&_data.front(),
|
||||
_data.size());
|
||||
}
|
||||
|
||||
void Wave::playAsync() const
|
||||
{
|
||||
std::thread([this] () {
|
||||
play();
|
||||
}).detach();
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
#ifndef WAVE_H
|
||||
#define WAVE_H
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <ao/ao.h>
|
||||
|
||||
class Wave {
|
||||
public:
|
||||
|
||||
using Raw = std::vector<char>;
|
||||
|
||||
static Wave fromFile(const std::string &filename);
|
||||
Wave(int bits,
|
||||
int channels,
|
||||
int rate,
|
||||
Raw data);
|
||||
Wave(const Wave &rhs);
|
||||
~Wave();
|
||||
|
||||
Wave &operator=(const Wave &rhs);
|
||||
|
||||
void play() const;
|
||||
void playAsync() const;
|
||||
|
||||
private:
|
||||
|
||||
mutable std::mutex _playMutex = { };
|
||||
|
||||
std::unique_ptr<ao_sample_format> _format;
|
||||
ao_device* _device = { nullptr };
|
||||
|
||||
const Raw _data;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -1,50 +0,0 @@
|
|||
#ifndef CONFIG_H
|
||||
#define CONFIG_H
|
||||
|
||||
#cmakedefine USE_COLORIZED_LOGS
|
||||
|
||||
#define DOORLOCK_VERSION_MAJOR "@DOORLOCK_VERSION_MAJOR@"
|
||||
#define DOORLOCK_VERSION_MINOR "@DOORLOCK_VERSION_MINOR@"
|
||||
#define DOORLOCK_VERSION_PATCH "@DOORLOCK_VERSION_PATCH@"
|
||||
#define DOORLOCK_VERSION "@DOORLOCK_VERSION@"
|
||||
|
||||
#define DOORLOCK_GIT_BRANCH @GIT_BRANCH@
|
||||
#define DOORLOCK_GIT_COMMIT_HASH @GIT_COMMIT_HASH@
|
||||
|
||||
#ifdef DEBUG
|
||||
#define DEFAULT_LOG_LEVEL LogLevel::debug2
|
||||
#else
|
||||
#define DEFAULT_LOG_LEVEL LogLevel::info
|
||||
#endif
|
||||
|
||||
#define DEFAULT_TOKEN_TIMEOUT (60*5)
|
||||
#define DEFAULT_PORT 5555
|
||||
#define DEFAULT_WEB_PREFIX "https://lock.binary.kitchen/"
|
||||
#define DEFAULT_LDAP_URI "ldaps://ldap1.binary.kitchen/ ldaps://ldap2.binary.kitchen/ ldaps://ldapm.binary.kitchen/"
|
||||
#define DEFAULT_BINDDN "cn=%s,ou=Users,dc=binary-kitchen,dc=de"
|
||||
#define DEFAULT_LOG_FILE "/var/log/doorlockd.log"
|
||||
#define DEFAULT_SERIAL_DEVICE "/dev/ttyAMA0"
|
||||
#define DEFAULT_SERIAL_BAUDRATE 9600UL
|
||||
|
||||
#define SHARED_LOCATION "@CMAKE_INSTALL_PREFIX@/share/doorlockd/"
|
||||
|
||||
#define IMAGE_LOCATION SHARED_LOCATION "images/"
|
||||
#define IMAGE_LED_GREEN IMAGE_LOCATION "led-green.png"
|
||||
#define IMAGE_LED_RED IMAGE_LOCATION "led-red.png"
|
||||
|
||||
|
||||
#define SOUNDS_LOCATION SHARED_LOCATION "sounds/"
|
||||
#define SOUND_LOCK SOUNDS_LOCATION "lock.wav"
|
||||
#define SOUND_UNLOCK SOUNDS_LOCATION "unlock.wav"
|
||||
#define SOUND_EMERGENCY_UNLOCK SOUNDS_LOCATION "emergency_unlock.wav"
|
||||
#define SOUND_ZONK SOUNDS_LOCATION "zonk.wav"
|
||||
#define SOUND_LOCK_BUTTON SOUNDS_LOCATION "lock_button.wav"
|
||||
#define SOUND_UNLOCK_BUTTON SOUNDS_LOCATION "unlock_button.wav"
|
||||
|
||||
#define PRE_LOCK_SCRIPT "@CMAKE_INSTALL_PREFIX@/etc/doorlockd/pre_lock &"
|
||||
#define POST_LOCK_SCRIPT "@CMAKE_INSTALL_PREFIX@/etc/doorlockd/post_lock &"
|
||||
#define PRE_UNLOCK_SCRIPT "@CMAKE_INSTALL_PREFIX@/etc/doorlockd/pre_unlock &"
|
||||
#define POST_UNLOCK_SCRIPT "@CMAKE_INSTALL_PREFIX@/etc/doorlockd/post_unlock &"
|
||||
#define EMERGENCY_UNLOCK_SCRIPT "@CMAKE_INSTALL_PREFIX@/etc/doorlockd/emergency_unlock &"
|
||||
|
||||
#endif
|
|
@ -1,257 +0,0 @@
|
|||
#include <csignal>
|
||||
#include <iostream>
|
||||
|
||||
#include <boost/program_options.hpp>
|
||||
#include <boost/asio.hpp>
|
||||
|
||||
#include <json/json.h>
|
||||
|
||||
#include "../lib/logic.h"
|
||||
|
||||
#include "config.h"
|
||||
|
||||
namespace po = boost::program_options;
|
||||
namespace ba = boost::asio;
|
||||
using ba::ip::tcp;
|
||||
|
||||
// Info about doorlockd version
|
||||
const static std::string version =
|
||||
"doorlockd-" DOORLOCK_VERSION;
|
||||
const static std::string gitversion =
|
||||
DOORLOCK_GIT_BRANCH "-" DOORLOCK_GIT_COMMIT_HASH;
|
||||
|
||||
// The receive buffer length of the TCP socket
|
||||
const int constexpr SOCKET_BUFFERLENGTH = 2048;
|
||||
|
||||
static Logger &l = Logger::get();
|
||||
|
||||
static std::unique_ptr<Logic> logic = nullptr;
|
||||
static ba::io_service io_service;
|
||||
|
||||
static std::mutex mutex;
|
||||
static std::condition_variable onClientMessage;
|
||||
static volatile bool run = true;
|
||||
|
||||
static void signal_handler(int signum)
|
||||
{
|
||||
l((std::string)"Received Signal " + std::to_string(signum),
|
||||
LogLevel::warning);
|
||||
io_service.stop();
|
||||
run = false;
|
||||
onClientMessage.notify_all();
|
||||
}
|
||||
|
||||
static Response subscribe(tcp::socket &sock)
|
||||
{
|
||||
sock.write_some(ba::buffer(logic->getClientMessage().toJson()));
|
||||
while (run) {
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
onClientMessage.wait(lock);
|
||||
if (run) {
|
||||
sock.write_some(ba::buffer(logic->getClientMessage().toJson()));
|
||||
}
|
||||
};
|
||||
return Response(Response::Code::Success);
|
||||
}
|
||||
|
||||
static void session(tcp::socket &&sock)
|
||||
{
|
||||
ba::ip::address remoteAddress;
|
||||
unsigned short remotePort = 0;
|
||||
Response response;
|
||||
|
||||
try {
|
||||
std::vector<char> data;
|
||||
data.resize(SOCKET_BUFFERLENGTH);
|
||||
|
||||
remoteAddress = sock.remote_endpoint().address();
|
||||
remotePort = sock.remote_endpoint().port();
|
||||
|
||||
l("Incoming TCP connection from " + remoteAddress.to_string() + "("
|
||||
+ std::to_string(remotePort) + ")",
|
||||
LogLevel::notice);
|
||||
|
||||
size_t length = sock.read_some(ba::buffer(data));
|
||||
|
||||
// Get Request
|
||||
const std::string requestString(data.begin(), data.begin()+length);
|
||||
l(" Parsing request...", LogLevel::info);
|
||||
Request request = Request::fromString(requestString);
|
||||
|
||||
switch (request.command) {
|
||||
case Request::Command::Lock:
|
||||
case Request::Command::Unlock:
|
||||
response = logic->request(request);
|
||||
break;
|
||||
|
||||
case Request::Command::Subscribe:
|
||||
if (remoteAddress.is_loopback() == false) {
|
||||
response.code = Response::Code::AccessDenied;
|
||||
response.message = "Subscriptions are only allowed from localhost";
|
||||
} else {
|
||||
response = subscribe(sock);
|
||||
}
|
||||
break;
|
||||
|
||||
case Request::Command::Unknown:
|
||||
default:
|
||||
response.code = Response::Code::UnknownCommand;
|
||||
response.message = "Received unknown command ";
|
||||
break;
|
||||
}
|
||||
|
||||
throw response;
|
||||
}
|
||||
catch (const Response &err) {
|
||||
response = err;
|
||||
}
|
||||
catch (const std::exception &err) {
|
||||
response.code = Response::Code::Fail;
|
||||
response.message = "Exception in session " + remoteAddress.to_string()
|
||||
+ ":" + std::to_string(remotePort) + " : " + err.what();
|
||||
}
|
||||
catch (...) {
|
||||
response.code = Response::Code::Fail;
|
||||
response.message = "Unhandled doorlockd error";
|
||||
}
|
||||
|
||||
if (!response) {
|
||||
l(response.message, LogLevel::warning);
|
||||
}
|
||||
|
||||
if (sock.is_open()) {
|
||||
boost::system::error_code ec;
|
||||
sock.write_some(ba::buffer(response.toJson()), ec);
|
||||
}
|
||||
|
||||
l("Closing TCP connection from " + remoteAddress.to_string(), LogLevel::notice);
|
||||
}
|
||||
|
||||
static void server(unsigned short port)
|
||||
{
|
||||
l(LogLevel::info, "Starting TCP Server");
|
||||
|
||||
const auto endpoint = tcp::endpoint(ba::ip::address::from_string("127.0.0.1"), port);
|
||||
tcp::acceptor a(io_service, endpoint);
|
||||
|
||||
tcp::socket sock(io_service);
|
||||
|
||||
std::function<void(void)> accept_connection = [&] () {
|
||||
a.async_accept(sock,
|
||||
[&] (boost::system::error_code ec) {
|
||||
if (ec)
|
||||
{
|
||||
return;
|
||||
}
|
||||
std::thread(session, std::move(sock)).detach();
|
||||
accept_connection();
|
||||
});
|
||||
};
|
||||
|
||||
accept_connection();
|
||||
|
||||
io_service.run();
|
||||
l(LogLevel::info, "Stopped TCP Server");
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
int retval = -1;
|
||||
short port;
|
||||
std::chrono::seconds tokenTimeout;
|
||||
std::string ldapUri;
|
||||
std::string bindDN;
|
||||
std::string lockPagePrefix;
|
||||
std::string logfile;
|
||||
std::string serDev;
|
||||
unsigned int baudrate;
|
||||
|
||||
try {
|
||||
unsigned int timeout;
|
||||
po::options_description desc("doorlockd (" + version + " built on " + gitversion + ")");
|
||||
desc.add_options()
|
||||
("help,h",
|
||||
"print help")
|
||||
("tokentimeout,t",
|
||||
po::value<unsigned int>(&timeout)->default_value(DEFAULT_TOKEN_TIMEOUT),
|
||||
"Token timeout in seconds")
|
||||
("port,p",
|
||||
po::value<short>(&port)->default_value(DEFAULT_PORT),
|
||||
"Port")
|
||||
("ldap,s",
|
||||
po::value<std::string>(&ldapUri)->default_value(DEFAULT_LDAP_URI),
|
||||
"Ldap Server")
|
||||
("bidndn,b",
|
||||
po::value<std::string>(&bindDN)->default_value(DEFAULT_BINDDN),
|
||||
"Bind DN, %s means username")
|
||||
("web,w",
|
||||
po::value<std::string>(&lockPagePrefix)->default_value(DEFAULT_WEB_PREFIX),
|
||||
"Prefix of the webpage")
|
||||
("logfile,l",
|
||||
po::value<std::string>(&logfile)->default_value(DEFAULT_LOG_FILE),
|
||||
"Log file")
|
||||
("serial,i",
|
||||
po::value<std::string>(&serDev)->default_value(DEFAULT_SERIAL_DEVICE),
|
||||
"Serial port")
|
||||
("baud,r",
|
||||
po::value<unsigned int>(&baudrate)->default_value((DEFAULT_SERIAL_BAUDRATE)),
|
||||
"Serial baudrate");
|
||||
|
||||
po::variables_map vm;
|
||||
po::store(po::command_line_parser(argc, argv).options(desc).run(), vm);
|
||||
|
||||
if (vm.count("help"))
|
||||
{
|
||||
std::cout << desc << std::endl;
|
||||
retval = 0;
|
||||
exit(0);
|
||||
}
|
||||
|
||||
po::notify(vm);
|
||||
|
||||
tokenTimeout = std::chrono::seconds(timeout);
|
||||
|
||||
l.setLogFile(logfile);
|
||||
l.logFile(true);
|
||||
}
|
||||
catch(const std::exception &e)
|
||||
{
|
||||
std::cerr << "Error: " << e.what() << "\n";
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
l((std::string)"Hello, this is " + version + " built on " + gitversion,
|
||||
LogLevel::info);
|
||||
l(LogLevel::notice, "Starting doorlockd");
|
||||
|
||||
signal(SIGINT, signal_handler);
|
||||
signal(SIGKILL, signal_handler);
|
||||
signal(SIGTERM, signal_handler);
|
||||
signal(SIGUSR1, signal_handler);
|
||||
signal(SIGUSR2, signal_handler);
|
||||
|
||||
l(LogLevel::info, "Starting Doorlock Logic");
|
||||
|
||||
retval = 0;
|
||||
try {
|
||||
logic = std::unique_ptr<Logic>(new Logic(tokenTimeout,
|
||||
ldapUri,
|
||||
bindDN,
|
||||
lockPagePrefix,
|
||||
serDev,
|
||||
baudrate,
|
||||
onClientMessage));
|
||||
server(port);
|
||||
}
|
||||
catch (...) {
|
||||
l(LogLevel::error, "Fatal error, shutting down");
|
||||
retval = -1;
|
||||
}
|
||||
|
||||
if (logic) {
|
||||
l(LogLevel::info, "Stopping Doorlock Logic");
|
||||
logic.reset();
|
||||
}
|
||||
l(LogLevel::notice, "Doorlockd stopped");
|
||||
return retval;
|
||||
}
|
|
@ -1,100 +0,0 @@
|
|||
#include "clientmessage.h"
|
||||
#include "util.h"
|
||||
#include "response.h"
|
||||
|
||||
const std::string Clientmessage::_tokenKey = "token";
|
||||
const std::string Clientmessage::_unlockButtonKey = "unlockButton";
|
||||
const std::string Clientmessage::_lockButtonKey = "lockButton";
|
||||
const std::string Clientmessage::_emergencyUnlockKey = "emergencyUnlock";
|
||||
const std::string Clientmessage::_isOpenKey = "isOpen";
|
||||
|
||||
Clientmessage::Clientmessage(std::string token,
|
||||
bool isOpen,
|
||||
Doormessage doormessage) :
|
||||
_token(token),
|
||||
_isOpen(isOpen),
|
||||
_doormessage(doormessage)
|
||||
{
|
||||
}
|
||||
|
||||
Clientmessage::Clientmessage() :
|
||||
_token(),
|
||||
_isOpen(false),
|
||||
_doormessage()
|
||||
{
|
||||
}
|
||||
|
||||
Clientmessage &Clientmessage::operator=(const Clientmessage &rhs)
|
||||
{
|
||||
// Protect against self assignement
|
||||
if (this == &rhs) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
this->_token = rhs._token;
|
||||
this->_isOpen = rhs._isOpen;
|
||||
this->_doormessage = rhs._doormessage;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
std::string Clientmessage::toJson() const
|
||||
{
|
||||
Json::StyledWriter writer;
|
||||
Json::Value message;
|
||||
|
||||
message[_tokenKey] = _token;
|
||||
message[_unlockButtonKey] = _doormessage.isUnlockButton;
|
||||
message[_lockButtonKey] = _doormessage.isLockButton;
|
||||
message[_emergencyUnlockKey] = _doormessage.isEmergencyUnlock;
|
||||
message[_isOpenKey] = _isOpen;
|
||||
|
||||
return writer.write(message);
|
||||
}
|
||||
|
||||
const std::string& Clientmessage::token() const
|
||||
{
|
||||
return _token;
|
||||
}
|
||||
|
||||
const Doormessage& Clientmessage::doormessage() const
|
||||
{
|
||||
return _doormessage;
|
||||
}
|
||||
|
||||
Clientmessage Clientmessage::fromJson(const Json::Value &root)
|
||||
{
|
||||
std::string token;
|
||||
bool isOpen;
|
||||
Doormessage doormessage;
|
||||
|
||||
try {
|
||||
token = getJsonOrFail<std::string>(root, _tokenKey);
|
||||
doormessage.isLockButton = getJsonOrFail<bool>(root, _lockButtonKey);
|
||||
doormessage.isUnlockButton = getJsonOrFail<bool>(root, _unlockButtonKey);
|
||||
doormessage.isEmergencyUnlock = getJsonOrFail<bool>(root, _emergencyUnlockKey);
|
||||
isOpen = getJsonOrFail<bool>(root, _isOpenKey);
|
||||
}
|
||||
catch (const std::exception &ex) {
|
||||
throw Response(Response::Code::JsonError, ex.what());
|
||||
}
|
||||
|
||||
return Clientmessage(token, isOpen, doormessage);
|
||||
}
|
||||
|
||||
Clientmessage Clientmessage::fromString(const std::string &string)
|
||||
{
|
||||
Json::Reader reader;
|
||||
Json::Value root;
|
||||
|
||||
if (reader.parse(string, root) == false)
|
||||
throw Response(Response::Code::NotJson,
|
||||
"No valid JSON");
|
||||
|
||||
return fromJson(root);
|
||||
}
|
||||
|
||||
bool Clientmessage::isOpen() const
|
||||
{
|
||||
return _isOpen;
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
#ifndef CLIENTMESSAGE_H
|
||||
#define CLIENTMESSAGE_H
|
||||
|
||||
#include <string>
|
||||
#include <json/json.h>
|
||||
|
||||
#include "doormessage.h"
|
||||
|
||||
class Clientmessage
|
||||
{
|
||||
public:
|
||||
|
||||
Clientmessage(std::string token,
|
||||
bool isOpen,
|
||||
Doormessage doormessage);
|
||||
Clientmessage();
|
||||
|
||||
Clientmessage &operator=(const Clientmessage& rhs);
|
||||
|
||||
static Clientmessage fromJson(const Json::Value &root);
|
||||
static Clientmessage fromString(const std::string &json);
|
||||
std::string toJson() const;
|
||||
|
||||
const std::string& token() const;
|
||||
bool isOpen() const;
|
||||
const Doormessage& doormessage() const;
|
||||
|
||||
private:
|
||||
|
||||
std::string _token;
|
||||
bool _isOpen;
|
||||
Doormessage _doormessage;
|
||||
|
||||
const static std::string _tokenKey;
|
||||
const static std::string _unlockButtonKey;
|
||||
const static std::string _lockButtonKey;
|
||||
const static std::string _emergencyUnlockKey;
|
||||
const static std::string _isOpenKey;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -1,200 +0,0 @@
|
|||
#include "config.h"
|
||||
#include "door.h"
|
||||
|
||||
#include "../../doorcmds.h"
|
||||
|
||||
Door::Door(const std::string &serDev,
|
||||
unsigned int baudrate) :
|
||||
_baudrate(baudrate),
|
||||
_port(_ioService, serDev),
|
||||
_logger(Logger::get())
|
||||
{
|
||||
// Configure serial port
|
||||
_port.set_option(boost::asio::serial_port_base::baud_rate(baudrate));
|
||||
_port.set_option(boost::asio::serial_port_base::character_size(8));
|
||||
_port.set_option(boost::asio::serial_port_base::stop_bits(boost::asio::serial_port_base::stop_bits::one));
|
||||
_port.set_option(boost::asio::serial_port_base::parity(boost::asio::serial_port_base::parity::none));
|
||||
_port.set_option(boost::asio::serial_port_base::flow_control(boost::asio::serial_port_base::flow_control::none));
|
||||
|
||||
_asyncRead();
|
||||
|
||||
_ioThread = std::thread([this] () {
|
||||
_ioService.run();
|
||||
});
|
||||
|
||||
// TODO Ping device
|
||||
}
|
||||
|
||||
Door::~Door()
|
||||
{
|
||||
lock();
|
||||
|
||||
_ioService.stop();
|
||||
_ioService.reset();
|
||||
|
||||
_port.cancel();
|
||||
_port.close();
|
||||
|
||||
_ioThread.join();
|
||||
}
|
||||
|
||||
bool Door::readByte(char &byte, std::chrono::milliseconds timeout)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(_receiveLock);
|
||||
_receivedCondition.wait_for(lock, timeout);
|
||||
if (_byteReady) {
|
||||
byte = recvBuf;
|
||||
_byteReady = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Door::_asyncRead()
|
||||
{
|
||||
_port.async_read_some(
|
||||
boost::asio::buffer(&recvBuf, sizeof(recvBuf)),
|
||||
[this] (const boost::system::error_code &ec, size_t bytes_transferred) {
|
||||
if (ec) {
|
||||
// Operation canceled occurs on system shutdown
|
||||
// So we return without invoking an additional asyncRead()
|
||||
if (ec == boost::system::errc::operation_canceled)
|
||||
return;
|
||||
|
||||
_logger(LogLevel::error, "Serialport error: %s", ec.message().c_str());
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (bytes_transferred != 1) {
|
||||
_logger(LogLevel::error, "Fatal serial error");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (recvBuf == DOOR_BUTTON_UNLOCK) {
|
||||
// In case that someone pushed the unlock button - just log it.
|
||||
// No further actions required
|
||||
_logger(LogLevel::notice, "Someone pushed the unlock button");
|
||||
if (_doorCallback) {
|
||||
_doorCallback(Doormessage(true, false, false));
|
||||
}
|
||||
goto out;
|
||||
} else if (recvBuf == DOOR_BUTTON_LOCK) {
|
||||
_logger(LogLevel::notice, "Someone pushed the lock button");
|
||||
_logger(LogLevel::notice, "Locking...");
|
||||
lock();
|
||||
if (_doorCallback) {
|
||||
_doorCallback(Doormessage(false, true, false));
|
||||
}
|
||||
goto out;
|
||||
} else if (recvBuf == DOOR_EMERGENCY_UNLOCK) {
|
||||
_logger(LogLevel::warning, "Someone did an emergency unlock!");
|
||||
system(EMERGENCY_UNLOCK_SCRIPT);
|
||||
if (_doorCallback) {
|
||||
_doorCallback(Doormessage(false, false, true));
|
||||
}
|
||||
goto out;
|
||||
}
|
||||
|
||||
_byteReady = true;
|
||||
_receivedCondition.notify_one();
|
||||
|
||||
out:
|
||||
_asyncRead();
|
||||
});
|
||||
}
|
||||
|
||||
Door::State Door::state() const
|
||||
{
|
||||
return _state;
|
||||
}
|
||||
|
||||
void Door::lock()
|
||||
{
|
||||
_stateMutex.lock();
|
||||
|
||||
|
||||
_logger(LogLevel::notice, "Executing Pre Lock Script");
|
||||
system(PRE_LOCK_SCRIPT);
|
||||
|
||||
if (_state == State::Locked) {
|
||||
_stateMutex.unlock();
|
||||
_logger(LogLevel::info, "Door already closed");
|
||||
goto out;
|
||||
}
|
||||
|
||||
_state = State::Locked;
|
||||
_stateMutex.unlock();
|
||||
_heartbeatCondition.notify_one();
|
||||
_heartbeatThread.join();
|
||||
|
||||
_logger(LogLevel::notice , "Door closed");
|
||||
|
||||
out:
|
||||
_logger(LogLevel::notice, "Executing Post Lock Script");
|
||||
system(POST_LOCK_SCRIPT);
|
||||
}
|
||||
|
||||
void Door::unlock()
|
||||
{
|
||||
_stateMutex.lock();
|
||||
_schnapper = true;
|
||||
|
||||
_logger(LogLevel::notice, "Executing Pre Unlock Script");
|
||||
system(PRE_UNLOCK_SCRIPT);
|
||||
|
||||
if(_state == State::Unlocked) {
|
||||
_stateMutex.unlock();
|
||||
_logger(LogLevel::info, "Door already opened");
|
||||
goto out;
|
||||
}
|
||||
|
||||
_state = State::Unlocked;
|
||||
_stateMutex.unlock();
|
||||
|
||||
_heartbeatThread = std::thread([this] () {
|
||||
std::unique_lock<std::mutex> lock(_heartbeatMutex);
|
||||
|
||||
while (_state == State::Unlocked) {
|
||||
if (_state == State::Unlocked) {
|
||||
writeCMD(DOOR_CMD_UNLOCK);
|
||||
|
||||
if (_schnapper) {
|
||||
_schnapper = false;
|
||||
writeCMD(DOOR_CMD_SCHNAPER);
|
||||
}
|
||||
}
|
||||
|
||||
_heartbeatCondition.wait_for(lock, Milliseconds(400));
|
||||
}
|
||||
writeCMD(DOOR_CMD_LOCK);
|
||||
});
|
||||
|
||||
_logger(LogLevel::notice, "Door opened");
|
||||
|
||||
out:
|
||||
_logger(LogLevel::notice, "Executing Post Unlock Script");
|
||||
system(POST_UNLOCK_SCRIPT);
|
||||
}
|
||||
|
||||
bool Door::writeCMD(char c)
|
||||
{
|
||||
std::lock_guard<std::mutex> l(_serialMutex);
|
||||
|
||||
_port.write_some(boost::asio::buffer(&c, sizeof(c)));
|
||||
char response;
|
||||
if (readByte(response, Milliseconds(100)))
|
||||
{
|
||||
if (c != response) {
|
||||
_logger(LogLevel::error, "Sent command '%c' but got response '%c'", c, response);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
_logger(LogLevel::error, "Sent Serial command, but got no response!");
|
||||
return false;
|
||||
}
|
||||
|
||||
void Door::setDoorCallback(DoorCallback doorCallback)
|
||||
{
|
||||
_doorCallback = doorCallback;
|
||||
}
|
|
@ -1,77 +0,0 @@
|
|||
#ifndef DOOR_H
|
||||
#define DOOR_H
|
||||
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/asio/serial_port.hpp>
|
||||
|
||||
#include "logger.h"
|
||||
#include "doormessage.h"
|
||||
|
||||
class Door final
|
||||
{
|
||||
public:
|
||||
using DoorCallback = std::function<void(Doormessage)>;
|
||||
enum class State {Unlocked, Locked};
|
||||
|
||||
|
||||
|
||||
Door(const std::string &serDev,
|
||||
unsigned int baudrate);
|
||||
~Door();
|
||||
|
||||
State state() const;
|
||||
void setDoorCallback(DoorCallback doorCallback);
|
||||
|
||||
void lock();
|
||||
void unlock();
|
||||
|
||||
private:
|
||||
|
||||
using Milliseconds = std::chrono::milliseconds;
|
||||
|
||||
const unsigned int _baudrate;
|
||||
|
||||
// To prevent concurrent writes
|
||||
std::mutex _serialMutex = { };
|
||||
|
||||
boost::asio::io_service _ioService = { };
|
||||
boost::asio::serial_port _port;
|
||||
|
||||
std::mutex _stateMutex = { };
|
||||
volatile State _state = { State::Locked };
|
||||
|
||||
std::thread _heartbeatThread = { };
|
||||
std::mutex _heartbeatMutex = { };
|
||||
std::condition_variable _heartbeatCondition = { };
|
||||
|
||||
std::thread _ioThread = { };
|
||||
void _asyncRead();
|
||||
|
||||
volatile bool _schnapper = { false };
|
||||
|
||||
// Indicates if recvBuf contains a valid response from AVR Board
|
||||
volatile bool _byteReady = { false };
|
||||
// Actual response
|
||||
char recvBuf = { };
|
||||
|
||||
std::condition_variable _receivedCondition = { };
|
||||
std::mutex _receiveLock = { };
|
||||
|
||||
DoorCallback _doorCallback = { };
|
||||
|
||||
Logger &_logger;
|
||||
|
||||
// Writed command to AVR board
|
||||
bool writeCMD(char c);
|
||||
// Receives one byte and returns true or returns false on timeout
|
||||
bool readByte(char &byte, Milliseconds timeout);
|
||||
};
|
||||
|
||||
#endif
|
|
@ -1,12 +0,0 @@
|
|||
#include "doormessage.h"
|
||||
|
||||
Doormessage::Doormessage()
|
||||
{
|
||||
}
|
||||
|
||||
Doormessage::Doormessage(bool isUnlockButton, bool isLockButton, bool isEmergencyUnlock) :
|
||||
isUnlockButton(isUnlockButton),
|
||||
isLockButton(isLockButton),
|
||||
isEmergencyUnlock(isEmergencyUnlock)
|
||||
{
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
#ifndef DOORMESSAGE_H
|
||||
#define DOORMESSAGE_H
|
||||
|
||||
struct Doormessage
|
||||
{
|
||||
Doormessage(bool isUnlockButton,
|
||||
bool isLockButton,
|
||||
bool isEmergencyUnlock);
|
||||
|
||||
Doormessage();
|
||||
|
||||
bool isUnlockButton = { false };
|
||||
bool isLockButton = { false };
|
||||
bool isEmergencyUnlock = { false };
|
||||
};
|
||||
|
||||
#endif
|
|
@ -1,232 +0,0 @@
|
|||
// (c) 2015 Ralf Ramsauer - Logger class from the sdfs project
|
||||
|
||||
#include <iostream>
|
||||
#include <cstdarg>
|
||||
#include <ctime>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <Windows.h>
|
||||
#endif
|
||||
|
||||
#include "config.h"
|
||||
#include "logger.h"
|
||||
|
||||
#ifdef USE_COLORIZED_LOGS
|
||||
#ifdef _WIN32 // If Windows, Use Windows Terminal coloring
|
||||
const static map<LogLevel, WORD> colorAttribute = {
|
||||
{LogLevel::error, FOREGROUND_RED },
|
||||
{LogLevel::warning, FOREGROUND_RED | FOREGROUND_GREEN },
|
||||
{LogLevel::notice, FOREGROUND_BLUE },
|
||||
{LogLevel::info, FOREGROUND_GREEN },
|
||||
{LogLevel::debug, FOREGROUND_BLUE | FOREGROUND_RED },
|
||||
{LogLevel::debug2, FOREGROUND_BLUE | FOREGROUND_RED },
|
||||
};
|
||||
#else // Use ANSI Escape sequences
|
||||
const static std::map<LogLevel, std::string> prefix_ansicolor = {
|
||||
{LogLevel::error, "\x1b[1m\x1b[31m" },
|
||||
{LogLevel::warning, "\x1b[1m\x1b[33m" },
|
||||
{LogLevel::notice, "\x1b[1m\x1b[34m" },
|
||||
{LogLevel::info, "\x1b[1m\x1b[32m" },
|
||||
{LogLevel::debug, "\x1b[1m\x1b[35m" },
|
||||
{LogLevel::debug2, "\x1b[1m\x1b[36m" },
|
||||
};
|
||||
|
||||
const static std::string suffix_ansireset = "\x1b[0m";
|
||||
#endif
|
||||
#endif
|
||||
|
||||
const static std::map<LogLevel, std::string> logLevel = {
|
||||
{LogLevel::error, "ERROR " },
|
||||
{LogLevel::warning, "WARNING" },
|
||||
{LogLevel::notice, "NOTICE " },
|
||||
{LogLevel::info, "INFO " },
|
||||
{LogLevel::debug, "DEBUG " },
|
||||
{LogLevel::debug2, "DEBUG2 " },
|
||||
};
|
||||
|
||||
Logger::Logger(const LogLevel level) :
|
||||
_level(level),
|
||||
_ostreamMutex()
|
||||
{
|
||||
}
|
||||
|
||||
Logger::~Logger()
|
||||
{
|
||||
if (_logFile.is_open())
|
||||
_logFile.close();
|
||||
}
|
||||
|
||||
Logger &Logger::get()
|
||||
{
|
||||
static Logger l(DEFAULT_LOG_LEVEL);
|
||||
return l;
|
||||
}
|
||||
|
||||
void Logger::operator ()(const std::string &message,
|
||||
const LogLevel level)
|
||||
{
|
||||
if(level > _level)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
std::ostringstream prefix;
|
||||
|
||||
auto t = std::time(nullptr);
|
||||
auto tm = *std::localtime(&t);
|
||||
|
||||
#if defined(USE_COLORIZED_LOGS) && !defined(_WIN32)
|
||||
prefix << prefix_ansicolor.at(level);
|
||||
#endif
|
||||
|
||||
// GCC does not support put_time :-(
|
||||
/*stringstream ss;
|
||||
ss << std::put_time(&tm, "%d-%m-%Y %H-%M-%S");
|
||||
prefix = "[" + ss.str() + "] ";*/
|
||||
|
||||
const size_t BUFFER_SIZE = 80;
|
||||
char timeBuffer[BUFFER_SIZE];
|
||||
std::strftime(timeBuffer, BUFFER_SIZE, "%Y-%m-%d %H:%M:%S", &tm);
|
||||
prefix << "[" << timeBuffer << "] -- " << logLevel.at(level) << " :: ";
|
||||
|
||||
// Critical section
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_ostreamMutex);
|
||||
|
||||
#if defined(USE_COLORIZED_LOGS) && !defined(_WIN32)
|
||||
if (_consoleActive) {
|
||||
std::cerr << prefix.str() << message
|
||||
<< suffix_ansireset << std::endl;
|
||||
std::cerr.flush();
|
||||
}
|
||||
if (_logFileActive && _logFile.is_open()) {
|
||||
_logFile << prefix.str() << message
|
||||
<< suffix_ansireset << std::endl;
|
||||
}
|
||||
#elif defined(USE_COLORIZED_LOGS) && defined(_WIN32)
|
||||
|
||||
if (_consoleActive) {
|
||||
// taken from GTEST
|
||||
const HANDLE stdout_handle = GetStdHandle(STD_ERROR_HANDLE);
|
||||
|
||||
// Gets the current text color.
|
||||
CONSOLE_SCREEN_BUFFER_INFO buffer_info;
|
||||
GetConsoleScreenBufferInfo(stdout_handle, &buffer_info);
|
||||
const WORD old_color_attrs = buffer_info.wAttributes;
|
||||
|
||||
// We need to flush the stream buffers into the console before each
|
||||
// SetConsoleTextAttribute call lest it affect the text that is already
|
||||
// printed but has not yet reached the console.
|
||||
std::cerr.flush();
|
||||
SetConsoleTextAttribute(stdout_handle,
|
||||
colorAttribute.at(level) | FOREGROUND_INTENSITY);
|
||||
|
||||
std::cerr << prefix.str() << message << std::endl;
|
||||
std::cerr.flush();
|
||||
|
||||
// Restores the text color.
|
||||
SetConsoleTextAttribute(stdout_handle, old_color_attrs);
|
||||
}
|
||||
if (_logFileActive && _logFile.is_open()) {
|
||||
_logFile << prefix.str() << message << std::endl;
|
||||
}
|
||||
|
||||
#else
|
||||
if (_consoleActive) {
|
||||
std::cerr << prefix.str() << message << std::endl;
|
||||
std::cerr.flush();
|
||||
}
|
||||
if (_logFileActive && _logFile.is_open()) {
|
||||
_logFile << prefix.str() << message << std::endl;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void Logger::operator ()(const std::ostringstream &message, const LogLevel level)
|
||||
{
|
||||
(*this)(message.str(), level);
|
||||
}
|
||||
|
||||
void Logger::operator ()(const LogLevel level, const char* format, ...)
|
||||
{
|
||||
if(level > _level)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
va_list argp;
|
||||
char* message = nullptr;
|
||||
|
||||
// determine buffer length
|
||||
va_start(argp, format);
|
||||
int size = vsnprintf(nullptr, 0, format, argp) + 1;
|
||||
va_end(argp);
|
||||
|
||||
if (size >= 0)
|
||||
{
|
||||
message = (char*)malloc(size);
|
||||
if (message == nullptr)
|
||||
{
|
||||
(*this)("[LOGGER] CRITICAL: MEMORY ALLOCATION ERROR",
|
||||
LogLevel::error);
|
||||
}
|
||||
|
||||
va_start(argp,format);
|
||||
vsnprintf(message, size, format, argp);
|
||||
va_end(argp);
|
||||
|
||||
(*this)(std::string(message), level);
|
||||
|
||||
free(message);
|
||||
} else {
|
||||
(*this)("[LOGGER] CRITICAL: VSNPRINTF ERROR",
|
||||
LogLevel::error);
|
||||
}
|
||||
}
|
||||
|
||||
void Logger::level(const LogLevel level)
|
||||
{
|
||||
_level = level;
|
||||
}
|
||||
|
||||
LogLevel Logger::level() const
|
||||
{
|
||||
return _level;
|
||||
}
|
||||
|
||||
bool Logger::console() const
|
||||
{
|
||||
return _consoleActive;
|
||||
}
|
||||
|
||||
void Logger::console(const bool active)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_ostreamMutex);
|
||||
_consoleActive = active;
|
||||
}
|
||||
|
||||
bool Logger::logFile() const
|
||||
{
|
||||
return _logFileActive;
|
||||
}
|
||||
|
||||
void Logger::logFile(const bool active)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_ostreamMutex);
|
||||
_logFileActive = active;
|
||||
}
|
||||
|
||||
void Logger::setLogFile(const std::string &logFile)
|
||||
{
|
||||
if (_logFile.is_open())
|
||||
_logFile.close();
|
||||
|
||||
_logFile.open(logFile, std::ofstream::out | std::ofstream::app);
|
||||
if (!_logFile.is_open())
|
||||
throw std::runtime_error("Unable to open " + logFile);
|
||||
}
|
|
@ -1,75 +0,0 @@
|
|||
#ifndef LOGGER_H
|
||||
#define LOGGER_H
|
||||
|
||||
#include <fstream>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <mutex>
|
||||
|
||||
/**
|
||||
* @brief The LogLevel enum
|
||||
*/
|
||||
enum class LogLevel : unsigned char
|
||||
{
|
||||
off, /// disable logging
|
||||
error, /// error conditions
|
||||
warning, /// warning conditions
|
||||
notice, /// normal but significant conditions
|
||||
info, /// informational messages
|
||||
debug, /// debug-level messages
|
||||
debug2 /// more debug-level messages
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The Logger class
|
||||
*
|
||||
* The logger class is a thread-safe class which is used for formatting and forwarding
|
||||
* log messages.
|
||||
*/
|
||||
class Logger final
|
||||
{
|
||||
public:
|
||||
static Logger &get();
|
||||
|
||||
/// Log a string
|
||||
void operator() (const std::string &message, const LogLevel level = LogLevel::debug);
|
||||
|
||||
/// Log a ostringstream
|
||||
void operator() (const std::ostringstream &message, const LogLevel level = LogLevel::debug);
|
||||
|
||||
/// Log a format string
|
||||
void operator() (const LogLevel level, const char* format, ...);
|
||||
|
||||
/// Set minimum required log level to generate output
|
||||
void level(const LogLevel level);
|
||||
/// Get minimum required log level to generate output
|
||||
LogLevel level() const;
|
||||
|
||||
/// Getter/Setter for console output
|
||||
bool console() const;
|
||||
void console(const bool active);
|
||||
|
||||
/// Getter/Setter for logfile output
|
||||
bool logFile() const;
|
||||
void logFile(const bool active);
|
||||
void setLogFile(const std::string &logFile);
|
||||
|
||||
private:
|
||||
|
||||
/**
|
||||
* @brief Constructor
|
||||
* @param level Minimum required log level to generate output
|
||||
*/
|
||||
Logger(const LogLevel level);
|
||||
~Logger();
|
||||
|
||||
bool _consoleActive = { true };
|
||||
bool _logFileActive = { false };
|
||||
|
||||
std::ofstream _logFile = {};
|
||||
|
||||
LogLevel _level;
|
||||
mutable std::mutex _ostreamMutex;
|
||||
};
|
||||
#endif
|
|
@ -1,260 +0,0 @@
|
|||
#include <errno.h>
|
||||
#define LDAP_DEPRECATED 1
|
||||
#include <ldap.h>
|
||||
|
||||
#include "logic.h"
|
||||
#include "util.h"
|
||||
|
||||
Logic::Logic(const std::chrono::seconds tokenTimeout,
|
||||
const std::string &ldapUri,
|
||||
const std::string &bindDN,
|
||||
const std::string &webPrefix,
|
||||
const std::string &serDev,
|
||||
const unsigned int baudrate,
|
||||
std::condition_variable &onClientUpdate) :
|
||||
_logger(Logger::get()),
|
||||
_door(serDev, baudrate),
|
||||
_tokenTimeout(tokenTimeout),
|
||||
_onClientUpdate(onClientUpdate),
|
||||
_ldapUri(ldapUri),
|
||||
_bindDN(bindDN),
|
||||
_webPrefix(webPrefix)
|
||||
{
|
||||
srand(time(NULL));
|
||||
_createNewToken(false);
|
||||
|
||||
_door.setDoorCallback(std::bind(&Logic::_doorCallback,
|
||||
this,
|
||||
std::placeholders::_1));
|
||||
|
||||
_tokenUpdater = std::thread([this] () {
|
||||
while (_run)
|
||||
{
|
||||
std::unique_lock<std::mutex> l(_mutex);
|
||||
_tokenCondition.wait_for(l, _tokenTimeout);
|
||||
if (_run == false)
|
||||
{
|
||||
break;
|
||||
} else {
|
||||
_createNewToken(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Logic::~Logic()
|
||||
{
|
||||
_run = false;
|
||||
_tokenCondition.notify_one();
|
||||
_tokenUpdater.join();
|
||||
}
|
||||
|
||||
Response Logic::processDoor(const DoorCommand &doorCommand)
|
||||
{
|
||||
Response response;
|
||||
|
||||
switch (doorCommand) {
|
||||
case DoorCommand::Lock:
|
||||
response = _lock();
|
||||
break;
|
||||
case DoorCommand::Unlock:
|
||||
response = _unlock();
|
||||
break;
|
||||
default:
|
||||
response.code = Response::Code::UnknownCommand;
|
||||
response.message = "Unknown DoorCommand";
|
||||
break;
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
Response Logic::request(const Request &request)
|
||||
{
|
||||
std::unique_lock<std::mutex> l(_mutex);
|
||||
Response response;
|
||||
|
||||
DoorCommand doorCommand;
|
||||
|
||||
switch (request.command) {
|
||||
case Request::Command::Lock:
|
||||
doorCommand = DoorCommand::Lock;
|
||||
break;
|
||||
case Request::Command::Unlock:
|
||||
doorCommand = DoorCommand::Unlock;
|
||||
break;
|
||||
default:
|
||||
response.code = Response::Code::UnknownCommand;
|
||||
response.message = "Unknown Command";
|
||||
goto out;
|
||||
}
|
||||
|
||||
response = _checkToken(request.token);
|
||||
if (!response) {
|
||||
goto out;
|
||||
}
|
||||
_logger(LogLevel::info, " -> Token check successful");
|
||||
|
||||
response = _checkLDAP(request.user, request.password);
|
||||
if (!response) {
|
||||
goto out;
|
||||
}
|
||||
_logger(LogLevel::info, " -> LDAP check successful");
|
||||
|
||||
response = processDoor(doorCommand);
|
||||
_logger(LogLevel::info, " -> Door Command successful");
|
||||
|
||||
out:
|
||||
return response;
|
||||
}
|
||||
|
||||
Response Logic::_lock()
|
||||
{
|
||||
Response response;
|
||||
if (_door.state() == Door::State::Locked)
|
||||
{
|
||||
response.code = Response::Code::AlreadyLocked;
|
||||
response.message = "Unable to lock: already closed";
|
||||
} else {
|
||||
_createNewToken(false);
|
||||
|
||||
response.code = Response::Code::Success;
|
||||
response.message = "Success";
|
||||
}
|
||||
|
||||
_door.lock();
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
Response Logic::_unlock()
|
||||
{
|
||||
Response response;
|
||||
|
||||
const auto oldState = _door.state();
|
||||
_door.unlock();
|
||||
_createNewToken(false);
|
||||
|
||||
if (oldState == Door::State::Unlocked)
|
||||
{
|
||||
response.code = Response::Code::AlreadyUnlocked;
|
||||
response.message = "Unable to unlock: already unlocked";
|
||||
_logger(response.message, LogLevel::info);
|
||||
} else {
|
||||
response.code = Response::Code::Success;
|
||||
response.message = "Success";
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
Response Logic::_checkToken(std::string token) const
|
||||
{
|
||||
std::transform(token.begin(),
|
||||
token.end(),
|
||||
token.begin(),
|
||||
::toupper);
|
||||
|
||||
if (token == _curToken)
|
||||
return Response(Response::Code::Success);
|
||||
|
||||
if (_prevValid == true && token == _prevToken)
|
||||
return Response(Response::Code::Success);
|
||||
|
||||
_logger("Check Token failed: got \"" + token
|
||||
+ "\", expected \"" + _curToken +"\"",
|
||||
LogLevel::error);
|
||||
|
||||
return Response(Response::InvalidToken,
|
||||
"User provided invalid token");
|
||||
}
|
||||
|
||||
Response Logic::_checkLDAP(const std::string &user,
|
||||
const std::string &password)
|
||||
{
|
||||
constexpr int BUFFERSIZE = 1024;
|
||||
char buffer[BUFFERSIZE];
|
||||
Response retval;
|
||||
|
||||
int rc = -1;
|
||||
LDAP* ld = nullptr;
|
||||
unsigned long version = LDAP_VERSION3;
|
||||
|
||||
_logger(LogLevel::info, " Trying to authenticate as user \"%s\"", user.c_str());
|
||||
snprintf(buffer, BUFFERSIZE, _bindDN.c_str(), user.c_str());
|
||||
|
||||
rc = ldap_initialize(&ld, _ldapUri.c_str());
|
||||
if(rc != LDAP_SUCCESS)
|
||||
{
|
||||
retval.message = (std::string)"LDAP initialize error: "
|
||||
+ ldap_err2string(rc);
|
||||
retval.code = Response::Code::LDAPInit;
|
||||
goto out2;
|
||||
}
|
||||
|
||||
rc = ldap_set_option(ld,
|
||||
LDAP_OPT_PROTOCOL_VERSION,
|
||||
(void*)&version);
|
||||
if (rc != LDAP_SUCCESS)
|
||||
{
|
||||
retval.code = Response::Code::LDAPInit;
|
||||
retval.message = "LDAP set version failed";
|
||||
goto out;
|
||||
}
|
||||
|
||||
rc = ldap_simple_bind_s(ld, buffer, password.c_str());
|
||||
if (rc != LDAP_SUCCESS)
|
||||
{
|
||||
retval = Response::Code::InvalidCredentials;
|
||||
retval.message = "Credential check for user \"" + user
|
||||
+ "\" failed: " + ldap_err2string(rc);
|
||||
goto out;
|
||||
}
|
||||
|
||||
retval.code = Response::Code::Success;
|
||||
retval.message = "";
|
||||
|
||||
out:
|
||||
ldap_unbind(ld);
|
||||
ld = nullptr;
|
||||
out2:
|
||||
return retval;
|
||||
}
|
||||
|
||||
void Logic::_createNewToken(const bool stillValid)
|
||||
{
|
||||
// Todo Mutex einführen
|
||||
|
||||
_prevToken = _curToken;
|
||||
_prevValid = stillValid;
|
||||
|
||||
_curToken = toHexString((((uint64_t)rand())<<32) | ((uint64_t)rand()));
|
||||
|
||||
std::ostringstream message;
|
||||
message << "New token: " << _curToken
|
||||
<< " old token: " << _prevToken << " is "
|
||||
<< (_prevValid?"still":"not") << " valid";
|
||||
_logger(message, LogLevel::notice);
|
||||
|
||||
_onClientUpdate.notify_all();
|
||||
}
|
||||
|
||||
Clientmessage Logic::getClientMessage()
|
||||
{
|
||||
std::lock_guard<std::mutex> l(_mutex);
|
||||
Clientmessage retval(_webPrefix + _curToken,
|
||||
_door.state() == Door::State::Unlocked,
|
||||
_doormessage);
|
||||
|
||||
// Reset doormessage
|
||||
_doormessage = Doormessage();
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
void Logic::_doorCallback(Doormessage doormessage)
|
||||
{
|
||||
std::lock_guard<std::mutex> l(_mutex);
|
||||
_doormessage = doormessage;
|
||||
_onClientUpdate.notify_all();
|
||||
}
|
|
@ -1,101 +0,0 @@
|
|||
#ifndef LOGIC_H
|
||||
#define LOGIC_H
|
||||
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
#include "config.h"
|
||||
#include "clientmessage.h"
|
||||
#include "door.h"
|
||||
#include "logger.h"
|
||||
#include "request.h"
|
||||
#include "response.h"
|
||||
|
||||
/* The "Logic" class
|
||||
*
|
||||
* This class is initilized by all settings.
|
||||
*
|
||||
* It handles incoming requests and allows modifications of the door
|
||||
*/
|
||||
class Logic
|
||||
{
|
||||
public:
|
||||
|
||||
Logic(const std::chrono::seconds tokenTimeout,
|
||||
const std::string &ldapUri,
|
||||
const std::string &bindDN,
|
||||
const std::string &webPrefix,
|
||||
const std::string &serDev,
|
||||
const unsigned int baudrate,
|
||||
std::condition_variable &onClientUpdate);
|
||||
~Logic();
|
||||
|
||||
// Parse incoming JSON Requests
|
||||
Response request(const Request &request);
|
||||
|
||||
// Send direct command to door without credential checks
|
||||
enum class DoorCommand { Lock, Unlock };
|
||||
Response processDoor(const DoorCommand &doorCommand);
|
||||
|
||||
// Returns the current Token
|
||||
Clientmessage getClientMessage();
|
||||
|
||||
private:
|
||||
|
||||
// Internal lock wrapper
|
||||
Response _lock();
|
||||
// Internal unlock wrapper
|
||||
Response _unlock();
|
||||
|
||||
// Checks if the incoming token is valid
|
||||
Response _checkToken(std::string token) const;
|
||||
|
||||
// Checks if incoming credentials against LDAP
|
||||
Response _checkLDAP(const std::string &user,
|
||||
const std::string &password);
|
||||
|
||||
// Creates a new random token and draws it on the epaper.
|
||||
// stillValid indicates whether the old (previous) token is still valid
|
||||
void _createNewToken(const bool stillValid);
|
||||
|
||||
void _doorCallback(Doormessage doormessage);
|
||||
|
||||
Logger &_logger;
|
||||
|
||||
// The door
|
||||
Door _door;
|
||||
|
||||
// The current token
|
||||
std::string _curToken = { "0000000000000000" };
|
||||
// The previous token
|
||||
std::string _prevToken = { "0000000000000000" };
|
||||
// Indicates whether the previous token is valid
|
||||
bool _prevValid = { false };
|
||||
|
||||
// Tokens are refreshed all tokenTimout seconds
|
||||
const std::chrono::seconds _tokenTimeout;
|
||||
// Thread for asynchronosly updating tokens
|
||||
std::thread _tokenUpdater = {};
|
||||
// Thread can be force-triggered for updates using the condition variable
|
||||
std::condition_variable _tokenCondition = {};
|
||||
// stop indicator for the thread
|
||||
bool _run = true;
|
||||
// General mutex for concurrent data access
|
||||
mutable std::mutex _mutex = {};
|
||||
|
||||
Doormessage _doormessage = {};
|
||||
|
||||
// This variable gets notified on token updates
|
||||
std::condition_variable &_onClientUpdate;
|
||||
|
||||
// The URI of the ldap server
|
||||
const std::string _ldapUri;
|
||||
// LDAP bindDN
|
||||
const std::string _bindDN;
|
||||
// Prefix of the website
|
||||
const std::string _webPrefix;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -1,69 +0,0 @@
|
|||
#include "request.h"
|
||||
#include "util.h"
|
||||
#include "logger.h"
|
||||
|
||||
const std::string Request::_commandKey = "command";
|
||||
|
||||
Request::Command Request::_commandFromString(const std::string &command)
|
||||
{
|
||||
Command retval = Command::Unknown;
|
||||
|
||||
if (command == "lock")
|
||||
retval = Command::Lock;
|
||||
else if (command == "unlock")
|
||||
retval = Command::Unlock;
|
||||
else if (command == "subscribe")
|
||||
retval = Command::Subscribe;
|
||||
else
|
||||
retval = Command::Unknown;
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
Request Request::fromJson(const Json::Value &root)
|
||||
{
|
||||
Request retval;
|
||||
auto &l = Logger::get();
|
||||
|
||||
try {
|
||||
const auto commandStr =
|
||||
getJsonOrFail<std::string>(root, _commandKey);
|
||||
l(" command: " + commandStr, LogLevel::info);
|
||||
retval.command = _commandFromString(commandStr);
|
||||
|
||||
// Stop parsing, if command is unknown
|
||||
if (retval.command == Command::Unknown)
|
||||
return retval;
|
||||
|
||||
if (retval.command == Command::Lock ||
|
||||
retval.command == Command::Unlock) {
|
||||
retval.user = getJsonOrFail<std::string>(root, "user");
|
||||
l(" user: " + retval.user, LogLevel::info);
|
||||
retval.password = getJsonOrFail<std::string>(root, "password");
|
||||
l(" password: XXX", LogLevel::info);
|
||||
retval.token = getJsonOrFail<std::string>(root, "token");
|
||||
l(" token: " + retval.token, LogLevel::info);
|
||||
}
|
||||
|
||||
if (retval.command == Command::Subscribe) {
|
||||
// Nothing to do in this case
|
||||
}
|
||||
}
|
||||
catch (const std::exception &ex) {
|
||||
throw Response(Response::Code::JsonError, ex.what());
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
Request Request::fromString(const std::string &string)
|
||||
{
|
||||
Json::Reader reader;
|
||||
Json::Value root;
|
||||
|
||||
if (reader.parse(string, root) == false)
|
||||
throw Response(Response::Code::NotJson,
|
||||
"No valid JSON");
|
||||
|
||||
return fromJson(root);
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
#ifndef REQUEST_H
|
||||
#define REQUEST_H
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <json/json.h>
|
||||
|
||||
#include "response.h"
|
||||
|
||||
class Request
|
||||
{
|
||||
public:
|
||||
static Request fromJson(const Json::Value &root);
|
||||
static Request fromString(const std::string &string);
|
||||
|
||||
enum class Command { Lock, Unlock, Subscribe, Unknown }
|
||||
command = { Command::Unknown };
|
||||
std::string user = { };
|
||||
std::string password = { };
|
||||
std::string token = { };
|
||||
|
||||
private:
|
||||
static Command _commandFromString(const std::string &command);
|
||||
|
||||
const static std::string _commandKey;
|
||||
};
|
||||
|
||||
#endif // REQUEST_H
|
||||
|
|
@ -1,69 +0,0 @@
|
|||
#include <exception>
|
||||
|
||||
#include <json/json.h>
|
||||
|
||||
#include "response.h"
|
||||
#include "util.h"
|
||||
|
||||
const std::string Response::_codeKey = "code";
|
||||
const std::string Response::_messageKey = "message";
|
||||
|
||||
Response::Response():
|
||||
code(Fail),
|
||||
message("General failure")
|
||||
{
|
||||
}
|
||||
|
||||
Response::Response(Response::Code code, const std::string &what) :
|
||||
code(code),
|
||||
message(what)
|
||||
{
|
||||
}
|
||||
|
||||
Response::operator bool() const
|
||||
{
|
||||
return code == Response::Code::Success;
|
||||
}
|
||||
|
||||
std::string Response::toJson() const
|
||||
{
|
||||
Json::Value response;
|
||||
Json::StyledWriter writer;
|
||||
|
||||
response[_codeKey] = code;
|
||||
response[_messageKey] = message;
|
||||
|
||||
return writer.write(response);
|
||||
}
|
||||
|
||||
Response Response::fromJson(const Json::Value &root)
|
||||
{
|
||||
Response retval;
|
||||
|
||||
try {
|
||||
retval.message = getJsonOrFail<std::string>(root, _messageKey);
|
||||
|
||||
const auto code = getJsonOrFail<int>(root, _codeKey);
|
||||
if (code > Code::RESPONSE_NUM_ITEMS)
|
||||
throw std::runtime_error("Error code out of range");
|
||||
|
||||
retval.code = static_cast<Code>(code);
|
||||
}
|
||||
catch (const std::exception &ex) {
|
||||
throw Response(Response::Code::JsonError, ex.what());
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
Response Response::fromString(const std::string &json)
|
||||
{
|
||||
Json::Reader reader;
|
||||
Json::Value root;
|
||||
|
||||
if (reader.parse(json, root) == false)
|
||||
throw Response(Response::Code::NotJson,
|
||||
"No valid JSON");
|
||||
|
||||
return fromJson(root);
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
#ifndef RESPONSE_H
|
||||
#define RESPONSE_H
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <json/json.h>
|
||||
|
||||
class Response
|
||||
{
|
||||
public:
|
||||
enum Code {
|
||||
Success = 0, // Request successful
|
||||
Fail, // General non-specified error
|
||||
AlreadyUnlocked, // Authentication successful, but door is already unlocked
|
||||
AlreadyLocked, // Authentication successful, but door is already locked
|
||||
NotJson, // Request is not a valid JSON object
|
||||
JsonError, // Request is valid JSON, but does not contain necessary material
|
||||
InvalidToken, // Request contains invalid token
|
||||
InvalidCredentials, // Invalid LDAP credentials
|
||||
InvalidIP, // IP check failure
|
||||
UnknownCommand, // Unknown action
|
||||
LDAPInit, // Ldap initialization failed
|
||||
AccessDenied, // Access denied
|
||||
RESPONSE_NUM_ITEMS
|
||||
} code;
|
||||
std::string message;
|
||||
|
||||
Response();
|
||||
Response(Response::Code code, const std::string &message = "");
|
||||
|
||||
static Response fromJson(const Json::Value &root);
|
||||
static Response fromString(const std::string &json);
|
||||
std::string toJson() const;
|
||||
|
||||
// Returns true if code is success
|
||||
operator bool() const;
|
||||
|
||||
private:
|
||||
|
||||
const static std::string _codeKey;
|
||||
const static std::string _messageKey;
|
||||
};
|
||||
|
||||
#endif // RESPONSE_H
|
|
@ -1,92 +0,0 @@
|
|||
#include "util.h"
|
||||
|
||||
template <>
|
||||
int getJson(const Json::Value &root, const std::string &key)
|
||||
{
|
||||
auto val = root.get(key, Json::Value());
|
||||
if (val.isInt())
|
||||
return val.asInt();
|
||||
throw std::runtime_error("Json Type error");
|
||||
}
|
||||
|
||||
template <>
|
||||
std::string getJson(const Json::Value &root, const std::string &key)
|
||||
{
|
||||
auto val = root.get(key, Json::Value());
|
||||
if (val.isString())
|
||||
return val.asString();
|
||||
throw std::runtime_error("Json Type error");
|
||||
}
|
||||
|
||||
template <>
|
||||
size_t getJson(const Json::Value &root, const std::string &key)
|
||||
{
|
||||
auto val = root.get(key, Json::Value());
|
||||
if (val.isInt())
|
||||
return val.asUInt64();
|
||||
throw std::runtime_error("Json Type error");
|
||||
}
|
||||
|
||||
template <>
|
||||
bool getJson(const Json::Value &root, const std::string &key)
|
||||
{
|
||||
auto val = root.get(key, Json::Value());
|
||||
if (val.isBool())
|
||||
return val.asBool();
|
||||
throw std::runtime_error("Json Type error");
|
||||
}
|
||||
|
||||
template <>
|
||||
Json::Value getJson(const Json::Value &root, const std::string &key)
|
||||
{
|
||||
auto val = root.get(key, Json::Value());
|
||||
return val;
|
||||
}
|
||||
|
||||
static char nibble2hex(unsigned char input)
|
||||
{
|
||||
input &= 0xf;
|
||||
|
||||
if(input <= 9)
|
||||
{
|
||||
return input + '0';
|
||||
}
|
||||
|
||||
return input - 0xA + 'A';
|
||||
}
|
||||
|
||||
std::string toHexString(const uint64_t c)
|
||||
{
|
||||
std::string retval;
|
||||
|
||||
retval = nibble2hex((c>>60) & 0xF);
|
||||
retval += nibble2hex((c>>56) & 0xF);
|
||||
retval += nibble2hex((c>>52) & 0xF);
|
||||
retval += nibble2hex((c>>48) & 0xF);
|
||||
retval += nibble2hex((c>>44) & 0xF);
|
||||
retval += nibble2hex((c>>40) & 0xF);
|
||||
retval += nibble2hex((c>>36) & 0xF);
|
||||
retval += nibble2hex((c>>32) & 0xF);
|
||||
retval += nibble2hex((c>>28) & 0xF);
|
||||
retval += nibble2hex((c>>24) & 0xF);
|
||||
retval += nibble2hex((c>>20) & 0xF);
|
||||
retval += nibble2hex((c>>16) & 0xF);
|
||||
retval += nibble2hex((c>>12) & 0xF);
|
||||
retval += nibble2hex((c>> 8) & 0xF);
|
||||
retval += nibble2hex((c>> 4) & 0xF);
|
||||
retval += nibble2hex((c ) & 0xF);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
unsigned char hex2uchar(const char input)
|
||||
{
|
||||
if(input >= '0' && input <= '9') {
|
||||
return input - '0';
|
||||
} else if(input >= 'A' && input <= 'F') {
|
||||
return input - 'A' + 10;
|
||||
} else if(input >= 'a' && input <= 'f') {
|
||||
return input - 'a' + 10;
|
||||
}
|
||||
throw std::runtime_error("Malformed hexadecimal input");
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
#ifndef UTIL_H
|
||||
#define UTIL_H
|
||||
|
||||
#include <algorithm>
|
||||
#include <exception>
|
||||
#include <json/json.h>
|
||||
|
||||
template <typename T>
|
||||
T getJson(const Json::Value &root, const std::string &key);
|
||||
|
||||
template <typename T>
|
||||
static T getJsonOrFail(const Json::Value &root, const std::string &key)
|
||||
{
|
||||
if (root.isObject() == false)
|
||||
{
|
||||
throw std::runtime_error("Invalid Json Object");
|
||||
}
|
||||
|
||||
if (root.isMember(key) == false) {
|
||||
throw std::runtime_error("Json key \"" + key + "\" not existing");
|
||||
}
|
||||
|
||||
return getJson<T>(root, key);
|
||||
}
|
||||
|
||||
std::string toHexString(uint64_t c);
|
||||
|
||||
#endif
|
|
@ -1 +0,0 @@
|
|||
#!/bin/bash
|
|
@ -1,3 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
wget -O /dev/null --timeout 3 "http://homer.binary.kitchen:8080/set?color=000000" > /dev/null 2>&1
|
|
@ -1,3 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
wget -O /dev/null --timeout 3 "http://homer.binary.kitchen:8080/set?color=0000FF" > /dev/null 2>&1
|
|
@ -1 +0,0 @@
|
|||
#!/bin/bash
|
|
@ -1 +0,0 @@
|
|||
#!/bin/bash
|
|
@ -1,23 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
echo -n "Cmd: "
|
||||
read CMD
|
||||
|
||||
echo -n "User: "
|
||||
read USER
|
||||
|
||||
echo -n "Password: "
|
||||
read -s PW
|
||||
echo
|
||||
|
||||
echo -n "Token: "
|
||||
read TOKEN
|
||||
|
||||
cat << EOF | nc localhost 5555
|
||||
{
|
||||
"command" : "$CMD",
|
||||
"user" : "$USER",
|
||||
"password" : "$PW",
|
||||
"token" : "$TOKEN"
|
||||
}
|
||||
EOF
|
198
doorstate
Executable file
|
@ -0,0 +1,198 @@
|
|||
#!/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'))
|
||||
|
||||
pin = cfg.int('GPIO_PIN')
|
||||
|
||||
request = gpiod.request_lines(
|
||||
cfg.str('GPIO_CHIP'),
|
||||
consumer = prog,
|
||||
config = {
|
||||
pin: gpiod.LineSettings(direction=gpiod.line.Direction.INPUT,
|
||||
edge_detection=gpiod.line.Edge.BOTH),
|
||||
},
|
||||
)
|
||||
|
||||
player_alarm = AudioPlayer(f_alarm)
|
||||
player_alarm.start()
|
||||
|
||||
player_door_call = AudioPlayer(f_door_call)
|
||||
player_door_call.start()
|
||||
|
||||
global door_open
|
||||
door_open = request.get_value(pin) == gpiod.line.Value.ACTIVE
|
||||
|
||||
door_alarm_set = False
|
||||
|
||||
global mqtt_connected
|
||||
mqtt_connected = False
|
||||
|
||||
mqttc.loop_start()
|
||||
publish_doorstate()
|
||||
|
||||
while True:
|
||||
# Synchronous loop: GPIO
|
||||
ev_line = request.wait_edge_events(1)
|
||||
if ev_line:
|
||||
request.read_edge_events()
|
||||
door_open = request.get_value(pin) == gpiod.line.Value.ACTIVE
|
||||
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()
|
2
etc/doorlockd.blacklist
Normal file
|
@ -0,0 +1,2 @@
|
|||
# Place blacklisted usernames here, separated by newlines. Blacklist applies to
|
||||
# all authentication backends.
|
41
etc/doorlockd.cfg
Normal file
|
@ -0,0 +1,41 @@
|
|||
[doorlockd]
|
||||
|
||||
DEBUG = False
|
||||
SIMULATE_SERIAL = False
|
||||
SIMULATE_AUTH = False
|
||||
RUN_HOOKS = True
|
||||
SOUNDS = True
|
||||
|
||||
# LDAP
|
||||
# LDAP_URI = ldaps://ldap1.binary.kitchen
|
||||
# LDAP_BINDDN = cn=%%s,ou=people,dc=binary-kitchen,dc=de
|
||||
|
||||
# Authentication Backends
|
||||
|
||||
# Local
|
||||
# LOCAL_USER_DB = /etc/doorlockd.passwd
|
||||
|
||||
# USER_BLACKLIST = /etc/doorlockd.blacklist
|
||||
|
||||
TITLE = Binary Kitchen Doorlock
|
||||
ROOM = Hauptraum
|
||||
WELCOME = Willkommen in der Binary Kitchen
|
||||
|
||||
SERIAL_PORT = /dev/ttyAMA0
|
||||
|
||||
SECRET_KEY = foobar
|
||||
|
||||
[dooralarm]
|
||||
|
||||
GPIO_CHIP = /dev/gpiochip0
|
||||
GPIO_PIN = 22
|
||||
|
||||
TOPIC_ALARM = kitchen/alarm
|
||||
TOPIC_DOORSTATE = kitchen/doorlock/frontdoor/doorstate
|
||||
TOPIC_LOCKSTATE = kitchen/doorlock/frontdoor/lockstate
|
||||
|
||||
DOORSTATE_ALARM_TIMEOUT = 2
|
||||
|
||||
MQTT_HOST = pizza.binary.kitchen
|
||||
MQTT_USERNAME = doorlock
|
||||
MQTT_PASSWORD =
|
132
pydoorlock/Authenticator.py
Normal file
|
@ -0,0 +1,132 @@
|
|||
"""
|
||||
Doorlockd -- Binary Kitchen's smart door opener
|
||||
|
||||
Copyright (c) Binary Kitchen e.V., 2018
|
||||
|
||||
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 hashlib
|
||||
import ldap
|
||||
import logging
|
||||
|
||||
from enum import Enum
|
||||
|
||||
from .Doorlock import DoorlockResponse
|
||||
|
||||
log = logging.getLogger()
|
||||
|
||||
|
||||
class AuthMethod(Enum):
|
||||
LDAP_USER_PW = 1
|
||||
LOCAL_USER_DB = 2
|
||||
|
||||
def __str__(self):
|
||||
if self == AuthMethod.LDAP_USER_PW:
|
||||
return 'LDAP'
|
||||
elif self == AuthMethod.LOCAL_USER_DB:
|
||||
return 'Local'
|
||||
return 'Error'
|
||||
|
||||
|
||||
class Authenticator:
|
||||
def __init__(self, cfg):
|
||||
self._simulate = cfg.boolean('SIMULATE_AUTH')
|
||||
self._backends = set()
|
||||
|
||||
f_blacklist = cfg.str('USER_BLACKLIST')
|
||||
self._user_blacklist = set()
|
||||
if f_blacklist:
|
||||
with open(f_blacklist, 'r') as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if line.startswith('#'):
|
||||
continue
|
||||
if line:
|
||||
self._user_blacklist.add(line)
|
||||
|
||||
if self._simulate:
|
||||
return
|
||||
|
||||
self._ldap_uri = cfg.str('LDAP_URI')
|
||||
self._ldap_binddn = cfg.str('LDAP_BINDDN')
|
||||
if self._ldap_uri and self._ldap_binddn:
|
||||
log.info('Initialising LDAP auth backend')
|
||||
self._backends.add(AuthMethod.LDAP_USER_PW)
|
||||
ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_DEMAND)
|
||||
ldap.set_option(ldap.OPT_REFERRALS, 0)
|
||||
|
||||
file_local_db = cfg.str('LOCAL_USER_DB')
|
||||
if file_local_db:
|
||||
log.info('Initialising local auth backend')
|
||||
self._local_db = dict()
|
||||
|
||||
with open(file_local_db, 'r') as f:
|
||||
for line in f:
|
||||
line = line.split()
|
||||
user = line[0]
|
||||
pwd = line[1].split(':')
|
||||
self._local_db[user] = pwd
|
||||
|
||||
self._backends.add(AuthMethod.LOCAL_USER_DB)
|
||||
|
||||
@property
|
||||
def backends(self):
|
||||
return self._backends
|
||||
|
||||
def _try_auth_local(self, user, password):
|
||||
log.info(' Trying to local auth (user, password) as user %s', user)
|
||||
if user not in self._local_db:
|
||||
log.info(' No user %s in local database', user)
|
||||
return DoorlockResponse.Perm
|
||||
|
||||
stored_pw = self._local_db[user][0]
|
||||
stored_salt = self._local_db[user][1]
|
||||
if stored_pw == hashlib.sha256(stored_salt.encode() + password.encode()).hexdigest():
|
||||
log.info(' Authenticated as user %s', user)
|
||||
return DoorlockResponse.Success
|
||||
|
||||
log.info(' Invalid credentials')
|
||||
return DoorlockResponse.Perm
|
||||
|
||||
def _try_auth_ldap(self, user, password):
|
||||
log.info(' Trying to LDAP auth (user, password) as user %s', user)
|
||||
ldap_username = self._ldap_binddn % user
|
||||
try:
|
||||
l = ldap.initialize(self._ldap_uri)
|
||||
l.simple_bind_s(ldap_username, password)
|
||||
l.unbind_s()
|
||||
except ldap.INVALID_CREDENTIALS:
|
||||
log.info(' Invalid credentials')
|
||||
return DoorlockResponse.Perm
|
||||
except ldap.LDAPError as e:
|
||||
log.info(' LDAP Error: %s' % e)
|
||||
return DoorlockResponse.InternalError
|
||||
log.info(' Authenticated as user %s', user)
|
||||
return DoorlockResponse.Success
|
||||
|
||||
def try_auth(self, credentials):
|
||||
user, password = credentials
|
||||
|
||||
if user in self._user_blacklist:
|
||||
return DoorlockResponse.Perm
|
||||
|
||||
if self._simulate:
|
||||
log.info('SIMULATION MODE! ACCEPTING ANYTHING!')
|
||||
return DoorlockResponse.Success
|
||||
if AuthMethod.LDAP_USER_PW in self._backends:
|
||||
retval = self._try_auth_ldap(user, password)
|
||||
if retval == DoorlockResponse.Success:
|
||||
return retval
|
||||
if AuthMethod.LOCAL_USER_DB in self._backends:
|
||||
return self._try_auth_local(user, password)
|
||||
return DoorlockResponse.Perm
|
57
pydoorlock/Config.py
Normal file
|
@ -0,0 +1,57 @@
|
|||
"""
|
||||
Doorlockd -- Binary Kitchen's smart door opener
|
||||
|
||||
Copyright (c) Binary Kitchen e.V., 2018-2019
|
||||
|
||||
Author:
|
||||
Ralf Ramsauer <ralf@binary-kitchen.de>
|
||||
Thomas Schmid <tom@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 functools
|
||||
from configparser import ConfigParser
|
||||
from os.path import join
|
||||
|
||||
|
||||
SYSCONFDIR = './etc'
|
||||
PREFIX = '.'
|
||||
|
||||
root_prefix = join(PREFIX, 'share', 'doorlockd')
|
||||
sounds_prefix = join(root_prefix, 'sounds')
|
||||
|
||||
|
||||
def check_exists(func):
|
||||
@functools.wraps(func)
|
||||
def decorator(*args, **kwargs):
|
||||
config = args[0]
|
||||
if not config.config.has_option(config.config_topic, args[1]):
|
||||
return None
|
||||
return func(*args, **kwargs)
|
||||
return decorator
|
||||
|
||||
|
||||
class Config:
|
||||
def __init__(self, config_topic):
|
||||
self.config_topic = config_topic
|
||||
self.config = ConfigParser()
|
||||
self.config.read(join(SYSCONFDIR, 'doorlockd.cfg'))
|
||||
|
||||
@check_exists
|
||||
def boolean(self, key):
|
||||
return self.config.getboolean(self.config_topic, key)
|
||||
|
||||
@check_exists
|
||||
def str(self, key):
|
||||
return self.config.get(self.config_topic, key)
|
||||
|
||||
@check_exists
|
||||
def int(self, key):
|
||||
return self.config.getint(self.config_topic, key)
|
55
pydoorlock/Door.py
Normal file
|
@ -0,0 +1,55 @@
|
|||
"""
|
||||
Doorlockd -- Binary Kitchen's smart door opener
|
||||
|
||||
Copyright (c) Binary Kitchen e.V., 2018
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class DoorState(Enum):
|
||||
Open = 0
|
||||
Present = 1
|
||||
Closed = 2
|
||||
|
||||
@staticmethod
|
||||
def from_string(string):
|
||||
if string == 'lock':
|
||||
return DoorState.Closed
|
||||
elif string == 'unlock':
|
||||
return DoorState.Open
|
||||
elif string == 'present':
|
||||
return DoorState.Present
|
||||
|
||||
return None
|
||||
|
||||
def is_open(self):
|
||||
if self != DoorState.Closed:
|
||||
return True
|
||||
return False
|
||||
|
||||
def to_img(self):
|
||||
led = 'red'
|
||||
if self == DoorState.Present:
|
||||
led = 'yellow'
|
||||
elif self == DoorState.Open:
|
||||
led = 'green'
|
||||
return 'static/led-%s.png' % led
|
||||
|
||||
def __str__(self):
|
||||
if self == DoorState.Open:
|
||||
return 'Offen'
|
||||
elif self == DoorState.Present:
|
||||
return 'Jemand da'
|
||||
return 'Geschlossen'
|
253
pydoorlock/Doorlock.py
Normal file
|
@ -0,0 +1,253 @@
|
|||
"""
|
||||
Doorlockd -- Binary Kitchen's smart door opener
|
||||
|
||||
Copyright (c) Binary Kitchen e.V., 2018-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 logging
|
||||
|
||||
from enum import Enum
|
||||
from random import sample
|
||||
from subprocess import run
|
||||
from serial import Serial
|
||||
from threading import Thread
|
||||
from time import sleep
|
||||
from os.path import join
|
||||
|
||||
from .Door import DoorState
|
||||
from .Protocol import Protocol
|
||||
|
||||
log = logging.getLogger()
|
||||
|
||||
# copied from sudo
|
||||
eperm_insults = [
|
||||
'Wrong! You cheating scum!',
|
||||
'And you call yourself a Rocket Scientist!',
|
||||
'No soap, honkie-lips.',
|
||||
'Where did you learn to type?',
|
||||
'Are you on drugs?',
|
||||
'My pet ferret can type better than you!',
|
||||
'You type like i drive.',
|
||||
'Do you think like you type?',
|
||||
'Your mind just hasn\'t been the same since the electro-shock, has it?',
|
||||
'Maybe if you used more than just two fingers...',
|
||||
'BOB says: You seem to have forgotten your passwd, enter another!',
|
||||
'stty: unknown mode: doofus',
|
||||
'I can\'t hear you -- I\'m using the scrambler.',
|
||||
'The more you drive -- the dumber you get.',
|
||||
'Listen, broccoli brains, I don\'t have time to listen to this trash.',
|
||||
'I\'ve seen penguins that can type better than that.',
|
||||
'Have you considered trying to match wits with a rutabaga?',
|
||||
'You speak an infinite deal of nothing',
|
||||
]
|
||||
|
||||
|
||||
def choose_insult():
|
||||
return sample(eperm_insults, 1)[0]
|
||||
|
||||
|
||||
def run_background(cmd):
|
||||
run('%s &' % cmd, shell=True)
|
||||
|
||||
|
||||
class DoorlockResponse(Enum):
|
||||
Success = 0
|
||||
Perm = 1
|
||||
AlreadyActive = 2
|
||||
# don't break old apps, value 3 is reserved now
|
||||
RESERVED = 3
|
||||
Inval = 4
|
||||
|
||||
EmergencyUnlock = 10,
|
||||
ButtonLock = 11,
|
||||
ButtonUnlock = 12,
|
||||
ButtonPresent = 13,
|
||||
|
||||
def __str__(self):
|
||||
if self == DoorlockResponse.Success:
|
||||
return 'Yo, passt.'
|
||||
elif self == DoorlockResponse.Perm:
|
||||
return choose_insult()
|
||||
elif self == DoorlockResponse.AlreadyActive:
|
||||
return 'Zustand bereits aktiv'
|
||||
elif self == DoorlockResponse.Inval:
|
||||
return 'Das was du willst geht nicht.'
|
||||
elif self == DoorlockResponse.EmergencyUnlock:
|
||||
return '!!! Emergency Unlock !!!'
|
||||
elif self == DoorlockResponse.ButtonLock:
|
||||
return 'Closed by button'
|
||||
elif self == DoorlockResponse.ButtonUnlock:
|
||||
return 'Opened by button'
|
||||
elif self == DoorlockResponse.ButtonPresent:
|
||||
return 'Present by button'
|
||||
|
||||
return 'Error'
|
||||
|
||||
|
||||
class DoorHandler:
|
||||
state = DoorState.Closed
|
||||
do_close = False
|
||||
|
||||
wave_lock = 'lock.wav'
|
||||
wave_lock_button = 'lock_button.wav'
|
||||
|
||||
wave_present = 'present.wav'
|
||||
wave_present_button = 'present.wav'
|
||||
|
||||
wave_unlock = 'unlock.wav'
|
||||
wave_unlock_button = 'unlock_button.wav'
|
||||
|
||||
wave_zonk = 'zonk.wav'
|
||||
|
||||
def __init__(self, cfg, sounds_prefix, scripts_prefix):
|
||||
self._callback = None
|
||||
|
||||
self.sounds = cfg.boolean('SOUNDS')
|
||||
if self.sounds:
|
||||
self.sounds_prefix = sounds_prefix
|
||||
|
||||
self.scripts_prefix = scripts_prefix
|
||||
self.run_hooks = cfg.boolean('RUN_HOOKS')
|
||||
|
||||
if cfg.boolean('SIMULATE_SERIAL'):
|
||||
return
|
||||
|
||||
device = cfg.str('SERIAL_PORT')
|
||||
log.info('Using serial port: %s' % device)
|
||||
|
||||
self.serial = Serial(device, baudrate=9600, bytesize=8, parity='N',
|
||||
stopbits=1, timeout=0)
|
||||
self.thread = Thread(target=self.thread_worker)
|
||||
log.debug('Spawning RS232 Thread')
|
||||
self.thread.start()
|
||||
|
||||
def thread_worker(self):
|
||||
while True:
|
||||
sleep(0.4)
|
||||
while True:
|
||||
rx = self.serial.read(1)
|
||||
if len(rx) == 0:
|
||||
break
|
||||
|
||||
old_state = self.state
|
||||
if rx == Protocol.STATE_SWITCH_RED.value.upper():
|
||||
self.close()
|
||||
log.info('Closed due to Button press')
|
||||
self.invoke_callback(DoorlockResponse.ButtonLock)
|
||||
elif rx == Protocol.STATE_SWITCH_GREEN.value.upper():
|
||||
self.open()
|
||||
log.info('Opened due to Button press')
|
||||
self.invoke_callback(DoorlockResponse.ButtonUnlock)
|
||||
elif rx == Protocol.STATE_SWITCH_YELLOW.value.upper():
|
||||
self.present()
|
||||
log.info('Present due to Button press')
|
||||
self.invoke_callback(DoorlockResponse.ButtonPresent)
|
||||
elif rx == Protocol.EMERGENCY.value:
|
||||
log.warning('Emergency unlock')
|
||||
self.invoke_callback(DoorlockResponse.EmergencyUnlock)
|
||||
else:
|
||||
log.error('Received unknown message "%s" from AVR' % rx)
|
||||
|
||||
self.sound_helper(old_state, self.state, True)
|
||||
|
||||
if self.do_close:
|
||||
tx = Protocol.STATE_SWITCH_RED.value
|
||||
self.do_close = False
|
||||
elif self.state == DoorState.Present:
|
||||
tx = Protocol.STATE_SWITCH_YELLOW.value
|
||||
elif self.state == DoorState.Open:
|
||||
tx = Protocol.STATE_SWITCH_GREEN.value
|
||||
else:
|
||||
continue
|
||||
|
||||
self.serial.write(tx)
|
||||
self.serial.flush()
|
||||
|
||||
def open(self):
|
||||
if self.state == DoorState.Open:
|
||||
return DoorlockResponse.AlreadyActive
|
||||
|
||||
self.state = DoorState.Open
|
||||
self.run_hook('post_unlock')
|
||||
return DoorlockResponse.Success
|
||||
|
||||
def present(self):
|
||||
if self.state == DoorState.Present:
|
||||
return DoorlockResponse.AlreadyActive
|
||||
|
||||
self.state = DoorState.Present
|
||||
self.run_hook('post_present')
|
||||
return DoorlockResponse.Success
|
||||
|
||||
def close(self):
|
||||
if self.state == DoorState.Closed:
|
||||
return DoorlockResponse.AlreadyActive
|
||||
|
||||
self.do_close = True
|
||||
self.state = DoorState.Closed
|
||||
self.run_hook('post_lock')
|
||||
return DoorlockResponse.Success
|
||||
|
||||
def request(self, state):
|
||||
old_state = self.state
|
||||
if state == DoorState.Closed:
|
||||
err = self.close()
|
||||
elif state == DoorState.Present:
|
||||
err = self.present()
|
||||
elif state == DoorState.Open:
|
||||
err = self.open()
|
||||
|
||||
self.sound_helper(old_state, self.state, False)
|
||||
self.invoke_callback(err)
|
||||
return err
|
||||
|
||||
def sound_helper(self, old_state, new_state, button):
|
||||
if not self.sounds:
|
||||
return
|
||||
|
||||
# TBD: Emergency Unlock
|
||||
# wave_emergency = 'emergency_unlock.wav'
|
||||
|
||||
if old_state == new_state:
|
||||
filename = self.wave_zonk
|
||||
elif button:
|
||||
if new_state == DoorState.Open:
|
||||
filename = self.wave_unlock_button
|
||||
elif new_state == DoorState.Present:
|
||||
filename = self.wave_present_button
|
||||
elif new_state == DoorState.Closed:
|
||||
filename = self.wave_lock_button
|
||||
else:
|
||||
if new_state == DoorState.Open:
|
||||
filename = self.wave_unlock
|
||||
elif new_state == DoorState.Present:
|
||||
filename = self.wave_present
|
||||
elif new_state == DoorState.Closed:
|
||||
filename = self.wave_lock
|
||||
|
||||
run_background('aplay %s' % join(self.sounds_prefix, filename))
|
||||
|
||||
def run_hook(self, script):
|
||||
if not self.run_hooks:
|
||||
log.info('Hooks disabled: not starting %s' % script)
|
||||
return
|
||||
log.info('Starting hook %s' % script)
|
||||
run_background(join(self.scripts_prefix, script))
|
||||
|
||||
def register_callback(self, callback):
|
||||
self._callback = callback
|
||||
|
||||
def invoke_callback(self, val):
|
||||
if self._callback:
|
||||
self._callback(val)
|
205
pydoorlock/WebApp.py
Normal file
|
@ -0,0 +1,205 @@
|
|||
"""
|
||||
Doorlockd -- Binary Kitchen's smart door opener
|
||||
|
||||
Copyright (c) Binary Kitchen e.V., 2020
|
||||
|
||||
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 logging
|
||||
import json
|
||||
import threading
|
||||
|
||||
from flask import abort, Flask, jsonify, render_template, request, Response
|
||||
from flask_wtf import FlaskForm
|
||||
from wtforms import PasswordField, StringField, SubmitField
|
||||
from wtforms.validators import DataRequired, Length
|
||||
|
||||
from .Door import DoorState
|
||||
from .Doorlock import DoorlockResponse
|
||||
|
||||
log = logging.getLogger()
|
||||
webapp = Flask(__name__)
|
||||
evt = threading.Event()
|
||||
json_push_state = ""
|
||||
|
||||
|
||||
def emit_doorstate(response=None):
|
||||
global json_push_state
|
||||
|
||||
if response:
|
||||
message = str(response)
|
||||
else:
|
||||
message = str(logic.state)
|
||||
|
||||
json_push_state = json.dumps({
|
||||
'message': message,
|
||||
'img': logic.state.to_img()})
|
||||
|
||||
# Notify push clients
|
||||
evt.set()
|
||||
evt.clear()
|
||||
|
||||
|
||||
def push_state():
|
||||
def event_str():
|
||||
return "data: {}\n\n".format(json_push_state)
|
||||
|
||||
try:
|
||||
yield event_str()
|
||||
while True:
|
||||
evt.wait()
|
||||
yield event_str()
|
||||
except GeneratorExit:
|
||||
return
|
||||
|
||||
|
||||
class AuthenticationForm(FlaskForm):
|
||||
username = StringField('Username', [Length(min=3, max=25)])
|
||||
password = PasswordField('Password', [DataRequired()])
|
||||
open = SubmitField('Open')
|
||||
present = SubmitField('Present')
|
||||
close = SubmitField('Close')
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
FlaskForm.__init__(self, *args, **kwargs)
|
||||
self.desired_state = DoorState.Closed
|
||||
|
||||
def validate(self):
|
||||
if not FlaskForm.validate(self):
|
||||
return False
|
||||
|
||||
if self.open.data:
|
||||
self.desired_state = DoorState.Open
|
||||
elif self.present.data:
|
||||
self.desired_state = DoorState.Present
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@webapp.route('/display')
|
||||
def display():
|
||||
return render_template('display.html',
|
||||
room=room,
|
||||
title=title,
|
||||
welcome=welcome)
|
||||
|
||||
|
||||
@webapp.route('/push')
|
||||
def push():
|
||||
emit_doorstate()
|
||||
resp = Response(push_state(), mimetype="text/event-stream")
|
||||
resp.headers['X-Accel-Buffering'] = 'no'
|
||||
resp.headers['Cache-Control'] = 'no-cache'
|
||||
return resp
|
||||
|
||||
|
||||
@webapp.route('/api', methods=['POST'])
|
||||
def api():
|
||||
def json_response(response, msg=None):
|
||||
json_dict = dict()
|
||||
json_dict['err'] = response.value
|
||||
json_dict['msg'] = str(response) if msg is None else msg
|
||||
if response == DoorlockResponse.Success or \
|
||||
response == DoorlockResponse.AlreadyActive:
|
||||
# TBD: Remove 'open'. No more users. Still used in App Version 2.1.1!
|
||||
json_dict['open'] = logic.state.is_open()
|
||||
json_dict['status'] = logic.state.value
|
||||
return jsonify(json_dict)
|
||||
|
||||
user = request.form.get('user')
|
||||
password = request.form.get('pass')
|
||||
command = request.form.get('command')
|
||||
|
||||
if (command is not None) and (command == 'status'):
|
||||
return json_response(DoorlockResponse.Success)
|
||||
|
||||
if any(v is None for v in [user, password, command]):
|
||||
log.warning('Incomplete API request')
|
||||
abort(400)
|
||||
|
||||
if not user or not password:
|
||||
log.warning('Invalid username or password format')
|
||||
return json_response(DoorlockResponse.Inval,
|
||||
'Invalid username or password format')
|
||||
|
||||
credentials = user, password
|
||||
|
||||
desired_state = DoorState.from_string(command)
|
||||
if not desired_state:
|
||||
return json_response(DoorlockResponse.Inval, "Invalid command requested")
|
||||
|
||||
log.info('Incoming API request from %s' % user.encode('utf-8'))
|
||||
log.info(' desired state: %s' % desired_state)
|
||||
log.info(' current state: %s' % logic.state)
|
||||
|
||||
response = logic.request(desired_state, credentials)
|
||||
|
||||
return json_response(response)
|
||||
|
||||
|
||||
@webapp.route('/', methods=['GET', 'POST'])
|
||||
def home():
|
||||
authentication_form = AuthenticationForm()
|
||||
response = None
|
||||
|
||||
if request.method == 'POST' and authentication_form.validate():
|
||||
user = authentication_form.username.data
|
||||
password = authentication_form.password.data
|
||||
credentials = user, password
|
||||
|
||||
log.info('Incoming request from %s' % user.encode('utf-8'))
|
||||
desired_state = authentication_form.desired_state
|
||||
log.info(' desired state: %s' % desired_state)
|
||||
log.info(' current state: %s' % logic.state)
|
||||
|
||||
response = logic.request(desired_state, credentials)
|
||||
log.info(' response: %s' % response)
|
||||
|
||||
# Don't trust python, zero credentials
|
||||
user = password = credentials = None
|
||||
|
||||
return render_template('index.html',
|
||||
authentication_form=authentication_form,
|
||||
response=response,
|
||||
state_text=str(logic.state),
|
||||
led=logic.state.to_img(),
|
||||
banner='%s - %s' % (title, room))
|
||||
|
||||
|
||||
def webapp_run(cfg, my_logic, status, version, template_folder, static_folder):
|
||||
global logic
|
||||
logic = my_logic
|
||||
|
||||
debug = cfg.boolean('DEBUG')
|
||||
|
||||
host = 'localhost'
|
||||
if debug:
|
||||
host = '0.0.0.0'
|
||||
|
||||
global room
|
||||
room = cfg.str('ROOM')
|
||||
|
||||
global title
|
||||
title = cfg.str('TITLE')
|
||||
|
||||
global welcome
|
||||
welcome = cfg.str('WELCOME')
|
||||
|
||||
global html_title
|
||||
html_title = '%s (%s - v%s)' % (title, status, version)
|
||||
|
||||
webapp.config['SECRET_KEY'] = cfg.str('SECRET_KEY')
|
||||
webapp.template_folder = template_folder
|
||||
webapp.static_folder = static_folder
|
||||
webapp.debug = debug
|
||||
webapp.run(host=host, port=8080)
|
0
pydoorlock/__init__.py
Normal file
29
schematics/Doorlock.txt
Normal file
|
@ -0,0 +1,29 @@
|
|||
Qty Value Device Package Parts Reichelt
|
||||
2 BC817-16-NPN-SOT23-BEC SOT23-BEC T1, T2 BC 816-16 SMD
|
||||
1 CRYSTALHC49UP HC49UP Q1 4,0000-HC49-SMD
|
||||
2 LEDCHIP-LED0805 CHIP-LED0805 LED1, LED2 SLO SMD-R0805-0
|
||||
1 PINHD-1X2 1X02 JP1 BKL 10120183
|
||||
1 SJ2W SJ_2 SJ1 JUMPER 2,54GL SW
|
||||
1 0 R-EU_R0805 R0805 R14 SMD-0805 0,00
|
||||
2 100 R-EU_R0805 R0805 R16, R17 SMD-0805 100
|
||||
6 100n C-EUC0805 C0805 C3, C5, C6, C8, C9, C11 X7R 0805 CD 120N
|
||||
5 10k R-EU_R0805 R0805 R2, R6, R7, R11, R15 SMD-0805 10,0K
|
||||
7 1k R-EU_R0805 R0805 R1, R3, R4, R5, R10, R18, R19 SMD-0805 1,00K
|
||||
2 220 R-EU_R0805 R0805 R12, R13 SMD-0805 220
|
||||
2 22p C-EUC0805 C0805 C1, C2 NPO 0805 BG 22P
|
||||
1 24C32ASN 24C32ASN SO-08 IC2 ST 24C32 MN6
|
||||
2 3k3 R-EU_R0805 R0805 R8, R9 SMD-0805 3,30K
|
||||
3 3k9 R-EU_R0805 R0805 R20, R21, R22 SMD-0805 3,90K
|
||||
1 AT90S2313S AT90S2313S SO20L IC1 ATTINY 2313A-SU
|
||||
2 CGRM4002-G CGRM4002-G SOD-123_MINI-SMA D1, D2 RND 1N4148W
|
||||
2 G5V1 G5V1 G5V1 K1, K2 G5V-1 5V
|
||||
1 ILD205 ILD205 SOIC08 OK1 ILD 217T
|
||||
1 RASPI_BOARD_B+#_H RASPI_BOARD_B+#_H RASPI_BOARD_B+HAT X1 /
|
||||
1 RIA182-04 RIA182-04 RIA182-04 X2 AKL 169-04 / AKL 182-04
|
||||
1 RIA182-05 RIA182-05 RIA182-05 X4 AKL 169-05 / AKL 182-05
|
||||
1 RJ45 RJ45-8 RJ45-8 J1 MEBP 8-8S
|
||||
1 VF1000/10K-G CPOL-EUUD-10X10 UD-10X10_NICHICON C4 VF 1000/10K-G
|
||||
|
||||
Buchsenleiste: MPE 094-2-040
|
||||
|
||||
https://www.reichelt.de/my/1503673
|
35
scripts/gen_protocol.sh
Executable file
|
@ -0,0 +1,35 @@
|
|||
#!/bin/bash
|
||||
#
|
||||
# doorlock-avr, AVR code of Binary Kitchen's doorlock
|
||||
#
|
||||
# Copyright (c) Binary Kitchen, 2019
|
||||
#
|
||||
# Authors:
|
||||
# Ralf Ramsauer <ralf@binary-kitchen.de>
|
||||
#
|
||||
# This work is licensed under the terms of the GNU GPL, version 2. See
|
||||
# the COPYING file in the top-level directory.
|
||||
|
||||
PROTOCOL_H=$1
|
||||
|
||||
function find_defines() {
|
||||
header=$1
|
||||
prefix=$2
|
||||
search="#define\s\+${prefix}\(\S*\)\s\+\(\S*\).*"
|
||||
replace=" \1\t= b\2"
|
||||
|
||||
grep $prefix $header | sed -e "s/$search/$replace/"
|
||||
}
|
||||
|
||||
AVR_COMM=$(find_defines $PROTOCOL_H AVR_)
|
||||
|
||||
cat <<END
|
||||
# This file is autogenerated. If you need to change it, edit
|
||||
# scripts/gen_protocol.sh instead.
|
||||
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class Protocol(Enum):
|
||||
${AVR_COMM}
|
||||
END
|
25
setup.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
#
|
||||
# pydoorlock, the python library for doorlock
|
||||
#
|
||||
# Author:
|
||||
# Ralf Ramsauer <ralf@binary-kitchen.de>
|
||||
#
|
||||
# Copyright (c) Binary Kitchen, 2018-2021
|
||||
#
|
||||
# This work is licensed under the terms of the GNU GPL, version 2. See
|
||||
# the COPYING file in the top-level directory.
|
||||
#
|
||||
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
with open("VERSION") as version_file:
|
||||
version = version_file.read().lstrip("v").rstrip()
|
||||
|
||||
setup(name="pydoorlock", version=version,
|
||||
description="A Python interface for doorlock",
|
||||
license="GPLv2",
|
||||
url="https://github.com/Binary-Kitchen/doorlockd/",
|
||||
author_email="ralf@binary-kitchen.de",
|
||||
packages=find_packages(),
|
||||
install_requires=["Flask", "Flask-WTF", "pyserial", "python-ldap"],
|
||||
)
|
3
share/doorlockd/scripts/post_lock
Executable file
|
@ -0,0 +1,3 @@
|
|||
#!/bin/bash
|
||||
|
||||
mosquitto_pub -r -t kitchen/doorlock/frontdoor/lockstate -m "locked"
|
3
share/doorlockd/scripts/post_present
Executable file
|
@ -0,0 +1,3 @@
|
|||
#!/bin/bash
|
||||
|
||||
mosquitto_pub -r -t kitchen/doorlock/frontdoor/lockstate -m "present"
|
3
share/doorlockd/scripts/post_unlock
Executable file
|
@ -0,0 +1,3 @@
|
|||
#!/bin/bash
|
||||
|
||||
mosquitto_pub -r -t kitchen/doorlock/frontdoor/lockstate -m "unlocked"
|
BIN
share/doorlockd/sounds/alarm.mp3
Normal file
BIN
share/doorlockd/sounds/door_call.mp3
Normal file
BIN
share/doorlockd/sounds/present.wav
Normal file
7
share/doorlockd/static/css/bootstrap.min.css
vendored
Normal file
1
share/doorlockd/static/css/bootstrap.min.css.map
Normal file
7
share/doorlockd/static/js/bootstrap.min.js
vendored
Normal file
2
share/doorlockd/static/js/jquery.min.js
vendored
Normal file
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
BIN
share/doorlockd/static/led-yellow.png
Normal file
After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 8.2 KiB After Width: | Height: | Size: 8.2 KiB |
532
share/doorlockd/static/token.svg
Normal file
|
@ -0,0 +1,532 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<!-- Created with qrencode 4.0.2 (https://fukuchi.org/works/qrencode/index.html) -->
|
||||
<svg width="4.34cm" height="4.34cm" viewBox="0 0 41 41" preserveAspectRatio="none" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="QRcode">
|
||||
<rect x="0" y="0" width="41" height="41" fill="#ffffff"/>
|
||||
<g id="Pattern" transform="translate(4,4)">
|
||||
<rect x="0" y="0" width="1" height="1" fill="#000000"/>
|
||||
<rect x="1" y="0" width="1" height="1" fill="#000000"/>
|
||||
<rect x="2" y="0" width="1" height="1" fill="#000000"/>
|
||||
<rect x="3" y="0" width="1" height="1" fill="#000000"/>
|
||||
<rect x="4" y="0" width="1" height="1" fill="#000000"/>
|
||||
<rect x="5" y="0" width="1" height="1" fill="#000000"/>
|
||||
<rect x="6" y="0" width="1" height="1" fill="#000000"/>
|
||||
<rect x="8" y="0" width="1" height="1" fill="#000000"/>
|
||||
<rect x="9" y="0" width="1" height="1" fill="#000000"/>
|
||||
<rect x="13" y="0" width="1" height="1" fill="#000000"/>
|
||||
<rect x="14" y="0" width="1" height="1" fill="#000000"/>
|
||||
<rect x="18" y="0" width="1" height="1" fill="#000000"/>
|
||||
<rect x="21" y="0" width="1" height="1" fill="#000000"/>
|
||||
<rect x="26" y="0" width="1" height="1" fill="#000000"/>
|
||||
<rect x="27" y="0" width="1" height="1" fill="#000000"/>
|
||||
<rect x="28" y="0" width="1" height="1" fill="#000000"/>
|
||||
<rect x="29" y="0" width="1" height="1" fill="#000000"/>
|
||||
<rect x="30" y="0" width="1" height="1" fill="#000000"/>
|
||||
<rect x="31" y="0" width="1" height="1" fill="#000000"/>
|
||||
<rect x="32" y="0" width="1" height="1" fill="#000000"/>
|
||||
<rect x="0" y="1" width="1" height="1" fill="#000000"/>
|
||||
<rect x="6" y="1" width="1" height="1" fill="#000000"/>
|
||||
<rect x="12" y="1" width="1" height="1" fill="#000000"/>
|
||||
<rect x="14" y="1" width="1" height="1" fill="#000000"/>
|
||||
<rect x="18" y="1" width="1" height="1" fill="#000000"/>
|
||||
<rect x="19" y="1" width="1" height="1" fill="#000000"/>
|
||||
<rect x="21" y="1" width="1" height="1" fill="#000000"/>
|
||||
<rect x="26" y="1" width="1" height="1" fill="#000000"/>
|
||||
<rect x="32" y="1" width="1" height="1" fill="#000000"/>
|
||||
<rect x="0" y="2" width="1" height="1" fill="#000000"/>
|
||||
<rect x="2" y="2" width="1" height="1" fill="#000000"/>
|
||||
<rect x="3" y="2" width="1" height="1" fill="#000000"/>
|
||||
<rect x="4" y="2" width="1" height="1" fill="#000000"/>
|
||||
<rect x="6" y="2" width="1" height="1" fill="#000000"/>
|
||||
<rect x="9" y="2" width="1" height="1" fill="#000000"/>
|
||||
<rect x="10" y="2" width="1" height="1" fill="#000000"/>
|
||||
<rect x="12" y="2" width="1" height="1" fill="#000000"/>
|
||||
<rect x="13" y="2" width="1" height="1" fill="#000000"/>
|
||||
<rect x="14" y="2" width="1" height="1" fill="#000000"/>
|
||||
<rect x="15" y="2" width="1" height="1" fill="#000000"/>
|
||||
<rect x="20" y="2" width="1" height="1" fill="#000000"/>
|
||||
<rect x="21" y="2" width="1" height="1" fill="#000000"/>
|
||||
<rect x="22" y="2" width="1" height="1" fill="#000000"/>
|
||||
<rect x="23" y="2" width="1" height="1" fill="#000000"/>
|
||||
<rect x="26" y="2" width="1" height="1" fill="#000000"/>
|
||||
<rect x="28" y="2" width="1" height="1" fill="#000000"/>
|
||||
<rect x="29" y="2" width="1" height="1" fill="#000000"/>
|
||||
<rect x="30" y="2" width="1" height="1" fill="#000000"/>
|
||||
<rect x="32" y="2" width="1" height="1" fill="#000000"/>
|
||||
<rect x="0" y="3" width="1" height="1" fill="#000000"/>
|
||||
<rect x="2" y="3" width="1" height="1" fill="#000000"/>
|
||||
<rect x="3" y="3" width="1" height="1" fill="#000000"/>
|
||||
<rect x="4" y="3" width="1" height="1" fill="#000000"/>
|
||||
<rect x="6" y="3" width="1" height="1" fill="#000000"/>
|
||||
<rect x="10" y="3" width="1" height="1" fill="#000000"/>
|
||||
<rect x="13" y="3" width="1" height="1" fill="#000000"/>
|
||||
<rect x="17" y="3" width="1" height="1" fill="#000000"/>
|
||||
<rect x="19" y="3" width="1" height="1" fill="#000000"/>
|
||||
<rect x="21" y="3" width="1" height="1" fill="#000000"/>
|
||||
<rect x="23" y="3" width="1" height="1" fill="#000000"/>
|
||||
<rect x="24" y="3" width="1" height="1" fill="#000000"/>
|
||||
<rect x="26" y="3" width="1" height="1" fill="#000000"/>
|
||||
<rect x="28" y="3" width="1" height="1" fill="#000000"/>
|
||||
<rect x="29" y="3" width="1" height="1" fill="#000000"/>
|
||||
<rect x="30" y="3" width="1" height="1" fill="#000000"/>
|
||||
<rect x="32" y="3" width="1" height="1" fill="#000000"/>
|
||||
<rect x="0" y="4" width="1" height="1" fill="#000000"/>
|
||||
<rect x="2" y="4" width="1" height="1" fill="#000000"/>
|
||||
<rect x="3" y="4" width="1" height="1" fill="#000000"/>
|
||||
<rect x="4" y="4" width="1" height="1" fill="#000000"/>
|
||||
<rect x="6" y="4" width="1" height="1" fill="#000000"/>
|
||||
<rect x="13" y="4" width="1" height="1" fill="#000000"/>
|
||||
<rect x="14" y="4" width="1" height="1" fill="#000000"/>
|
||||
<rect x="16" y="4" width="1" height="1" fill="#000000"/>
|
||||
<rect x="17" y="4" width="1" height="1" fill="#000000"/>
|
||||
<rect x="18" y="4" width="1" height="1" fill="#000000"/>
|
||||
<rect x="19" y="4" width="1" height="1" fill="#000000"/>
|
||||
<rect x="20" y="4" width="1" height="1" fill="#000000"/>
|
||||
<rect x="21" y="4" width="1" height="1" fill="#000000"/>
|
||||
<rect x="26" y="4" width="1" height="1" fill="#000000"/>
|
||||
<rect x="28" y="4" width="1" height="1" fill="#000000"/>
|
||||
<rect x="29" y="4" width="1" height="1" fill="#000000"/>
|
||||
<rect x="30" y="4" width="1" height="1" fill="#000000"/>
|
||||
<rect x="32" y="4" width="1" height="1" fill="#000000"/>
|
||||
<rect x="0" y="5" width="1" height="1" fill="#000000"/>
|
||||
<rect x="6" y="5" width="1" height="1" fill="#000000"/>
|
||||
<rect x="9" y="5" width="1" height="1" fill="#000000"/>
|
||||
<rect x="11" y="5" width="1" height="1" fill="#000000"/>
|
||||
<rect x="17" y="5" width="1" height="1" fill="#000000"/>
|
||||
<rect x="20" y="5" width="1" height="1" fill="#000000"/>
|
||||
<rect x="23" y="5" width="1" height="1" fill="#000000"/>
|
||||
<rect x="24" y="5" width="1" height="1" fill="#000000"/>
|
||||
<rect x="26" y="5" width="1" height="1" fill="#000000"/>
|
||||
<rect x="32" y="5" width="1" height="1" fill="#000000"/>
|
||||
<rect x="0" y="6" width="1" height="1" fill="#000000"/>
|
||||
<rect x="1" y="6" width="1" height="1" fill="#000000"/>
|
||||
<rect x="2" y="6" width="1" height="1" fill="#000000"/>
|
||||
<rect x="3" y="6" width="1" height="1" fill="#000000"/>
|
||||
<rect x="4" y="6" width="1" height="1" fill="#000000"/>
|
||||
<rect x="5" y="6" width="1" height="1" fill="#000000"/>
|
||||
<rect x="6" y="6" width="1" height="1" fill="#000000"/>
|
||||
<rect x="8" y="6" width="1" height="1" fill="#000000"/>
|
||||
<rect x="10" y="6" width="1" height="1" fill="#000000"/>
|
||||
<rect x="12" y="6" width="1" height="1" fill="#000000"/>
|
||||
<rect x="14" y="6" width="1" height="1" fill="#000000"/>
|
||||
<rect x="16" y="6" width="1" height="1" fill="#000000"/>
|
||||
<rect x="18" y="6" width="1" height="1" fill="#000000"/>
|
||||
<rect x="20" y="6" width="1" height="1" fill="#000000"/>
|
||||
<rect x="22" y="6" width="1" height="1" fill="#000000"/>
|
||||
<rect x="24" y="6" width="1" height="1" fill="#000000"/>
|
||||
<rect x="26" y="6" width="1" height="1" fill="#000000"/>
|
||||
<rect x="27" y="6" width="1" height="1" fill="#000000"/>
|
||||
<rect x="28" y="6" width="1" height="1" fill="#000000"/>
|
||||
<rect x="29" y="6" width="1" height="1" fill="#000000"/>
|
||||
<rect x="30" y="6" width="1" height="1" fill="#000000"/>
|
||||
<rect x="31" y="6" width="1" height="1" fill="#000000"/>
|
||||
<rect x="32" y="6" width="1" height="1" fill="#000000"/>
|
||||
<rect x="8" y="7" width="1" height="1" fill="#000000"/>
|
||||
<rect x="9" y="7" width="1" height="1" fill="#000000"/>
|
||||
<rect x="12" y="7" width="1" height="1" fill="#000000"/>
|
||||
<rect x="13" y="7" width="1" height="1" fill="#000000"/>
|
||||
<rect x="14" y="7" width="1" height="1" fill="#000000"/>
|
||||
<rect x="18" y="7" width="1" height="1" fill="#000000"/>
|
||||
<rect x="20" y="7" width="1" height="1" fill="#000000"/>
|
||||
<rect x="22" y="7" width="1" height="1" fill="#000000"/>
|
||||
<rect x="0" y="8" width="1" height="1" fill="#000000"/>
|
||||
<rect x="1" y="8" width="1" height="1" fill="#000000"/>
|
||||
<rect x="3" y="8" width="1" height="1" fill="#000000"/>
|
||||
<rect x="4" y="8" width="1" height="1" fill="#000000"/>
|
||||
<rect x="6" y="8" width="1" height="1" fill="#000000"/>
|
||||
<rect x="9" y="8" width="1" height="1" fill="#000000"/>
|
||||
<rect x="12" y="8" width="1" height="1" fill="#000000"/>
|
||||
<rect x="14" y="8" width="1" height="1" fill="#000000"/>
|
||||
<rect x="16" y="8" width="1" height="1" fill="#000000"/>
|
||||
<rect x="17" y="8" width="1" height="1" fill="#000000"/>
|
||||
<rect x="18" y="8" width="1" height="1" fill="#000000"/>
|
||||
<rect x="19" y="8" width="1" height="1" fill="#000000"/>
|
||||
<rect x="20" y="8" width="1" height="1" fill="#000000"/>
|
||||
<rect x="22" y="8" width="1" height="1" fill="#000000"/>
|
||||
<rect x="26" y="8" width="1" height="1" fill="#000000"/>
|
||||
<rect x="32" y="8" width="1" height="1" fill="#000000"/>
|
||||
<rect x="1" y="9" width="1" height="1" fill="#000000"/>
|
||||
<rect x="3" y="9" width="1" height="1" fill="#000000"/>
|
||||
<rect x="4" y="9" width="1" height="1" fill="#000000"/>
|
||||
<rect x="5" y="9" width="1" height="1" fill="#000000"/>
|
||||
<rect x="8" y="9" width="1" height="1" fill="#000000"/>
|
||||
<rect x="10" y="9" width="1" height="1" fill="#000000"/>
|
||||
<rect x="11" y="9" width="1" height="1" fill="#000000"/>
|
||||
<rect x="14" y="9" width="1" height="1" fill="#000000"/>
|
||||
<rect x="15" y="9" width="1" height="1" fill="#000000"/>
|
||||
<rect x="17" y="9" width="1" height="1" fill="#000000"/>
|
||||
<rect x="19" y="9" width="1" height="1" fill="#000000"/>
|
||||
<rect x="21" y="9" width="1" height="1" fill="#000000"/>
|
||||
<rect x="22" y="9" width="1" height="1" fill="#000000"/>
|
||||
<rect x="23" y="9" width="1" height="1" fill="#000000"/>
|
||||
<rect x="27" y="9" width="1" height="1" fill="#000000"/>
|
||||
<rect x="28" y="9" width="1" height="1" fill="#000000"/>
|
||||
<rect x="30" y="9" width="1" height="1" fill="#000000"/>
|
||||
<rect x="31" y="9" width="1" height="1" fill="#000000"/>
|
||||
<rect x="1" y="10" width="1" height="1" fill="#000000"/>
|
||||
<rect x="5" y="10" width="1" height="1" fill="#000000"/>
|
||||
<rect x="6" y="10" width="1" height="1" fill="#000000"/>
|
||||
<rect x="7" y="10" width="1" height="1" fill="#000000"/>
|
||||
<rect x="8" y="10" width="1" height="1" fill="#000000"/>
|
||||
<rect x="9" y="10" width="1" height="1" fill="#000000"/>
|
||||
<rect x="11" y="10" width="1" height="1" fill="#000000"/>
|
||||
<rect x="15" y="10" width="1" height="1" fill="#000000"/>
|
||||
<rect x="16" y="10" width="1" height="1" fill="#000000"/>
|
||||
<rect x="17" y="10" width="1" height="1" fill="#000000"/>
|
||||
<rect x="20" y="10" width="1" height="1" fill="#000000"/>
|
||||
<rect x="22" y="10" width="1" height="1" fill="#000000"/>
|
||||
<rect x="23" y="10" width="1" height="1" fill="#000000"/>
|
||||
<rect x="25" y="10" width="1" height="1" fill="#000000"/>
|
||||
<rect x="28" y="10" width="1" height="1" fill="#000000"/>
|
||||
<rect x="31" y="10" width="1" height="1" fill="#000000"/>
|
||||
<rect x="32" y="10" width="1" height="1" fill="#000000"/>
|
||||
<rect x="0" y="11" width="1" height="1" fill="#000000"/>
|
||||
<rect x="7" y="11" width="1" height="1" fill="#000000"/>
|
||||
<rect x="8" y="11" width="1" height="1" fill="#000000"/>
|
||||
<rect x="14" y="11" width="1" height="1" fill="#000000"/>
|
||||
<rect x="15" y="11" width="1" height="1" fill="#000000"/>
|
||||
<rect x="16" y="11" width="1" height="1" fill="#000000"/>
|
||||
<rect x="17" y="11" width="1" height="1" fill="#000000"/>
|
||||
<rect x="20" y="11" width="1" height="1" fill="#000000"/>
|
||||
<rect x="24" y="11" width="1" height="1" fill="#000000"/>
|
||||
<rect x="25" y="11" width="1" height="1" fill="#000000"/>
|
||||
<rect x="26" y="11" width="1" height="1" fill="#000000"/>
|
||||
<rect x="27" y="11" width="1" height="1" fill="#000000"/>
|
||||
<rect x="29" y="11" width="1" height="1" fill="#000000"/>
|
||||
<rect x="30" y="11" width="1" height="1" fill="#000000"/>
|
||||
<rect x="3" y="12" width="1" height="1" fill="#000000"/>
|
||||
<rect x="4" y="12" width="1" height="1" fill="#000000"/>
|
||||
<rect x="5" y="12" width="1" height="1" fill="#000000"/>
|
||||
<rect x="6" y="12" width="1" height="1" fill="#000000"/>
|
||||
<rect x="9" y="12" width="1" height="1" fill="#000000"/>
|
||||
<rect x="10" y="12" width="1" height="1" fill="#000000"/>
|
||||
<rect x="16" y="12" width="1" height="1" fill="#000000"/>
|
||||
<rect x="20" y="12" width="1" height="1" fill="#000000"/>
|
||||
<rect x="23" y="12" width="1" height="1" fill="#000000"/>
|
||||
<rect x="26" y="12" width="1" height="1" fill="#000000"/>
|
||||
<rect x="27" y="12" width="1" height="1" fill="#000000"/>
|
||||
<rect x="31" y="12" width="1" height="1" fill="#000000"/>
|
||||
<rect x="32" y="12" width="1" height="1" fill="#000000"/>
|
||||
<rect x="0" y="13" width="1" height="1" fill="#000000"/>
|
||||
<rect x="2" y="13" width="1" height="1" fill="#000000"/>
|
||||
<rect x="3" y="13" width="1" height="1" fill="#000000"/>
|
||||
<rect x="4" y="13" width="1" height="1" fill="#000000"/>
|
||||
<rect x="8" y="13" width="1" height="1" fill="#000000"/>
|
||||
<rect x="10" y="13" width="1" height="1" fill="#000000"/>
|
||||
<rect x="14" y="13" width="1" height="1" fill="#000000"/>
|
||||
<rect x="15" y="13" width="1" height="1" fill="#000000"/>
|
||||
<rect x="16" y="13" width="1" height="1" fill="#000000"/>
|
||||
<rect x="18" y="13" width="1" height="1" fill="#000000"/>
|
||||
<rect x="23" y="13" width="1" height="1" fill="#000000"/>
|
||||
<rect x="25" y="13" width="1" height="1" fill="#000000"/>
|
||||
<rect x="27" y="13" width="1" height="1" fill="#000000"/>
|
||||
<rect x="0" y="14" width="1" height="1" fill="#000000"/>
|
||||
<rect x="3" y="14" width="1" height="1" fill="#000000"/>
|
||||
<rect x="4" y="14" width="1" height="1" fill="#000000"/>
|
||||
<rect x="5" y="14" width="1" height="1" fill="#000000"/>
|
||||
<rect x="6" y="14" width="1" height="1" fill="#000000"/>
|
||||
<rect x="7" y="14" width="1" height="1" fill="#000000"/>
|
||||
<rect x="9" y="14" width="1" height="1" fill="#000000"/>
|
||||
<rect x="10" y="14" width="1" height="1" fill="#000000"/>
|
||||
<rect x="14" y="14" width="1" height="1" fill="#000000"/>
|
||||
<rect x="15" y="14" width="1" height="1" fill="#000000"/>
|
||||
<rect x="19" y="14" width="1" height="1" fill="#000000"/>
|
||||
<rect x="20" y="14" width="1" height="1" fill="#000000"/>
|
||||
<rect x="21" y="14" width="1" height="1" fill="#000000"/>
|
||||
<rect x="22" y="14" width="1" height="1" fill="#000000"/>
|
||||
<rect x="0" y="15" width="1" height="1" fill="#000000"/>
|
||||
<rect x="1" y="15" width="1" height="1" fill="#000000"/>
|
||||
<rect x="2" y="15" width="1" height="1" fill="#000000"/>
|
||||
<rect x="3" y="15" width="1" height="1" fill="#000000"/>
|
||||
<rect x="7" y="15" width="1" height="1" fill="#000000"/>
|
||||
<rect x="9" y="15" width="1" height="1" fill="#000000"/>
|
||||
<rect x="11" y="15" width="1" height="1" fill="#000000"/>
|
||||
<rect x="12" y="15" width="1" height="1" fill="#000000"/>
|
||||
<rect x="14" y="15" width="1" height="1" fill="#000000"/>
|
||||
<rect x="15" y="15" width="1" height="1" fill="#000000"/>
|
||||
<rect x="18" y="15" width="1" height="1" fill="#000000"/>
|
||||
<rect x="20" y="15" width="1" height="1" fill="#000000"/>
|
||||
<rect x="23" y="15" width="1" height="1" fill="#000000"/>
|
||||
<rect x="25" y="15" width="1" height="1" fill="#000000"/>
|
||||
<rect x="27" y="15" width="1" height="1" fill="#000000"/>
|
||||
<rect x="28" y="15" width="1" height="1" fill="#000000"/>
|
||||
<rect x="30" y="15" width="1" height="1" fill="#000000"/>
|
||||
<rect x="32" y="15" width="1" height="1" fill="#000000"/>
|
||||
<rect x="0" y="16" width="1" height="1" fill="#000000"/>
|
||||
<rect x="1" y="16" width="1" height="1" fill="#000000"/>
|
||||
<rect x="2" y="16" width="1" height="1" fill="#000000"/>
|
||||
<rect x="4" y="16" width="1" height="1" fill="#000000"/>
|
||||
<rect x="6" y="16" width="1" height="1" fill="#000000"/>
|
||||
<rect x="8" y="16" width="1" height="1" fill="#000000"/>
|
||||
<rect x="12" y="16" width="1" height="1" fill="#000000"/>
|
||||
<rect x="13" y="16" width="1" height="1" fill="#000000"/>
|
||||
<rect x="14" y="16" width="1" height="1" fill="#000000"/>
|
||||
<rect x="16" y="16" width="1" height="1" fill="#000000"/>
|
||||
<rect x="19" y="16" width="1" height="1" fill="#000000"/>
|
||||
<rect x="20" y="16" width="1" height="1" fill="#000000"/>
|
||||
<rect x="22" y="16" width="1" height="1" fill="#000000"/>
|
||||
<rect x="24" y="16" width="1" height="1" fill="#000000"/>
|
||||
<rect x="25" y="16" width="1" height="1" fill="#000000"/>
|
||||
<rect x="26" y="16" width="1" height="1" fill="#000000"/>
|
||||
<rect x="27" y="16" width="1" height="1" fill="#000000"/>
|
||||
<rect x="28" y="16" width="1" height="1" fill="#000000"/>
|
||||
<rect x="29" y="16" width="1" height="1" fill="#000000"/>
|
||||
<rect x="31" y="16" width="1" height="1" fill="#000000"/>
|
||||
<rect x="32" y="16" width="1" height="1" fill="#000000"/>
|
||||
<rect x="1" y="17" width="1" height="1" fill="#000000"/>
|
||||
<rect x="7" y="17" width="1" height="1" fill="#000000"/>
|
||||
<rect x="9" y="17" width="1" height="1" fill="#000000"/>
|
||||
<rect x="13" y="17" width="1" height="1" fill="#000000"/>
|
||||
<rect x="17" y="17" width="1" height="1" fill="#000000"/>
|
||||
<rect x="18" y="17" width="1" height="1" fill="#000000"/>
|
||||
<rect x="19" y="17" width="1" height="1" fill="#000000"/>
|
||||
<rect x="20" y="17" width="1" height="1" fill="#000000"/>
|
||||
<rect x="24" y="17" width="1" height="1" fill="#000000"/>
|
||||
<rect x="26" y="17" width="1" height="1" fill="#000000"/>
|
||||
<rect x="27" y="17" width="1" height="1" fill="#000000"/>
|
||||
<rect x="28" y="17" width="1" height="1" fill="#000000"/>
|
||||
<rect x="31" y="17" width="1" height="1" fill="#000000"/>
|
||||
<rect x="32" y="17" width="1" height="1" fill="#000000"/>
|
||||
<rect x="2" y="18" width="1" height="1" fill="#000000"/>
|
||||
<rect x="4" y="18" width="1" height="1" fill="#000000"/>
|
||||
<rect x="6" y="18" width="1" height="1" fill="#000000"/>
|
||||
<rect x="8" y="18" width="1" height="1" fill="#000000"/>
|
||||
<rect x="10" y="18" width="1" height="1" fill="#000000"/>
|
||||
<rect x="11" y="18" width="1" height="1" fill="#000000"/>
|
||||
<rect x="15" y="18" width="1" height="1" fill="#000000"/>
|
||||
<rect x="17" y="18" width="1" height="1" fill="#000000"/>
|
||||
<rect x="19" y="18" width="1" height="1" fill="#000000"/>
|
||||
<rect x="21" y="18" width="1" height="1" fill="#000000"/>
|
||||
<rect x="22" y="18" width="1" height="1" fill="#000000"/>
|
||||
<rect x="23" y="18" width="1" height="1" fill="#000000"/>
|
||||
<rect x="27" y="18" width="1" height="1" fill="#000000"/>
|
||||
<rect x="32" y="18" width="1" height="1" fill="#000000"/>
|
||||
<rect x="3" y="19" width="1" height="1" fill="#000000"/>
|
||||
<rect x="7" y="19" width="1" height="1" fill="#000000"/>
|
||||
<rect x="8" y="19" width="1" height="1" fill="#000000"/>
|
||||
<rect x="11" y="19" width="1" height="1" fill="#000000"/>
|
||||
<rect x="12" y="19" width="1" height="1" fill="#000000"/>
|
||||
<rect x="13" y="19" width="1" height="1" fill="#000000"/>
|
||||
<rect x="14" y="19" width="1" height="1" fill="#000000"/>
|
||||
<rect x="19" y="19" width="1" height="1" fill="#000000"/>
|
||||
<rect x="20" y="19" width="1" height="1" fill="#000000"/>
|
||||
<rect x="25" y="19" width="1" height="1" fill="#000000"/>
|
||||
<rect x="26" y="19" width="1" height="1" fill="#000000"/>
|
||||
<rect x="27" y="19" width="1" height="1" fill="#000000"/>
|
||||
<rect x="28" y="19" width="1" height="1" fill="#000000"/>
|
||||
<rect x="30" y="19" width="1" height="1" fill="#000000"/>
|
||||
<rect x="31" y="19" width="1" height="1" fill="#000000"/>
|
||||
<rect x="32" y="19" width="1" height="1" fill="#000000"/>
|
||||
<rect x="1" y="20" width="1" height="1" fill="#000000"/>
|
||||
<rect x="2" y="20" width="1" height="1" fill="#000000"/>
|
||||
<rect x="3" y="20" width="1" height="1" fill="#000000"/>
|
||||
<rect x="4" y="20" width="1" height="1" fill="#000000"/>
|
||||
<rect x="5" y="20" width="1" height="1" fill="#000000"/>
|
||||
<rect x="6" y="20" width="1" height="1" fill="#000000"/>
|
||||
<rect x="8" y="20" width="1" height="1" fill="#000000"/>
|
||||
<rect x="10" y="20" width="1" height="1" fill="#000000"/>
|
||||
<rect x="12" y="20" width="1" height="1" fill="#000000"/>
|
||||
<rect x="14" y="20" width="1" height="1" fill="#000000"/>
|
||||
<rect x="17" y="20" width="1" height="1" fill="#000000"/>
|
||||
<rect x="18" y="20" width="1" height="1" fill="#000000"/>
|
||||
<rect x="19" y="20" width="1" height="1" fill="#000000"/>
|
||||
<rect x="23" y="20" width="1" height="1" fill="#000000"/>
|
||||
<rect x="24" y="20" width="1" height="1" fill="#000000"/>
|
||||
<rect x="27" y="20" width="1" height="1" fill="#000000"/>
|
||||
<rect x="0" y="21" width="1" height="1" fill="#000000"/>
|
||||
<rect x="1" y="21" width="1" height="1" fill="#000000"/>
|
||||
<rect x="3" y="21" width="1" height="1" fill="#000000"/>
|
||||
<rect x="5" y="21" width="1" height="1" fill="#000000"/>
|
||||
<rect x="8" y="21" width="1" height="1" fill="#000000"/>
|
||||
<rect x="9" y="21" width="1" height="1" fill="#000000"/>
|
||||
<rect x="10" y="21" width="1" height="1" fill="#000000"/>
|
||||
<rect x="11" y="21" width="1" height="1" fill="#000000"/>
|
||||
<rect x="14" y="21" width="1" height="1" fill="#000000"/>
|
||||
<rect x="15" y="21" width="1" height="1" fill="#000000"/>
|
||||
<rect x="17" y="21" width="1" height="1" fill="#000000"/>
|
||||
<rect x="18" y="21" width="1" height="1" fill="#000000"/>
|
||||
<rect x="19" y="21" width="1" height="1" fill="#000000"/>
|
||||
<rect x="23" y="21" width="1" height="1" fill="#000000"/>
|
||||
<rect x="28" y="21" width="1" height="1" fill="#000000"/>
|
||||
<rect x="29" y="21" width="1" height="1" fill="#000000"/>
|
||||
<rect x="31" y="21" width="1" height="1" fill="#000000"/>
|
||||
<rect x="0" y="22" width="1" height="1" fill="#000000"/>
|
||||
<rect x="2" y="22" width="1" height="1" fill="#000000"/>
|
||||
<rect x="6" y="22" width="1" height="1" fill="#000000"/>
|
||||
<rect x="7" y="22" width="1" height="1" fill="#000000"/>
|
||||
<rect x="8" y="22" width="1" height="1" fill="#000000"/>
|
||||
<rect x="11" y="22" width="1" height="1" fill="#000000"/>
|
||||
<rect x="15" y="22" width="1" height="1" fill="#000000"/>
|
||||
<rect x="20" y="22" width="1" height="1" fill="#000000"/>
|
||||
<rect x="21" y="22" width="1" height="1" fill="#000000"/>
|
||||
<rect x="23" y="22" width="1" height="1" fill="#000000"/>
|
||||
<rect x="25" y="22" width="1" height="1" fill="#000000"/>
|
||||
<rect x="26" y="22" width="1" height="1" fill="#000000"/>
|
||||
<rect x="31" y="22" width="1" height="1" fill="#000000"/>
|
||||
<rect x="32" y="22" width="1" height="1" fill="#000000"/>
|
||||
<rect x="0" y="23" width="1" height="1" fill="#000000"/>
|
||||
<rect x="4" y="23" width="1" height="1" fill="#000000"/>
|
||||
<rect x="8" y="23" width="1" height="1" fill="#000000"/>
|
||||
<rect x="15" y="23" width="1" height="1" fill="#000000"/>
|
||||
<rect x="16" y="23" width="1" height="1" fill="#000000"/>
|
||||
<rect x="17" y="23" width="1" height="1" fill="#000000"/>
|
||||
<rect x="18" y="23" width="1" height="1" fill="#000000"/>
|
||||
<rect x="20" y="23" width="1" height="1" fill="#000000"/>
|
||||
<rect x="22" y="23" width="1" height="1" fill="#000000"/>
|
||||
<rect x="25" y="23" width="1" height="1" fill="#000000"/>
|
||||
<rect x="30" y="23" width="1" height="1" fill="#000000"/>
|
||||
<rect x="0" y="24" width="1" height="1" fill="#000000"/>
|
||||
<rect x="1" y="24" width="1" height="1" fill="#000000"/>
|
||||
<rect x="2" y="24" width="1" height="1" fill="#000000"/>
|
||||
<rect x="3" y="24" width="1" height="1" fill="#000000"/>
|
||||
<rect x="4" y="24" width="1" height="1" fill="#000000"/>
|
||||
<rect x="5" y="24" width="1" height="1" fill="#000000"/>
|
||||
<rect x="6" y="24" width="1" height="1" fill="#000000"/>
|
||||
<rect x="7" y="24" width="1" height="1" fill="#000000"/>
|
||||
<rect x="9" y="24" width="1" height="1" fill="#000000"/>
|
||||
<rect x="12" y="24" width="1" height="1" fill="#000000"/>
|
||||
<rect x="15" y="24" width="1" height="1" fill="#000000"/>
|
||||
<rect x="19" y="24" width="1" height="1" fill="#000000"/>
|
||||
<rect x="24" y="24" width="1" height="1" fill="#000000"/>
|
||||
<rect x="25" y="24" width="1" height="1" fill="#000000"/>
|
||||
<rect x="26" y="24" width="1" height="1" fill="#000000"/>
|
||||
<rect x="27" y="24" width="1" height="1" fill="#000000"/>
|
||||
<rect x="28" y="24" width="1" height="1" fill="#000000"/>
|
||||
<rect x="31" y="24" width="1" height="1" fill="#000000"/>
|
||||
<rect x="8" y="25" width="1" height="1" fill="#000000"/>
|
||||
<rect x="9" y="25" width="1" height="1" fill="#000000"/>
|
||||
<rect x="11" y="25" width="1" height="1" fill="#000000"/>
|
||||
<rect x="14" y="25" width="1" height="1" fill="#000000"/>
|
||||
<rect x="15" y="25" width="1" height="1" fill="#000000"/>
|
||||
<rect x="16" y="25" width="1" height="1" fill="#000000"/>
|
||||
<rect x="18" y="25" width="1" height="1" fill="#000000"/>
|
||||
<rect x="22" y="25" width="1" height="1" fill="#000000"/>
|
||||
<rect x="24" y="25" width="1" height="1" fill="#000000"/>
|
||||
<rect x="28" y="25" width="1" height="1" fill="#000000"/>
|
||||
<rect x="30" y="25" width="1" height="1" fill="#000000"/>
|
||||
<rect x="31" y="25" width="1" height="1" fill="#000000"/>
|
||||
<rect x="0" y="26" width="1" height="1" fill="#000000"/>
|
||||
<rect x="1" y="26" width="1" height="1" fill="#000000"/>
|
||||
<rect x="2" y="26" width="1" height="1" fill="#000000"/>
|
||||
<rect x="3" y="26" width="1" height="1" fill="#000000"/>
|
||||
<rect x="4" y="26" width="1" height="1" fill="#000000"/>
|
||||
<rect x="5" y="26" width="1" height="1" fill="#000000"/>
|
||||
<rect x="6" y="26" width="1" height="1" fill="#000000"/>
|
||||
<rect x="9" y="26" width="1" height="1" fill="#000000"/>
|
||||
<rect x="10" y="26" width="1" height="1" fill="#000000"/>
|
||||
<rect x="12" y="26" width="1" height="1" fill="#000000"/>
|
||||
<rect x="14" y="26" width="1" height="1" fill="#000000"/>
|
||||
<rect x="15" y="26" width="1" height="1" fill="#000000"/>
|
||||
<rect x="17" y="26" width="1" height="1" fill="#000000"/>
|
||||
<rect x="18" y="26" width="1" height="1" fill="#000000"/>
|
||||
<rect x="19" y="26" width="1" height="1" fill="#000000"/>
|
||||
<rect x="20" y="26" width="1" height="1" fill="#000000"/>
|
||||
<rect x="22" y="26" width="1" height="1" fill="#000000"/>
|
||||
<rect x="23" y="26" width="1" height="1" fill="#000000"/>
|
||||
<rect x="24" y="26" width="1" height="1" fill="#000000"/>
|
||||
<rect x="26" y="26" width="1" height="1" fill="#000000"/>
|
||||
<rect x="28" y="26" width="1" height="1" fill="#000000"/>
|
||||
<rect x="30" y="26" width="1" height="1" fill="#000000"/>
|
||||
<rect x="0" y="27" width="1" height="1" fill="#000000"/>
|
||||
<rect x="6" y="27" width="1" height="1" fill="#000000"/>
|
||||
<rect x="9" y="27" width="1" height="1" fill="#000000"/>
|
||||
<rect x="16" y="27" width="1" height="1" fill="#000000"/>
|
||||
<rect x="18" y="27" width="1" height="1" fill="#000000"/>
|
||||
<rect x="23" y="27" width="1" height="1" fill="#000000"/>
|
||||
<rect x="24" y="27" width="1" height="1" fill="#000000"/>
|
||||
<rect x="28" y="27" width="1" height="1" fill="#000000"/>
|
||||
<rect x="29" y="27" width="1" height="1" fill="#000000"/>
|
||||
<rect x="30" y="27" width="1" height="1" fill="#000000"/>
|
||||
<rect x="32" y="27" width="1" height="1" fill="#000000"/>
|
||||
<rect x="0" y="28" width="1" height="1" fill="#000000"/>
|
||||
<rect x="2" y="28" width="1" height="1" fill="#000000"/>
|
||||
<rect x="3" y="28" width="1" height="1" fill="#000000"/>
|
||||
<rect x="4" y="28" width="1" height="1" fill="#000000"/>
|
||||
<rect x="6" y="28" width="1" height="1" fill="#000000"/>
|
||||
<rect x="8" y="28" width="1" height="1" fill="#000000"/>
|
||||
<rect x="10" y="28" width="1" height="1" fill="#000000"/>
|
||||
<rect x="12" y="28" width="1" height="1" fill="#000000"/>
|
||||
<rect x="13" y="28" width="1" height="1" fill="#000000"/>
|
||||
<rect x="15" y="28" width="1" height="1" fill="#000000"/>
|
||||
<rect x="20" y="28" width="1" height="1" fill="#000000"/>
|
||||
<rect x="23" y="28" width="1" height="1" fill="#000000"/>
|
||||
<rect x="24" y="28" width="1" height="1" fill="#000000"/>
|
||||
<rect x="25" y="28" width="1" height="1" fill="#000000"/>
|
||||
<rect x="26" y="28" width="1" height="1" fill="#000000"/>
|
||||
<rect x="27" y="28" width="1" height="1" fill="#000000"/>
|
||||
<rect x="28" y="28" width="1" height="1" fill="#000000"/>
|
||||
<rect x="29" y="28" width="1" height="1" fill="#000000"/>
|
||||
<rect x="31" y="28" width="1" height="1" fill="#000000"/>
|
||||
<rect x="0" y="29" width="1" height="1" fill="#000000"/>
|
||||
<rect x="2" y="29" width="1" height="1" fill="#000000"/>
|
||||
<rect x="3" y="29" width="1" height="1" fill="#000000"/>
|
||||
<rect x="4" y="29" width="1" height="1" fill="#000000"/>
|
||||
<rect x="6" y="29" width="1" height="1" fill="#000000"/>
|
||||
<rect x="8" y="29" width="1" height="1" fill="#000000"/>
|
||||
<rect x="10" y="29" width="1" height="1" fill="#000000"/>
|
||||
<rect x="13" y="29" width="1" height="1" fill="#000000"/>
|
||||
<rect x="15" y="29" width="1" height="1" fill="#000000"/>
|
||||
<rect x="18" y="29" width="1" height="1" fill="#000000"/>
|
||||
<rect x="19" y="29" width="1" height="1" fill="#000000"/>
|
||||
<rect x="20" y="29" width="1" height="1" fill="#000000"/>
|
||||
<rect x="21" y="29" width="1" height="1" fill="#000000"/>
|
||||
<rect x="22" y="29" width="1" height="1" fill="#000000"/>
|
||||
<rect x="27" y="29" width="1" height="1" fill="#000000"/>
|
||||
<rect x="0" y="30" width="1" height="1" fill="#000000"/>
|
||||
<rect x="2" y="30" width="1" height="1" fill="#000000"/>
|
||||
<rect x="3" y="30" width="1" height="1" fill="#000000"/>
|
||||
<rect x="4" y="30" width="1" height="1" fill="#000000"/>
|
||||
<rect x="6" y="30" width="1" height="1" fill="#000000"/>
|
||||
<rect x="11" y="30" width="1" height="1" fill="#000000"/>
|
||||
<rect x="12" y="30" width="1" height="1" fill="#000000"/>
|
||||
<rect x="15" y="30" width="1" height="1" fill="#000000"/>
|
||||
<rect x="16" y="30" width="1" height="1" fill="#000000"/>
|
||||
<rect x="17" y="30" width="1" height="1" fill="#000000"/>
|
||||
<rect x="18" y="30" width="1" height="1" fill="#000000"/>
|
||||
<rect x="19" y="30" width="1" height="1" fill="#000000"/>
|
||||
<rect x="20" y="30" width="1" height="1" fill="#000000"/>
|
||||
<rect x="23" y="30" width="1" height="1" fill="#000000"/>
|
||||
<rect x="24" y="30" width="1" height="1" fill="#000000"/>
|
||||
<rect x="25" y="30" width="1" height="1" fill="#000000"/>
|
||||
<rect x="28" y="30" width="1" height="1" fill="#000000"/>
|
||||
<rect x="29" y="30" width="1" height="1" fill="#000000"/>
|
||||
<rect x="30" y="30" width="1" height="1" fill="#000000"/>
|
||||
<rect x="32" y="30" width="1" height="1" fill="#000000"/>
|
||||
<rect x="0" y="31" width="1" height="1" fill="#000000"/>
|
||||
<rect x="6" y="31" width="1" height="1" fill="#000000"/>
|
||||
<rect x="8" y="31" width="1" height="1" fill="#000000"/>
|
||||
<rect x="10" y="31" width="1" height="1" fill="#000000"/>
|
||||
<rect x="11" y="31" width="1" height="1" fill="#000000"/>
|
||||
<rect x="12" y="31" width="1" height="1" fill="#000000"/>
|
||||
<rect x="13" y="31" width="1" height="1" fill="#000000"/>
|
||||
<rect x="15" y="31" width="1" height="1" fill="#000000"/>
|
||||
<rect x="16" y="31" width="1" height="1" fill="#000000"/>
|
||||
<rect x="19" y="31" width="1" height="1" fill="#000000"/>
|
||||
<rect x="22" y="31" width="1" height="1" fill="#000000"/>
|
||||
<rect x="27" y="31" width="1" height="1" fill="#000000"/>
|
||||
<rect x="29" y="31" width="1" height="1" fill="#000000"/>
|
||||
<rect x="30" y="31" width="1" height="1" fill="#000000"/>
|
||||
<rect x="31" y="31" width="1" height="1" fill="#000000"/>
|
||||
<rect x="32" y="31" width="1" height="1" fill="#000000"/>
|
||||
<rect x="0" y="32" width="1" height="1" fill="#000000"/>
|
||||
<rect x="1" y="32" width="1" height="1" fill="#000000"/>
|
||||
<rect x="2" y="32" width="1" height="1" fill="#000000"/>
|
||||
<rect x="3" y="32" width="1" height="1" fill="#000000"/>
|
||||
<rect x="4" y="32" width="1" height="1" fill="#000000"/>
|
||||
<rect x="5" y="32" width="1" height="1" fill="#000000"/>
|
||||
<rect x="6" y="32" width="1" height="1" fill="#000000"/>
|
||||
<rect x="8" y="32" width="1" height="1" fill="#000000"/>
|
||||
<rect x="9" y="32" width="1" height="1" fill="#000000"/>
|
||||
<rect x="14" y="32" width="1" height="1" fill="#000000"/>
|
||||
<rect x="16" y="32" width="1" height="1" fill="#000000"/>
|
||||
<rect x="17" y="32" width="1" height="1" fill="#000000"/>
|
||||
<rect x="19" y="32" width="1" height="1" fill="#000000"/>
|
||||
<rect x="23" y="32" width="1" height="1" fill="#000000"/>
|
||||
<rect x="24" y="32" width="1" height="1" fill="#000000"/>
|
||||
<rect x="25" y="32" width="1" height="1" fill="#000000"/>
|
||||
<rect x="26" y="32" width="1" height="1" fill="#000000"/>
|
||||
<rect x="27" y="32" width="1" height="1" fill="#000000"/>
|
||||
<rect x="28" y="32" width="1" height="1" fill="#000000"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 31 KiB |
56
share/doorlockd/templates/display.html
Normal file
|
@ -0,0 +1,56 @@
|
|||
{% extends "layout.html" %}
|
||||
|
||||
{% block head %}
|
||||
{{ super() }}
|
||||
<meta http-equiv="refresh" content="600">
|
||||
<meta name="google" content="notranslate">
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
{{ super() }}
|
||||
<script src="/static/js/jquery.min.js"></script>
|
||||
<script src="/static/js/bootstrap.min.js"></script>
|
||||
<script type="text/javascript" charset="utf-8">
|
||||
$(document).ready(function() {
|
||||
var source = new EventSource('{{ url_for('push')}}');
|
||||
source.onmessage = function(event){
|
||||
console.log("event:" + event.data);
|
||||
try{
|
||||
var data = JSON.parse(event.data);
|
||||
$('#message').html(data.message);
|
||||
led = $('#led');
|
||||
led.attr('src', data.img);
|
||||
} catch(e){console.log(e);}}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1 class="text-center display-4">{{ welcome }}</h1>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="text-center">
|
||||
<div class="h2">{{ room }}</div>
|
||||
<img id="led" src="">
|
||||
<div class="h2" id="message"></div>
|
||||
</div>
|
||||
<hr>
|
||||
<div>
|
||||
<h4 class="text">Kontakt</h4>
|
||||
0941 / 37801 - 740
|
||||
<hr>
|
||||
Keine App? Kein Problem! WiFi anschalten und https://lock.binary.kitchen besuchen.
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<a class="twitter-grid" data-partner="tweetdeck" href="https://twitter.com/binary_kitchen/timelines/1038927503446949888?ref_src=twsrc%5Etfw">gallery</a>
|
||||
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
|
||||
</div>
|
||||
<div class="col text-center">
|
||||
<img src="static/token.svg" height="300">
|
||||
<img width=175 src='https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png'/>
|
||||
<img width=175 src='https://fdroid.gitlab.io/artwork/badge/get-it-on.png' />
|
||||
</div>
|
||||
</div>
|
||||
{{ super() }}
|
||||
{% endblock %}
|
39
share/doorlockd/templates/index.html
Normal file
|
@ -0,0 +1,39 @@
|
|||
{% extends "layout.html" %}
|
||||
|
||||
{%- block metas %}
|
||||
{{super()}}
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
{%- endblock metas %}
|
||||
|
||||
{% block content %}
|
||||
<h1 class="text-center">{{ banner }}</h1>
|
||||
<div class="row">
|
||||
<div class="col-md-3 text-center">
|
||||
<img src="{{ led }}">
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<form action="" method="post" class="form" role="form">
|
||||
{{ authentication_form.csrf_token }}
|
||||
<div class="form-group ">
|
||||
<label class="control-label" for="username">Username</label>
|
||||
<input class="form-control" id="username" name="username" type="text" value="">
|
||||
</div>
|
||||
<div class="form-group required">
|
||||
<label class="control-label" for="password">Password</label>
|
||||
<input class="form-control" id="password" name="password" required type="password" value="">
|
||||
</div>
|
||||
<input class="btn btn-success btn-lg btn-block" id="open" name="open" type="submit" value="Open">
|
||||
<input class="btn btn-warning btn-lg btn-block" id="present" name="present" type="submit" value="Present">
|
||||
<input class="btn btn-danger btn-lg btn-block" id="close" name="close" type="submit" value="Close">
|
||||
</form>
|
||||
{% if response %}
|
||||
<hr/>
|
||||
<h1>{{ response }}</h1>
|
||||
{% endif %}
|
||||
<hr/>
|
||||
Die Kitchen ist: {{ state_text }}
|
||||
</div>
|
||||
</div>
|
||||
{{ super() }}
|
||||
{% endblock %}
|
17
share/doorlockd/templates/layout.html
Normal file
|
@ -0,0 +1,17 @@
|
|||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
|
||||
"http://www.w3.org/TR/html4/loose.dtd">
|
||||
<html>
|
||||
<head>
|
||||
{% block head %}{%- endblock head %}
|
||||
<title>{{ title }}</title>
|
||||
{% block metas %}{%- endblock metas %}
|
||||
<link href="/static/css/bootstrap.min.css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
{% block content %}{%- endblock content %}
|
||||
<div class="footer-copyright text-center py-3 small">
|
||||
Ralf Ramsauer © 2018 Binary Kitchen e.V.
|
||||
</div>
|
||||
{% block scripts %}{%- endblock scripts %}
|
||||
</body>
|
||||
</html>
|
|
@ -1,13 +1,11 @@
|
|||
[Unit]
|
||||
Description=Binary Kitchen doorlockd service
|
||||
After=network.target
|
||||
After=network.target network-online.target
|
||||
|
||||
[Service]
|
||||
User=root
|
||||
Group=root
|
||||
EnvironmentFile=-/etc/sysconfig/doorlockd
|
||||
ExecStart=/usr/local/sbin/doorlockd
|
||||
ExecStart=doorlockd
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
13
systemd/doorstate.service
Normal file
|
@ -0,0 +1,13 @@
|
|||
[Unit]
|
||||
Description=Binary Kitchen doorstate service
|
||||
After=network.target network-online.target
|
||||
|
||||
[Service]
|
||||
User=root
|
||||
Group=root
|
||||
ExecStart=doorstate
|
||||
Restart=on-failure
|
||||
RestartSec=5s
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
|
@ -1,171 +0,0 @@
|
|||
<?php
|
||||
function tellLock($pCommand, $pUser, $pPass, $pToken, $pIp){
|
||||
|
||||
$json = '{
|
||||
"user":' . json_encode( $pUser ) . ',
|
||||
"password":' . json_encode( $pPass ) . ',
|
||||
"command":' . json_encode( $pCommand ) . ',
|
||||
"token":' . json_encode( $pToken ) . ',
|
||||
"ip":' . json_encode( $pIp ) . '
|
||||
}'."\n";
|
||||
|
||||
$address = "127.0.0.1";
|
||||
$port = "5555";
|
||||
|
||||
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
|
||||
if ($socket === false) {
|
||||
echo "socket_create() failed: " . socket_strerror(socket_last_error()) . "\n";
|
||||
}
|
||||
|
||||
$result = socket_connect($socket, $address, $port);
|
||||
if ($result === false) {
|
||||
echo "socket_connect() failed: ($result) " . socket_strerror(socket_last_error($socket)) . "\n";
|
||||
}
|
||||
|
||||
socket_write($socket, $json, strlen($json));
|
||||
|
||||
$result = socket_read($socket, 1024);
|
||||
|
||||
socket_close($socket);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
$showLoginForm = false;
|
||||
$showSuccess = false;
|
||||
$showFailure = false;
|
||||
$showTokenForm = false;
|
||||
$isApi = false;
|
||||
|
||||
$pIp = $_SERVER[ 'REMOTE_ADDR' ];
|
||||
|
||||
if( $_SERVER[ 'REQUEST_METHOD' ] == "POST" ) {
|
||||
|
||||
if (array_key_exists("user", $_POST)
|
||||
&& array_key_exists('pass', $_POST)
|
||||
&& array_key_exists('token', $_POST)
|
||||
&& array_key_exists('command', $_POST)
|
||||
&& array_key_exists('api', $_POST))
|
||||
{
|
||||
$pUser = $_POST[ 'user' ];
|
||||
$pPass = $_POST[ 'pass' ];
|
||||
$pToken = $_POST[ 'token' ];
|
||||
$pCommand = $_POST[ 'command' ];
|
||||
$pApi = $_POST[ 'api' ];
|
||||
|
||||
if ($pApi == "true")
|
||||
{
|
||||
$isApi = true;
|
||||
}
|
||||
|
||||
$jsonResponse = json_decode(tellLock($pCommand, $pUser, $pPass, $pToken, $pIp), true);
|
||||
if ($jsonResponse == null || !isset($jsonResponse['message']) || !isset($jsonResponse['code'])) {
|
||||
$showFailure = true;
|
||||
$failureMsg = 'Error parsing JSON response';
|
||||
} else {
|
||||
$failureMsg = $jsonResponse['message'];
|
||||
$code = $jsonResponse['code'];
|
||||
$showSuccess = ($code == 0);
|
||||
$showFailure = !$showSuccess;
|
||||
}
|
||||
} else {
|
||||
$failureMsg = 'Invalid Request';
|
||||
$showFailure = true;
|
||||
}
|
||||
|
||||
} else {
|
||||
// This is done by apache mod_rewrite
|
||||
$pToken = $_GET['token'];
|
||||
$lToken = $str= ltrim ($pToken, '/');
|
||||
|
||||
if(strlen($lToken) == 0) {
|
||||
$showLoginForm = true;
|
||||
$showTokenForm = true;
|
||||
} else {
|
||||
$showLoginForm = true;
|
||||
}
|
||||
}
|
||||
if ($isApi == false) {
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Login</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<style>
|
||||
* {
|
||||
font: normal 30px Arial,sans-serif;
|
||||
}
|
||||
body {
|
||||
background-color: #037;
|
||||
color: white;
|
||||
background-image: url('logo.svg' );
|
||||
background-repeat: repeat;
|
||||
background-size: 300%;
|
||||
background-position: -200px -100px;
|
||||
}
|
||||
form {
|
||||
position: relative;
|
||||
display: block;
|
||||
width: auto;
|
||||
text-align: center;
|
||||
}
|
||||
input {
|
||||
position: relative;
|
||||
display: block;
|
||||
width: auto;
|
||||
width: 100%;
|
||||
}
|
||||
button {
|
||||
width: 100%;
|
||||
margin-top: 40px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<?php if ($showLoginForm): ?>
|
||||
|
||||
<form name="login" method="post" action="/">
|
||||
<?php if ($showTokenForm): ?>
|
||||
<label for="token">Token</label>
|
||||
<input type="text" name="token" value="">
|
||||
<?php else: ?>
|
||||
<input type="hidden" name="token" value="<?php echo $lToken;?>">
|
||||
<?php endif; ?>
|
||||
|
||||
<input type="hidden" name="api" value="false">
|
||||
|
||||
<label for="user">User</label>
|
||||
<input id="user" type="text" name="user">
|
||||
|
||||
<label for="pass">Pass</label>
|
||||
<input id="pass" type="password" name="pass">
|
||||
|
||||
<button name="command" value="unlock">Open</button>
|
||||
<hr/>
|
||||
<button name="command" value="lock">Lock</button>
|
||||
</form>
|
||||
|
||||
<?php elseif( $showSuccess ): ?>
|
||||
|
||||
<h1>Welcome Cpt. Cook</h1>
|
||||
|
||||
<?php elseif( $showFailure ): ?>
|
||||
|
||||
<h1>Something went wrong: <?php echo $failureMsg; ?></h1>
|
||||
|
||||
<?php endif; ?>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
<?php
|
||||
} else {
|
||||
echo $code;
|
||||
}
|
||||
?>
|