From d610db0d1f360e8b79d33eac5dabaf93649323ce Mon Sep 17 00:00:00 2001 From: +++ Date: Sun, 22 Nov 2020 22:35:27 +0100 Subject: [PATCH 01/33] fix bug in api file replace strings with boolean and integer --- source/server/apistatusd.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/source/server/apistatusd.py b/source/server/apistatusd.py index 2f97f1b..04ad48c 100755 --- a/source/server/apistatusd.py +++ b/source/server/apistatusd.py @@ -155,11 +155,11 @@ def set_values(raw_data): param 1: byte return: tuple ''' - status = "true" if raw_data == b'\x01' else "false" - timestamp = str(time()).split('.')[0] + status = True if raw_data == b'\x01' else False + timestamp = int(str(time()).split('.')[0]) logging.debug('Set values for timestamp: {} and status: {}'.format( - timestamp, status)) + str(timestamp), str(status))) return (status, timestamp) @@ -235,7 +235,7 @@ def main(): logging.debug('Socket created') mySocket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) keep = mySocket.getsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE) - logging.debug('Socket keepalive: {}'format(keep)) + logging.debug('Socket keepalive: {}'.format(keep)) try: mySocket.bind((config['server']['host'], int(config['server']['port']))) mySocket.listen(5) -- 2.39.5 From c47afa29428e085b3ed2613aa3819b40ea4a3927 Mon Sep 17 00:00:00 2001 From: +++ Date: Sun, 22 Nov 2020 22:42:24 +0100 Subject: [PATCH 02/33] socket shutdown in exception --- source/server/apistatusd.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/source/server/apistatusd.py b/source/server/apistatusd.py index 04ad48c..eff1010 100755 --- a/source/server/apistatusd.py +++ b/source/server/apistatusd.py @@ -296,7 +296,10 @@ def main(): finally: if mySocket: logging.info('Shutdown socket') - mySocket.shutdown(socket.SHUT_RDWR) + try: + mySocket.shutdown(socket.SHUT_RDWR) + except Exception as e: + logging.error(f'Error while shutdown socket: {e}') return 0 -- 2.39.5 From 641964b17fde1d62bd6afb08a148ca337eb9d771 Mon Sep 17 00:00:00 2001 From: +++ Date: Sun, 22 Nov 2020 23:01:32 +0100 Subject: [PATCH 03/33] =?UTF-8?q?statusd.service=20in=20apistatusd.service?= =?UTF-8?q?=20umbenannt,=20angepa=C3=9Ft?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/server/{statusd.service => apistatusd.service} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename source/server/{statusd.service => apistatusd.service} (86%) diff --git a/source/server/statusd.service b/source/server/apistatusd.service similarity index 86% rename from source/server/statusd.service rename to source/server/apistatusd.service index f91bab0..a76f4dc 100644 --- a/source/server/statusd.service +++ b/source/server/apistatusd.service @@ -5,7 +5,7 @@ After=systemd-network.service network.target [Service] Type=simple WorkingDirectory=/opt/doorstatus/ -ExecStart=/opt/doorstatus/statusd.py +ExecStart=/opt/doorstatus/apistatusd.py SyslogIdentifier=doorstatus User=doorstatus Group=doorstatus -- 2.39.5 From 868c0bd28554437231549bd075aacd7abff8d5d8 Mon Sep 17 00:00:00 2001 From: Philipp Matthias Schaefer Date: Sat, 21 Nov 2020 21:42:24 +0100 Subject: [PATCH 04/33] Change Arduino's description from broadcast to UDP req/resp --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index c9c2da4..e1d9d36 100644 --- a/README.md +++ b/README.md @@ -8,10 +8,10 @@ KrautStatus consists of three components: an Arduino-sensor-combination at the door, a Python script on a local server, and an API file served on by web server. -The Arduino broadcasts the current state of the door lock to our LAN. The state -is determined by a reed sensor in the door lock. In addition, it controls an -RGB LED matrix shield that provides a visual indication of the door locks's -state in the room. +The Arduino provides the current state of the door lock upon request via UDP. +The state is determined by a reed sensor in the door lock. In addition, it +controls an RGB LED matrix shield that provides a visual indication of the door +locks's state in the room. The python script polls the broadcast state and pushes changes to Twitter, Mastodon and our API file. -- 2.39.5 From d959e044fde94ba031c4a01f3f3806633f3c1132 Mon Sep 17 00:00:00 2001 From: Philipp Matthias Schaefer Date: Sat, 21 Nov 2020 21:43:07 +0100 Subject: [PATCH 05/33] Mention Space API --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e1d9d36..ec54111 100644 --- a/README.md +++ b/README.md @@ -16,8 +16,8 @@ locks's state in the room. The python script polls the broadcast state and pushes changes to Twitter, Mastodon and our API file. -The API file is just that, a JSON file. It contains, among other things, the -current state of the door lock. +The API file follows the [Space API](http://spaceapi.net) specification, which +includes a field indicating whether the space is open or closed. ## Arduino -- 2.39.5 From 28de70e73257779a0b7161dd5de6fafabe2edf62 Mon Sep 17 00:00:00 2001 From: Philipp Matthias Schaefer Date: Thu, 26 Nov 2020 09:20:24 +0100 Subject: [PATCH 06/33] Remove unused variable --- source/arduino/door_status.ino | 2 -- 1 file changed, 2 deletions(-) diff --git a/source/arduino/door_status.ino b/source/arduino/door_status.ino index fd81a16..f5acb10 100644 --- a/source/arduino/door_status.ino +++ b/source/arduino/door_status.ino @@ -28,8 +28,6 @@ with the Clean CommonMark library. If not, see . uint8_t OPEN = 1; uint8_t CLOSED = 0; -int wifi_status = WL_IDLE_STATUS; - WiFiUDP Udp; void setup() { -- 2.39.5 From 001c7cd5687d1796a05a5c103453115b2300c878 Mon Sep 17 00:00:00 2001 From: Philipp Matthias Schaefer Date: Fri, 11 Dec 2020 14:18:46 +0100 Subject: [PATCH 07/33] Implement and document Arduino software --- .gitignore | 2 + README.md | 116 +++++++++++++++++-- documentation/Makefile | 33 ++++++ documentation/arduino.png | Bin 0 -> 16474 bytes documentation/arduino.tex | 55 +++++++++ scripts/test_udp_api.py | 62 ++++++++++ source/arduino/Makefile | 33 ++++++ source/arduino/arduino.ino | 70 +++++++++++ source/arduino/config.h | 11 +- source/arduino/matrix.cpp | 87 ++++++++++++++ source/arduino/matrix.h | 33 ++++++ source/arduino/sensor.cpp | 35 ++++++ source/arduino/sensor.h | 29 +++++ source/arduino/serial.cpp | 26 +++++ source/arduino/serial.h | 22 ++++ source/arduino/{door_status.ino => wifi.cpp} | 77 +++--------- source/arduino/wifi.h | 27 +++++ 17 files changed, 648 insertions(+), 70 deletions(-) create mode 100644 .gitignore create mode 100644 documentation/Makefile create mode 100644 documentation/arduino.png create mode 100644 documentation/arduino.tex create mode 100755 scripts/test_udp_api.py create mode 100644 source/arduino/Makefile create mode 100644 source/arduino/arduino.ino create mode 100644 source/arduino/matrix.cpp create mode 100644 source/arduino/matrix.h create mode 100644 source/arduino/sensor.cpp create mode 100644 source/arduino/sensor.h create mode 100644 source/arduino/serial.cpp create mode 100644 source/arduino/serial.h rename source/arduino/{door_status.ino => wifi.cpp} (52%) create mode 100644 source/arduino/wifi.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7274ad1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/documentation/build/ +/source/arduino/build/ diff --git a/README.md b/README.md index ec54111..863398e 100644 --- a/README.md +++ b/README.md @@ -21,24 +21,126 @@ includes a field indicating whether the space is open or closed. ## Arduino -## Python Script +### Arduino: Hardware + +We measure the door's lock state using a reed switch. The switch is installed at +the bottom of hole for the deadbolt and gets triggered by a magnet glued to the +tip of the deadbolt. + +The reed switch is connected to pin 0 and ground on an Arduino MKR1000. The +software sets up pin 0 (by default) with a pull up resistor. This prevents +currents induced into the long wire between switch an Arduino to trigger the pin +state. + +A second component connected to the MKR1000 is an Arduino MKR RGB Shield that +provides a 7x12 RGB LED matrix. It is used to give visual feedback on the +software's current state. + +While trying to connect to the configured WLAN, a vertical yellow line +oscillates between both sides of the display. When connecting to the WLAN fails, +a red cross is displayed. When the room is open all LEDs are green, and when it +is closed all LEDs are turned off (nobody should be there to see it anyway). + +The following images is a schematic showing the connections between the three +components mentioned. + +![a schematic of Arduino MKR1000 and all components physically connected to it, as described in the text](documentation/arduino.png) + +The Arduino is powered by a USB charger. + +If you need to change the schematic, adapt `documentation/arduino.tex` and run +`make`. This requires the following software and TeX libraries to be installed +on your systems: + +- pdflatex +- convert from ImageMagick +- tikz +- circuitikz + +### Arduino: Setup and Configuration + +The sketch for our Arduino is located in `sources/arduino`. Before we flash the +Arduino with the sketch, we should adapt `config.h`. We can use either the +[Arduino IDE](https://www.arduino.cc/en/software) or the provided Makefile to +verify or flash the sketch. + +For the Makefile to work, +[Arduino CLI](https://arduino.github.io/arduino-cli/latest/) has to be +installed. Use `make` for verification and `make install` to flash the board, +which needs to be connected to our PC via USB cable. + +Verifying and flashing the sketch requires the installation of several +libraries. + +First we need the core for the family of boards Arduino MKR1000 +belongs to: Arduino SAMD Boards (or `arduino:samd`). Installation instructions +for [Arduino IDE](https://www.arduino.cc/en/guide/cores) and +[Arduino CLI](https://arduino.github.io/arduino-cli/latest/commands/arduino-cli_core_install/) +can be found in the respective documentation. + +Then we need to make sure the following libraries are installed: + +- ArduinoGraphics +- Arduino_MKRRGB +- uTimerLib +- WiFi101 + +Installed instructions for +[Arduino IDE](https://www.arduino.cc/en/guide/libraries) and +[Arduino CLI](https://arduino.github.io/arduino-cli/latest/commands/arduino-cli_lib_install/) +can be found in the respective documentation. + +We assign a fixed IP address in the space's WiFi network to the Arduino's MAC +address. The MAC address gets printed to `Serial` on each start up. Once +connected to the configured WLAN, the IP address is also printed to `Serial`. +(TODO: MENTION WHERE TO FIND DOCUMENTATION FOR OUR DHCPD.) + +### Arduino: UDP API + +Once the Arduino has started successfully, it listens on the configured port +(see [sources/arduino/config.h](sources/arduino/config.h) for the default port) +for UDP packets and answers each incoming packet with the state of the door. The +state is encoded as a one byte integer, where `0` stands for closed and `1` +stands for open. + +In `scripts` is the Python 3 script `test_udp_api.py`, which we use to test the +UDP API. It takes an IP address and optionally a port, queries the Arduino, and +prints out the result. + +## In-Space Service + +## Internet Service -## API ## License The authors of the Arduino code license the code under the AGPL 3.0 (see -`LICENSE.AGPL`). The respective authors are listed in the in the preambles of -each source file. +`LICENSE.AGPL`). The respective authors are listed in the preambles of each +source file. ## Dependencies +The creation of the schematic image depends on the following libraries: + +*TikZ* published by the copyright holders under the GPL 2, the Free +Documentation License, and the LaTeX Project Public License 1.3c + +*Circuitikz* published by the copyright holders under the GPL 2 and the LaTeX +Project Public License + The Arduino code depends on the following libraries: -_Arduino Core_ published by the copyright holders under the LGPL 2.1 or later. +*Arduino SAMD Boards* published by the copyright holders under the LGPL 2.1 or later. -_SPI Master Library for Arduino Zero_ published by the copyright holders under +*SPI Master Library for Arduino Zero* published by the copyright holders under the LGPL 2.1 or later. -_Library for Arduino WiFi Shield_ published by the copyright holders under the +*ArduinoGraphics* published by the copyright holders under the LGPL 2.1 or +later. + +*Arduino_MKRRGB* published by the copyright holders under the LGPL 2.1 or later. + +*uTimerLib* published by the copyright holders under the LGPL 3. + +*WiFi101* published by the copyright holders under the LGPL 2.1 or later. diff --git a/documentation/Makefile b/documentation/Makefile new file mode 100644 index 0000000..f4a2f05 --- /dev/null +++ b/documentation/Makefile @@ -0,0 +1,33 @@ +# Copyright (c): +# +# Philipp Matthias Schäfer (philipp.matthias.schaefer@posteo.de), 2020 +# +# This file is part of the KrautStatus' Arduino code. +# +# The Clean CommonMark library is free software: you can redistribute it and/or +# modify it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# The Clean CommonMark library 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 Affero +# General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License along +# with the Clean CommonMark library. If not, see +# . + +.PHONY: all clean + +all: arduino.png + +clean: + rm --recursive build + +arduino.png: arduino.tex build + pdflatex -output-directory build arduino + convert -density 300 build/arduino.pdf -trim -colorspace RGB arduino.png + +build: + mkdir --parents build diff --git a/documentation/arduino.png b/documentation/arduino.png new file mode 100644 index 0000000000000000000000000000000000000000..efa3f652e2a6bc9312afa60462220c056e0e944b GIT binary patch literal 16474 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 literal 0 HcmV?d00001 diff --git a/documentation/arduino.tex b/documentation/arduino.tex new file mode 100644 index 0000000..816564a --- /dev/null +++ b/documentation/arduino.tex @@ -0,0 +1,55 @@ +\documentclass[border=1mm]{standalone} + +\usepackage{tikz} +\usetikzlibrary{positioning} +\usepackage{circuitikz} + +\begin{document} + +\begin{circuitikz}[ + chip/.style = { + dipchip, + hide numbers, + no topmark, + external pins width=0, + num pins=14, + }] + \ctikzset{multipoles/dipchip/width=1.6} + + % Arduino MKR1000 + \node [chip] (MKR1000) {}; + % align=center enables line breaks in node text + \node [align=center, rotate=90] at (MKR1000.center){Arduino\\MKR1000}; + + \draw (MKR1000.n) -- ++(0,1) node[vcc]{}; + \draw (MKR1000.s) -- ++(0,-1) node[ground] {}; + + \node [right, font=\tiny] at (MKR1000.bpin 4) {0}; + + \node [left, font=\tiny] at (MKR1000.bpin 12) {VIN}; + \node [left, font=\tiny] at (MKR1000.bpin 11) {A3}; + \node [left, font=\tiny] at (MKR1000.bpin 10) {A4}; + + % Arduino MKR RGB Shield + \node[chip, right=of MKR1000] (RGB) {}; + % align=center enables line breaks in node text + \node [align=center, rotate=90] at (RGB.center){Arduino\\MKR RGB Shield}; + + \draw (RGB.s) -- ++(0, -1) node[ground]{}; + + \node [right, font=\tiny] at (RGB.bpin 3) {VIN}; + \node [right, font=\tiny] at (RGB.bpin 4) {A3}; + \node [right, font=\tiny] at (RGB.bpin 5) {A4}; + + \draw (MKR1000.bpin 12) -- (RGB.bpin 3); + \draw (MKR1000.bpin 11) -- (RGB.bpin 4); + \draw (MKR1000.bpin 10) -- (RGB.bpin 5); + + % Reed switch + \draw (MKR1000.bpin 4) -- ++(-1, 0) to [normal open switch, name=R] ++(0, -2.96) node[ground]{}; + + \node [rotate=90, above] at (R) {Reed Switch}; +\end{circuitikz} + + +\end{document} diff --git a/scripts/test_udp_api.py b/scripts/test_udp_api.py new file mode 100755 index 0000000..ada9d0c --- /dev/null +++ b/scripts/test_udp_api.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +""" +Test KrautStatus's UDP API for a given IP and (optionally) port. +""" + +import argparse +import ipaddress +import socket + +def port(string): + "Convert string to an unsigend integer that is a valid port number" + port = int(string) + + if port < 0 or port > 65535: + raise ValueError() + + return port + +def main(): + parser = argparse.ArgumentParser(__doc__) + + parser.add_argument("ip", + metavar="IP", + type=ipaddress.ip_address, + help="IP address of the KrautStatus Arduino") + + parser.add_argument("port", + nargs="?", + metavar="PORT", + default=12345, + type=port, + help="port that the KrautStatus Arduino listens to") + + args = parser.parse_args() + + + receiver = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + receiver.bind(('0.0.0.0', args.port)) + print(f'Listening on 0.0.0.0:{args.port}.') + + sender = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + # We do not use \0 or \1 here, so that we do not trigger ourselves when + # testing the script locally (127.0.0.1) + sender.sendto(b'\2', (str(args.ip), args.port)) + print(f'Sent null byte to {args.ip}:{args.port}.') + + while True: + status, address = receiver.recvfrom(1) + + if not address[0] == str(args.ip): + continue + + if status[0] == 0: + print('Responded: door closed') + return + + if status[0] == 1: + print('Resondend: door open') + return + +if __name__ == "__main__": + main() diff --git a/source/arduino/Makefile b/source/arduino/Makefile new file mode 100644 index 0000000..0f54ea4 --- /dev/null +++ b/source/arduino/Makefile @@ -0,0 +1,33 @@ +# Copyright (c): +# +# Philipp Matthias Schäfer (philipp.matthias.schaefer@posteo.de), 2020 +# +# This file is part of the KrautStatus' Arduino code. +# +# The Clean CommonMark library is free software: you can redistribute it and/or +# modify it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# The Clean CommonMark library 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 Affero +# General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License along +# with the Clean CommonMark library. If not, see +# . + +.PHONY: all clean install + +all: + arduino-cli compile --fqbn arduino:samd:mkr1000 + +clean: + rm --recursive build + +install: + PORT=$(arduino-cli board list | \ + grep arduino:samd:mkr1000 | \ + sed -e 's/ .*//g'); \ + arduino-cli upload --fqbn arduino:samd:mkr1000 --port $$PORT diff --git a/source/arduino/arduino.ino b/source/arduino/arduino.ino new file mode 100644 index 0000000..f6a3477 --- /dev/null +++ b/source/arduino/arduino.ino @@ -0,0 +1,70 @@ +/* +Copyright (c): + +Philipp Matthias Schäfer (philipp.matthias.schaefer@posteo.de), 2020 + +This file is part of the KrautStatus' Arduino code. + +The Clean CommonMark library is free software: you can redistribute it and/or +modify it under the terms of the GNU Affero General Public License as published +by the Free Software Foundation, either version 3 of the License, or (at your +option) any later version. + +The Clean CommonMark library 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 Affero General Public License +for more details. + +You should have received a copy of the GNU Affero General Public License along +with the Clean CommonMark library. If not, see . +*/ + +#include +#include + +#include "config.h" +#include "matrix.h" +#include "sensor.h" +#include "serial.h" +#include "wifi.h" + +const unsigned long int MATRIX_UPDATE_FREQUENCY = 1; + +void setup() { + serial_setup(); + matrix_setup(); + + boolean wifi_result = matrix_show_scan_and_run(wifi_setup); + if (!wifi_result) { + matrix_show_failure(); + while(true); + } + + sensor_setup(); + + start_matrix_update(); +} + +void loop() { + int packetSize = Udp.parsePacket(); + if (packetSize) { + Udp.beginPacket(Udp.remoteIP(), Udp.remotePort()); + Udp.write(uint8_t(sensor_get_door_state())); + Udp.endPacket(); + } +} + +void start_matrix_update() { + TimerLib.setInterval_us(matrix_update, 1000000 / MATRIX_UPDATE_FREQUENCY); +} + +void matrix_update() { + switch (sensor_get_door_state()) { + OPEN: + matrix_fill(GREEN); + break; + CLOSED: + matrix_fill(BLACK); + break; + } +} diff --git a/source/arduino/config.h b/source/arduino/config.h index 49db287..2cb04dc 100644 --- a/source/arduino/config.h +++ b/source/arduino/config.h @@ -19,14 +19,15 @@ You should have received a copy of the GNU Affero General Public License along with the Clean CommonMark library. If not, see . */ +#include + // SSID and password of the WiFi network to which we broadcast the door lock's // status. -char SSID[] = ""; -char PASSWORD[] = ""; - +const char SSID[] = ""; +const char PASSWORD[] = ""; // Port on which to listen for status requests -unsigned int PORT = 12345; +const unsigned int SERVER_PORT = 12345; // Pin to which the reed switch is connected -uint8_t REED_PIN = 0; +const uint8_t SENSOR_PIN = 0; diff --git a/source/arduino/matrix.cpp b/source/arduino/matrix.cpp new file mode 100644 index 0000000..38f9ace --- /dev/null +++ b/source/arduino/matrix.cpp @@ -0,0 +1,87 @@ +/* +Copyright (c): + +Philipp Matthias Schäfer (philipp.matthias.schaefer@posteo.de), 2020 + +This file is part of the KrautStatus' Arduino code. + +The Clean CommonMark library is free software: you can redistribute it and/or +modify it under the terms of the GNU Affero General Public License as published +by the Free Software Foundation, either version 3 of the License, or (at your +option) any later version. + +The Clean CommonMark library 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 Affero General Public License +for more details. + +You should have received a copy of the GNU Affero General Public License along +with the Clean CommonMark library. If not, see . +*/ + +#include +#include +#include + +#include "matrix.h" + + +const uint32_t RED = COLOR(255, 0, 0); +const uint32_t YELLOW = COLOR(255, 255, 0); + +const unsigned long int SCAN_UPDATE_FREQUENCY = 25; + +void matrix_setup() { + MATRIX.begin(); +} + +void matrix_show_failure() { + MATRIX.stroke(RED); + MATRIX.beginDraw(); + MATRIX.clear(); + MATRIX.line(0, 0, MATRIX.width() - 1, MATRIX.height() - 1); + MATRIX.line(0, MATRIX.height() - 1, MATRIX.width() - 1, 0); + MATRIX.endDraw(); +} + +uint8_t scan_timer = 0; + +void matrix_paint_scan() { + // We paint a vertical line that oscillates between the left and right border + // of the matrix. Going back and forth one pixel at a time gives us as period + // of + // 2 * width - 2 + // because we do not want to remain at the borders for one tick. + scan_timer += 1; + scan_timer %= 2 * MATRIX.width() - 2; + + uint8_t position = scan_timer; + if(position >= MATRIX.width()) + position = 2 * MATRIX.width() - position - 2; + + MATRIX.beginDraw(); + MATRIX.clear(); + MATRIX.line(position, 0, position, 6); + MATRIX.endDraw(); +} + +bool matrix_show_scan_and_run(bool(*thunk)()) { + MATRIX.stroke(YELLOW); + TimerLib.setInterval_us(matrix_paint_scan, 1000000 / SCAN_UPDATE_FREQUENCY); + + bool result = thunk(); + + TimerLib.clearTimer(); + MATRIX.beginDraw(); + MATRIX.clear(); + MATRIX.endDraw(); + + return result; +} + +void matrix_fill(uint32_t color) { + MATRIX.fill(color); + MATRIX.beginDraw(); + MATRIX.clear(); + MATRIX.endDraw(); +} diff --git a/source/arduino/matrix.h b/source/arduino/matrix.h new file mode 100644 index 0000000..0b4029f --- /dev/null +++ b/source/arduino/matrix.h @@ -0,0 +1,33 @@ +/* +Copyright (c): + +Philipp Matthias Schäfer (philipp.matthias.schaefer@posteo.de), 2020 + +This file is part of the KrautStatus' Arduino code. + +The Clean CommonMark library is free software: you can redistribute it and/or +modify it under the terms of the GNU Affero General Public License as published +by the Free Software Foundation, either version 3 of the License, or (at your +option) any later version. + +The Clean CommonMark library 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 Affero General Public License +for more details. + +You should have received a copy of the GNU Affero General Public License along +with the Clean CommonMark library. If not, see . +*/ + +#define COLOR(r, g, b) (r << 16 | g << 8 | b) + +const uint32_t BLACK = COLOR( 0, 0, 0); +const uint32_t GREEN = COLOR( 0, 255, 0); + +void matrix_setup(); + +void matrix_show_failure(); + +bool matrix_show_scan_and_run(bool(*)()); + +void matrix_fill(uint32_t color); diff --git a/source/arduino/sensor.cpp b/source/arduino/sensor.cpp new file mode 100644 index 0000000..173b04a --- /dev/null +++ b/source/arduino/sensor.cpp @@ -0,0 +1,35 @@ +/* +Copyright (c): + +Philipp Matthias Schäfer (philipp.matthias.schaefer@posteo.de), 2020 + +This file is part of the KrautStatus' Arduino code. + +The Clean CommonMark library is free software: you can redistribute it and/or +modify it under the terms of the GNU Affero General Public License as published +by the Free Software Foundation, either version 3 of the License, or (at your +option) any later version. + +The Clean CommonMark library 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 Affero General Public License +for more details. + +You should have received a copy of the GNU Affero General Public License along +with the Clean CommonMark library. If not, see . +*/ + +#include + +#include "sensor.h" +#include "config.h" + +void sensor_setup() { + pinMode(SENSOR_PIN, INPUT_PULLUP); +} + +door_state sensor_get_door_state() { + if (digitalRead(SENSOR_PIN) == HIGH) + return OPEN; + return CLOSED; +} diff --git a/source/arduino/sensor.h b/source/arduino/sensor.h new file mode 100644 index 0000000..e6e5d4b --- /dev/null +++ b/source/arduino/sensor.h @@ -0,0 +1,29 @@ +/* +Copyright (c): + +Philipp Matthias Schäfer (philipp.matthias.schaefer@posteo.de), 2020 + +This file is part of the KrautStatus' Arduino code. + +The Clean CommonMark library is free software: you can redistribute it and/or +modify it under the terms of the GNU Affero General Public License as published +by the Free Software Foundation, either version 3 of the License, or (at your +option) any later version. + +The Clean CommonMark library 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 Affero General Public License +for more details. + +You should have received a copy of the GNU Affero General Public License along +with the Clean CommonMark library. If not, see . +*/ + +typedef enum { + CLOSED = 0, + OPEN = 1 +} door_state; + +void sensor_setup(); + +door_state sensor_get_door_state(); diff --git a/source/arduino/serial.cpp b/source/arduino/serial.cpp new file mode 100644 index 0000000..d394e4d --- /dev/null +++ b/source/arduino/serial.cpp @@ -0,0 +1,26 @@ +/* +Copyright (c): + +Philipp Matthias Schäfer (philipp.matthias.schaefer@posteo.de), 2020 + +This file is part of the KrautStatus' Arduino code. + +The Clean CommonMark library is free software: you can redistribute it and/or +modify it under the terms of the GNU Affero General Public License as published +by the Free Software Foundation, either version 3 of the License, or (at your +option) any later version. + +The Clean CommonMark library 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 Affero General Public License +for more details. + +You should have received a copy of the GNU Affero General Public License along +with the Clean CommonMark library. If not, see . +*/ + +#include + +void serial_setup() { + Serial.begin(9600); +} diff --git a/source/arduino/serial.h b/source/arduino/serial.h new file mode 100644 index 0000000..4e9ed58 --- /dev/null +++ b/source/arduino/serial.h @@ -0,0 +1,22 @@ +/* +Copyright (c): + +Philipp Matthias Schäfer (philipp.matthias.schaefer@posteo.de), 2020 + +This file is part of the KrautStatus' Arduino code. + +The Clean CommonMark library is free software: you can redistribute it and/or +modify it under the terms of the GNU Affero General Public License as published +by the Free Software Foundation, either version 3 of the License, or (at your +option) any later version. + +The Clean CommonMark library 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 Affero General Public License +for more details. + +You should have received a copy of the GNU Affero General Public License along +with the Clean CommonMark library. If not, see . +*/ + +void serial_setup(); diff --git a/source/arduino/door_status.ino b/source/arduino/wifi.cpp similarity index 52% rename from source/arduino/door_status.ino rename to source/arduino/wifi.cpp index f5acb10..a604d12 100644 --- a/source/arduino/door_status.ino +++ b/source/arduino/wifi.cpp @@ -25,82 +25,43 @@ with the Clean CommonMark library. If not, see . #include "config.h" -uint8_t OPEN = 1; -uint8_t CLOSED = 0; - WiFiUDP Udp; -void setup() { - pinMode(REED_PIN, INPUT_PULLUP); - - Serial.begin(9600); - while (!Serial); +void print_mac_address() { + uint8_t mac[6]; + WiFi.macAddress(mac); + Serial.print("MAC: "); + for(unsigned int i = 5; i > 0; --i) { + Serial.print(mac[i], HEX); + Serial.print(":"); + } + Serial.println(mac[0], HEX); +} +boolean wifi_setup() { if (WiFi.status() == WL_NO_SHIELD) { Serial.println("No WiFI shield present"); - // TODO: Create noShieldLoop with visual indication. - while (true); + return false; } + print_mac_address(); + while (true) { - // TODO: Visually indicate that we are trying to connect Serial.print("Connecting to SSID: "); Serial.println(SSID); - status = WiFi.begin(SSID, PASSWORD); - if (status == WL_CONNECTED) { + if (WiFi.begin(SSID, PASSWORD) == WL_CONNECTED) { break; } - // TODO: Visually indicate that we waiting for trying to connect again delay(10000); } - printNetworkInfo(); - -} - -void loop() { - int reed_state = digitalRead(REED_PIN); - - int packetSize = Udp.readPacket(); - if (packetSize) { - Udp.beginPacket(udp.remoteIP(), Udp.remotePort()); - - if (reed_state == HIGH) { - Udp.write(CLOSED); - } else { - Udp.write(OPEN); - } - - Udp.endPacket(); - } - // TODO: Visually indicate open/closed state. -} - - -void printNetworkInfo() { - - Serial.print("Connect to WiFi"); - - IPAddress ip = WiFi.localIP(); + Serial.println("Connect to WiFi"); Serial.print("IP Address: "); - Serial.println(ip); + Serial.println(WiFi.localIP()); - byte mac[6]; - WiFi.macAddress(mac); - Serial.print("MAC address: "); - for (int i = 5; i >= 0; --i) { - if (mac[i] < 16) { - Serial.print("0"); - } - - Serial.print(mac[i], HEX); - - if (i > 0) { - Serial.print(":"); - } - } - Serial.println(); + Udp.begin(SERVER_PORT); + return true; } diff --git a/source/arduino/wifi.h b/source/arduino/wifi.h new file mode 100644 index 0000000..17185b6 --- /dev/null +++ b/source/arduino/wifi.h @@ -0,0 +1,27 @@ +/* +Copyright (c): + +Philipp Matthias Schäfer (philipp.matthias.schaefer@posteo.de), 2020 + +This file is part of the KrautStatus' Arduino code. + +The Clean CommonMark library is free software: you can redistribute it and/or +modify it under the terms of the GNU Affero General Public License as published +by the Free Software Foundation, either version 3 of the License, or (at your +option) any later version. + +The Clean CommonMark library 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 Affero General Public License +for more details. + +You should have received a copy of the GNU Affero General Public License along +with the Clean CommonMark library. If not, see . +*/ + +#include +#include + +extern WiFiUDP Udp; + +bool wifi_setup(); -- 2.39.5 From d734f86979547b91f746ba4aa3d00c3de9248a36 Mon Sep 17 00:00:00 2001 From: Philipp Matthias Schaefer Date: Mon, 28 Dec 2020 22:01:32 +0100 Subject: [PATCH 08/33] Add missing license header --- documentation/arduino.tex | 19 +++++++++++++++++++ scripts/test_udp_api.py | 19 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/documentation/arduino.tex b/documentation/arduino.tex index 816564a..0ddf92f 100644 --- a/documentation/arduino.tex +++ b/documentation/arduino.tex @@ -1,3 +1,22 @@ +% Copyright (c): +% +% Philipp Matthias Schäfer (philipp.matthias.schaefer@posteo.de), 2020 +% +% This file is part of the KrautStatus' Arduino code. +% +% The Clean CommonMark library is free software: you can redistribute it and/or +% modify it under the terms of the GNU Affero General Public License as +% published by the Free Software Foundation, either version 3 of the License, or +% (at your option) any later version. +% +% The Clean CommonMark library 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 Affero +% General Public License for more details. +% +% You should have received a copy of the GNU Affero General Public License along +% with the Clean CommonMark library. If not, see +% . \documentclass[border=1mm]{standalone} \usepackage{tikz} diff --git a/scripts/test_udp_api.py b/scripts/test_udp_api.py index ada9d0c..f0cc5a6 100755 --- a/scripts/test_udp_api.py +++ b/scripts/test_udp_api.py @@ -1,4 +1,23 @@ #!/usr/bin/env python3 +# Copyright (c): +# +# Philipp Matthias Schäfer (philipp.matthias.schaefer@posteo.de), 2020 +# +# This file is part of the KrautStatus' Arduino code. +# +# The Clean CommonMark library is free software: you can redistribute it and/or +# modify it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# The Clean CommonMark library 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 Affero +# General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License along +# with the Clean CommonMark library. If not, see +# . """ Test KrautStatus's UDP API for a given IP and (optionally) port. """ -- 2.39.5 From 9894af021e40ee01a99d1a06f7088861eab5205a Mon Sep 17 00:00:00 2001 From: example Date: Sun, 10 Oct 2021 23:34:24 +0200 Subject: [PATCH 09/33] add error handling for argument and class creation --- source/server/setstatus.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/source/server/setstatus.py b/source/server/setstatus.py index f62a2e3..fc615be 100755 --- a/source/server/setstatus.py +++ b/source/server/setstatus.py @@ -60,6 +60,11 @@ class SetStatus: """ return: boolean """ + try: + self.status = int(self.status) + except Exception as e: + self.log.error('Status argument does not represent a integer') + return False if self.status in (0, 1): self.log.debug('Set value to {}'.format(self.status)) self.status = bytes([self.status]) @@ -233,3 +238,12 @@ class SetStatus: self.log.error('Error: {}'.format(e)) exit(7) + +if __name__ == '__main__': + s = SetStatus() + if len(argv) < 2: + log.error('Usage: setstatus.py <0|1>') + exit(255) + else: + s.run(argv[1]) + -- 2.39.5 From 5f3bb44c7b9035e4828c7f1069b2427a19586eaa Mon Sep 17 00:00:00 2001 From: example Date: Sun, 6 Mar 2022 11:55:29 +0100 Subject: [PATCH 10/33] verarbeitung der statusdaten umgestellt, code verschlankt statusdaten werden jetzt mit den funktionen encode() und decode() verarbeitet, antwort des servers als variable, finaly klausel wieder entfernt --- source/server/apistatusd.py | 41 ++++++++++++------------------------- source/server/setstatus.py | 18 +++++++--------- 2 files changed, 20 insertions(+), 39 deletions(-) diff --git a/source/server/apistatusd.py b/source/server/apistatusd.py index eff1010..a59b555 100755 --- a/source/server/apistatusd.py +++ b/source/server/apistatusd.py @@ -1,6 +1,6 @@ #!/usr/bin/python3 -# file: statusd.py +# file: apistatusd.py # date: 26.07.2019 # email: berhsi@web.de @@ -76,13 +76,12 @@ def display_peercert(cert): def receive_buffer_is_valid(raw_data): ''' Checks validity of the received buffer contents. - param 1: byte + param 1: byte object return: boolean ''' - if raw_data in (b'\x00', b'\x01'): + if raw_data.decode('utf-8', 'strict') in ('0', '1'): logging.debug('Argument is valid: {}'.format(raw_data)) return True - logging.debug('Argument is not valid: {}'.format(raw_data)) return False @@ -90,7 +89,7 @@ def receive_buffer_is_valid(raw_data): def change_status(raw_data, api): ''' Write the new status together with a timestamp into the Space API JSON. - param 1: byte + param 1: byte object param 2: string return: boolean ''' @@ -113,6 +112,7 @@ def change_status(raw_data, api): except Exception as e: logging.error('Failed to change API file') logging.error('{}'.format(e)) + return False logging.debug('API file changed') else: logging.error('API file is not writable. Wrong permissions?') @@ -152,10 +152,10 @@ def set_values(raw_data): ''' Create a timestamp, changes the value of the given byte into a string and returns both. - param 1: byte + param 1: byte object return: tuple ''' - status = True if raw_data == b'\x01' else False + status = True if raw_data.decode('utf-8', 'strict') == '1' else False timestamp = int(str(time()).split('.')[0]) logging.debug('Set values for timestamp: {} and status: {}'.format( @@ -173,6 +173,7 @@ def main(): (cve-2011-3389) ''' + answer = '3'.encode(encoding='utf-8', errors='strict') loglevel = logging.WARNING formatstring = '%(asctime)s: %(levelname)s: %(message)s' logging.basicConfig(format=formatstring, level=loglevel) @@ -196,7 +197,7 @@ def main(): 'template': './api_template' } } - configfile = './statusd.conf' + configfile = './apistatusd.conf' config = configparser.ConfigParser() config.read_dict(default_config) if not config.read(configfile): @@ -273,19 +274,10 @@ def main(): raw_data = conn.recv(1) if receive_buffer_is_valid(raw_data) is True: if change_status(raw_data, config['api']['api']) is True: - logging.debug('Send {} back'.format(raw_data)) - conn.send(raw_data) - # change_status returns false: - else: - logging.info('Failed to change status') - if conn: - conn.send(b'\x03') - # receive_handle returns false: - else: - logging.info('Invalid argument received: {}'.format(raw_data)) - logging.debug('Send {} back'.format(b'\x03')) - if conn: - conn.send(b'\x03') + answer = raw_data + if conn: + logging.debug('Send {} back'.format(raw_data)) + conn.send(answer) sleep(0.1) # protection against dos except KeyboardInterrupt: logging.info('Keyboard interrupt received') @@ -293,13 +285,6 @@ def main(): except Exception as e: logging.error('{}'.format(e)) continue - finally: - if mySocket: - logging.info('Shutdown socket') - try: - mySocket.shutdown(socket.SHUT_RDWR) - except Exception as e: - logging.error(f'Error while shutdown socket: {e}') return 0 diff --git a/source/server/setstatus.py b/source/server/setstatus.py index fc615be..e0a36d2 100755 --- a/source/server/setstatus.py +++ b/source/server/setstatus.py @@ -60,15 +60,10 @@ class SetStatus: """ return: boolean """ - try: - self.status = int(self.status) - except Exception as e: - self.log.error('Status argument does not represent a integer') - return False - if self.status in (0, 1): - self.log.debug('Set value to {}'.format(self.status)) - self.status = bytes([self.status]) + if self.status in ('0', '1'): + self.log.debug('Set status to {}'.format(self.status)) return True + self.log.debug('{} is not a valid status'.format(self.status)) return False def set_config(self): @@ -197,7 +192,6 @@ class SetStatus: # check given status if self.check_status() is False: - self.log.error('No valid status given') exit(1) # log config if level is debug @@ -222,12 +216,14 @@ class SetStatus: # send status try: self.log.debug('Send new status: {}'.format(self.status)) - self.connection.send(self.status) + self.connection.send(self.status.encode(encoding='utf-8', + errors='strict')) except Exception as e: self.log.error('Error: {}'.format(e)) exit(6) try: - response = self.connection.recv(1) + response = self.connection.recv(1).decode(encoding='utf-8', + errors='strict') self.log.debug('Server returns: {}'.format(response)) if response == self.status: self.log.info('Status sucessfull updated') -- 2.39.5 From 58d9c327c9b8d525e28e6ccd54a18604bdbb643a Mon Sep 17 00:00:00 2001 From: example Date: Fri, 11 Mar 2022 13:28:03 +0100 Subject: [PATCH 11/33] =?UTF-8?q?debugausgaben=20f=C3=BCr=20den=20ssl-cont?= =?UTF-8?q?ext=20hinzu?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/server/apistatusd.py | 62 ++++++++++++++++++++++++------------- 1 file changed, 41 insertions(+), 21 deletions(-) diff --git a/source/server/apistatusd.py b/source/server/apistatusd.py index a59b555..563960f 100755 --- a/source/server/apistatusd.py +++ b/source/server/apistatusd.py @@ -43,35 +43,51 @@ def print_config(config): for i in config[section]: logging.debug(' {}: {}'.format(i, config[section][i])) - def print_ciphers(cipherlist): ''' Prints the list of allowed ciphers. param1: dictionary return: boolean ''' - print('Available ciphers') + logging.debug('Available ciphers') for i in cipherlist: - print('\n') for j in i.keys(): - print('{}: {}'.format(j, i[j])) - print('\n') + if j in ('name', 'protocol'): + logging.debug('{}: {}'.format(j, i[j])) +def print_context(ctx): + ''' + Prints the ssl settings for the given ssl context. + param1: context object + ''' + logging.debug('----------- context ----------------') + logging.debug('Minimum version ssl: {}'.format(ctx.minimum_version)) + logging.debug('Maximum version ssl: {}'.format(ctx.maximum_version)) + logging.debug('SSL options enabled: {}'.format(ctx.options)) + logging.debug('Protocol: {}'.format(ctx.protocol)) + logging.debug('Verify flags certificates: {}'.format(ctx.verify_flags)) + logging.debug('Verify mode: {}'.format(ctx.verify_mode)) + print_ciphers(ctx.get_ciphers()) + logging.debug('------------------------------------') def display_peercert(cert): ''' Displays the values of a given certificate. - param1: dictionary - return: boolean + param1: dictionary or none ''' - for i in cert.keys(): - print('{}:'.format(i)) - if i in ('subject', 'issuer'): - for j in cert[i]: - print('\t{}'.format(j)) - else: - print('\t{}'.format(cert[i])) - + if cert == None: + logging.debug('Peer does not offer a certificate') + elif len(cert) == 0: + logging.debug('Peer certificate was not valid') + else: + logging.debug('Peer certificate commonName: {}'.format( + cert['subject'][5][0][1])) + logging.debug('Peer certificate serialNumber: {}'.format( + cert['serialNumber'])) + logging.debug('Peer certificate notBefore: {}'.format( + cert['notBefore'])) + logging.debug('Peer certificate notAfter: {}'.format( + cert['notAfter'])) def receive_buffer_is_valid(raw_data): ''' @@ -225,12 +241,11 @@ def main(): context.load_cert_chain(certfile=config['server']['cert'], keyfile=config['server']['key']) context.load_verify_locations(cafile=config['client']['cert']) - context.set_ciphers('EECDH+AESGCM') # only ciphers for tls 1.2 and 1.3 context.options = ssl.OP_CIPHER_SERVER_PREFERENCE # ensure, compression is disabled (disabled by default anyway at the moment) context.options |= ssl.OP_NO_COMPRESSION logging.debug('SSL context created') - # print_ciphers(context.get_ciphers()) + print_context(context) with socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) as mySocket: logging.debug('Socket created') @@ -263,13 +278,18 @@ def main(): conn.settimeout(float(config['general']['timeout'])) except socket.timeout: logging.error('Socket timeout') + continue except Exception as e: logging.error('Connection failed: {}'.format(e)) + continue logging.info('Connection established') - logging.info('Peer certificate commonName: {}'.format( - conn.getpeercert()['subject'][5][0][1])) - logging.debug('Peer certificate serialNumber: {}'.format( - conn.getpeercert()['serialNumber'])) + try: + cert = conn.getpeercert(binary_form=False) + display_peercert(cert) + except ValueError: + logging.debug('SSL handshake has not been done yet') + except Exception as e: + logging.debug('Unexpected error: {}'.format(e)) raw_data = conn.recv(1) if receive_buffer_is_valid(raw_data) is True: -- 2.39.5 From 804e9a10e56e6b71c2220a2d27f630b8806d199c Mon Sep 17 00:00:00 2001 From: example Date: Wed, 6 Apr 2022 10:20:06 +0200 Subject: [PATCH 12/33] clientauthentifizierung auf optional gesetzt --- source/server/apistatusd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/server/apistatusd.py b/source/server/apistatusd.py index 563960f..a4c7244 100755 --- a/source/server/apistatusd.py +++ b/source/server/apistatusd.py @@ -237,7 +237,7 @@ def main(): sys.exit(1) context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) - context.verify_mode = ssl.CERT_REQUIRED + context.verify_mode = ssl.CERT_OPTIONAL context.load_cert_chain(certfile=config['server']['cert'], keyfile=config['server']['key']) context.load_verify_locations(cafile=config['client']['cert']) -- 2.39.5 From e79258b8bee9b04a1dbf4bafa7b976bd0f0ee314 Mon Sep 17 00:00:00 2001 From: example Date: Wed, 6 Apr 2022 10:22:36 +0200 Subject: [PATCH 13/33] zertifikate umbenannt --- source/server/apistatusd.conf | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/server/apistatusd.conf b/source/server/apistatusd.conf index 73433e8..e0306de 100644 --- a/source/server/apistatusd.conf +++ b/source/server/apistatusd.conf @@ -14,11 +14,11 @@ loglevel = debug [server] host = localhost port = 10001 -cert = ./certs/server-pub.pem -key = ./certs/server-key.pem +cert = ./certs/statusd-pub.pem +key = ./certs/statusd-key.pem [client] -cert = ./certs/client-pub.pem +cert = ./certs/statusclient-pub.pem [api] api = ./api -- 2.39.5 From b6acaa08a831f64b1cd17523752985b050a0f721 Mon Sep 17 00:00:00 2001 From: example Date: Wed, 6 Apr 2022 10:39:15 +0200 Subject: [PATCH 14/33] =?UTF-8?q?kommentare=20eingef=C3=BCgt,=20fehlerbeha?= =?UTF-8?q?ndlung=20ge=C3=A4ndert?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/server/setstatus.py | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/source/server/setstatus.py b/source/server/setstatus.py index e0a36d2..eb9f3cf 100755 --- a/source/server/setstatus.py +++ b/source/server/setstatus.py @@ -58,6 +58,7 @@ class SetStatus: def check_status(self): """ + checkes, if the self.status variable is a valid value return: boolean """ if self.status in ('0', '1'): @@ -68,6 +69,8 @@ class SetStatus: def set_config(self): """ + Tries to read and use the values from the configuration file. If + this failes, we still use the default values. """ self.log = logging.getLogger() # read config file @@ -89,7 +92,8 @@ class SetStatus: def check_certs(self, certs): """ - Check if certs readable. + Check if certs are readable. + return: boolean """ self.log.debug('Check certificates') for certfile in certs: @@ -111,25 +115,28 @@ class SetStatus: def create_ssl_context(self): """ + Creates SSL context + return: context object or false """ - context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH, - cafile=self.config['server']['cert']) - if not context: + try: + context = ssl.create_default_context(purpose=ssl.Purpose.SERVER_AUTH) + except Exception as e: self.log.error('Failed to create SSL Context') return False + context.load_verify_locations(cafile=self.config['server']['cert']) + context.load_cert_chain(certfile=self.config['client']['cert'], + keyfile=self.config['client']['key']) context.set_ciphers('EECDH+AESGCM') # only ciphers for tls 1.2 and 1.3 context.options |= getattr(ssl._ssl, 'OP_NO_COMPRESSION', 0) - try: - context.load_cert_chain(certfile=self.config['client']['cert'], - keyfile=self.config['client']['key']) - except Exception as e: - self.log.error('Failed to load cert chain') - return False; self.log.debug('SSL context created') return context def create_ssl_socket(self, config, context): """ + Opens a socket and wrapes the socket into the given ssl context. + param1: dictionary + param2: ssl context + return: ssl-socket or false """ bare_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) if not bare_socket: @@ -210,7 +217,7 @@ class SetStatus: if self.context is False: exit(3) - # get connection + # get a ssl encrypted connection self.connection = self.create_ssl_connection() # send status -- 2.39.5 From 56742d87077f54546d0333a86239120814f936e3 Mon Sep 17 00:00:00 2001 From: example Date: Wed, 6 Apr 2022 10:43:16 +0200 Subject: [PATCH 15/33] veralteten kommentar entfernt --- source/server/setstatus.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/source/server/setstatus.py b/source/server/setstatus.py index eb9f3cf..e6fccfa 100755 --- a/source/server/setstatus.py +++ b/source/server/setstatus.py @@ -9,8 +9,7 @@ # krautspaces doorstatus. # client, who connects to the statusserver at port 10001 to update the -# krautspace door status. If no status is given as argument, he reads from -# stdin until input is 0 or 1. +# krautspace door status. import os import ssl -- 2.39.5 From 7598b237bffbc60fac9bac4db471fc62121ee763 Mon Sep 17 00:00:00 2001 From: example Date: Wed, 6 Apr 2022 11:37:51 +0200 Subject: [PATCH 16/33] =?UTF-8?q?initialer=20commit=20eines=20statusclient?= =?UTF-8?q?s=20f=C3=BCr=20eine=20NodeMCU?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/nodemcu/statusclient/certs.template | 36 ++++ source/nodemcu/statusclient/config.h | 18 ++ .../nodemcu/statusclient/credentials.template | 17 ++ source/nodemcu/statusclient/statusclient.ino | 202 ++++++++++++++++++ 4 files changed, 273 insertions(+) create mode 100644 source/nodemcu/statusclient/certs.template create mode 100644 source/nodemcu/statusclient/config.h create mode 100644 source/nodemcu/statusclient/credentials.template create mode 100644 source/nodemcu/statusclient/statusclient.ino diff --git a/source/nodemcu/statusclient/certs.template b/source/nodemcu/statusclient/certs.template new file mode 100644 index 0000000..07679bb --- /dev/null +++ b/source/nodemcu/statusclient/certs.template @@ -0,0 +1,36 @@ +/* + * file: certs.template + * desc: This file is part of the Krautspace Doorstatus project. It contains + * certificates for the statusclient.ino programm, that runs on a NodeMCU + * with a ESP8266 chip. + * + * Replace the comments in certificate sections with our own certificates. + */ + + +const char SERVER_CERT[] PROGMEM = R"EOF( +-----BEGIN CERTIFICATE----- +/* + * lace for the public server certificate to authenticate the doorstatus + * server. + */ +-----END CERTIFICATE----- +)EOF"; + +const char CLIENT_CERT[] PROGMEM = R"EOF( +-----BEGIN CERTIFICATE----- +/* + * Place for the clients (this program) public certificate to authenticate + * client against the server. + */ +-----END CERTIFICATE----- +)EOF"; + +const char CLIENT_KEY[] PROGMEM = R"EOF( +-----BEGIN CERTIFICATE----- +/* + * Place for the clients private key file. + */ +-----END CERTIFICATE----- +)EOF"; + diff --git a/source/nodemcu/statusclient/config.h b/source/nodemcu/statusclient/config.h new file mode 100644 index 0000000..a9bbde5 --- /dev/null +++ b/source/nodemcu/statusclient/config.h @@ -0,0 +1,18 @@ +/* + * file: config.h + */ + +/* endpoint */ +#define SERVER_URL "status.kraut.space" +#define SERVER_PORT 10001 + +/* serial interface settings */ +#define BAUD_RATE 115200 +#define DEBUG true + +/* frequence to read the pin */ +#define FREQUENCY 10000 + +/* time server settings */ +#define NTP_URL "pool.ntp.org" +#define TZ_STRING "CET-1CDT,M3.5.0,M10.5.0/3" diff --git a/source/nodemcu/statusclient/credentials.template b/source/nodemcu/statusclient/credentials.template new file mode 100644 index 0000000..2ba711d --- /dev/null +++ b/source/nodemcu/statusclient/credentials.template @@ -0,0 +1,17 @@ +/* + * file: credentials.template + * desc: This file is part of the Krautspace Doorstatus project. It contains + * wifi ssid and passwords for the statusclient.ino programm, that runs on a + * NodeMCU with a ESP8266 chip. + * + * Rename this file into 'credentials.h' and adapt the values to your + * wifi conditions. + */ + +/* wifi credentials */ +#define SSID_1 "your_first__wlan_ssid" +#define PSK_1 "your_first_wlan_passwort" +#define SSID_2 "your_second_wlan_ssid" +#define PSK_2 "your_seconde_wlan_password" +#define SSID_3 "your_third_wlan_ssid" +#define PSK_3 "your_third_wlan_password" diff --git a/source/nodemcu/statusclient/statusclient.ino b/source/nodemcu/statusclient/statusclient.ino new file mode 100644 index 0000000..6ef9ded --- /dev/null +++ b/source/nodemcu/statusclient/statusclient.ino @@ -0,0 +1,202 @@ +/* + * file: statusclient.ino + * desc: This file is part of the Krautspace Doorstatus project. It's the + * main file for a client, who deals with the input from a reed sensor and + * push these values to a server. The code is make to run on a NodeMCU with + * ESP8266 chip. + */ + +#include +#include +#include + +#include "config.h" +#include "certs.h" +#include "credentials.h" + +const int LED_PIN = 16; // D0 +const int REED_PIN = 5; // D1 + +typedef enum { + DOOR_CLOSED = 0, + DOOR_OPEN = 1 +} door_state; +door_state current_door_state = DOOR_CLOSED; +door_state new_door_state = DOOR_CLOSED; + + +void init_serial() { + /* + * set baudrate and debug modus + */ + Serial.begin(BAUD_RATE); + Serial.setDebugOutput(DEBUG); + Serial.println(); + Serial.println("[Srl] Serial interface initialized"); +} + +void init_pins() { + /* + * set gpio for reed sensor and led + */ + pinMode(REED_PIN, INPUT_PULLUP); + pinMode(LED_PIN, OUTPUT); + digitalWrite(LED_PIN, LOW); + Serial.println("[Pin] LED and REED initialized"); +} + + +void init_wifi() { + /* + * first turn wifi off and than in access point mode + * maybe turn of is not needed! + */ + ESP8266WiFiMulti wifi; + WiFi.mode(WIFI_OFF); + WiFi.mode(WIFI_STA); + wifi.addAP(SSID_1, PSK_1); + wifi.addAP(SSID_2, PSK_2); + Serial.println("[Wifi] Wifi initialized"); + wifi.run(); + if (WiFi.status() == WL_CONNECTED) { + Serial.print("[Wif] Connected to "); + Serial.println(WiFi.SSID()); + Serial.print("[Wifi] IP: "); + Serial.println(WiFi.localIP()); + } else { + Serial.println("[Wifi] Error: Failed to connect"); + } +} + +door_state read_door_state() { + /* + * die initialisierung des reed-pin mit pullup bewirkt, daß am pin + * 3,3 volt anliegen. die verbindung des pins mit GND sorgt dafür, + * daß die spannung "abfließen" kann. dadurch hat der pin dann den + * status 'low'. + * geschlossene tür -> reed geschlossen -> low + * geöffnete tür -> reed offen -> high + */ + if (digitalRead(REED_PIN) == HIGH) { + return DOOR_OPEN; + } + return DOOR_CLOSED; +} + +void toggle_led(door_state state) { + /* + * turns onboard led on or depends on the door state + */ + if (state == DOOR_OPEN) { + digitalWrite(LED_PIN, LOW); + } else { + digitalWrite(LED_PIN, HIGH); + } + delay(500); +} + +void set_clock() { + + configTime(TZ_STRING, NTP_URL); + + Serial.print("Waiting for NTP time sync: "); + time_t now = time(nullptr); + while (now < 8 * 3600 * 2) { + delay(500); + Serial.print("."); + now = time(nullptr); + } + Serial.println(""); + struct tm timeinfo; + gmtime_r(&now, &timeinfo); + Serial.print("Current time: "); + Serial.print(asctime(&timeinfo)); +} + +int send_status(door_state state) { + + /* + * geht die initialisierung mit einem byte länge? + * terminiert strcpy den status mit \0? + */ + char status[2] = ""; + + if (state == DOOR_CLOSED) { + strncpy(status, "0", 1); + } else if (state == DOOR_OPEN) { + strncpy(status, "1", 1); + } else { + return 1; + } + + BearSSL::WiFiClientSecure client; + BearSSL::X509List server_cert(SERVER_CERT); + BearSSL::X509List client_cert(CLIENT_CERT); + BearSSL::PrivateKey client_key(CLIENT_KEY); + client.setTrustAnchors(&server_cert); + client.setClientRSACert(&client_cert, &client_key); + delay(500); + Serial.println("[Ctx] SSL Context initialized"); + Serial.print("[Ctx] Free Heap: "); + Serial.println(ESP.getFreeHeap()); + delay(500); + Serial.printf("[Send] Connect to %s:%i\n", SERVER_URL, SERVER_PORT); + client.connect(SERVER_URL, SERVER_PORT); + if (!client.connected()) { + Serial.println("[Send] Can't connect to server"); + Serial.print("[Send] SSL Error: "); + Serial.println(client.getLastSSLError()); + client.stop(); + return 1; + } else { + ESP.resetFreeContStack(); + uint32_t freeStackStart = ESP.getFreeContStack(); + Serial.println("[Send] Connection successful established"); + Serial.printf("[Send] Send status: %s\n", status); + client.write(status); + + } + return 0; +} + + + + + +void setup() { + + /* + * things to do once at boot time + */ + init_serial(); + Serial.print("[Init] Free Heap ( after serial init): "); + Serial.println(ESP.getFreeHeap()); + init_pins(); + Serial.print("[Init] Free Heap (after pins init): "); + Serial.println(ESP.getFreeHeap()); + init_wifi(); + Serial.print("[Init] Free Heap (after wifi init): "); + Serial.println(ESP.getFreeHeap()); + delay(500); + set_clock(); + Serial.print("[Init] Free Heap (after setting clock): "); + Serial.println(ESP.getFreeHeap()); + delay(500); +} + +void loop() { + + /* + * things are running in a endless loop + */ + new_door_state = read_door_state(); + if (new_door_state != current_door_state) { + Serial.printf("[Loop] Status has changed to %i\n", new_door_state); + toggle_led(new_door_state); + send_status(new_door_state); + current_door_state = new_door_state; + } + Serial.print("[Loop] Free Heap: "); + Serial.println(ESP.getFreeHeap()); + delay(FREQUENCY); +} -- 2.39.5 From a1d14235ff8b710be1368b64706f6677c5178c82 Mon Sep 17 00:00:00 2001 From: example Date: Thu, 7 Apr 2022 01:07:44 +0200 Subject: [PATCH 17/33] frequenz der abfrage des reed-pins halbiert --- source/nodemcu/statusclient/config.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/nodemcu/statusclient/config.h b/source/nodemcu/statusclient/config.h index a9bbde5..b078888 100644 --- a/source/nodemcu/statusclient/config.h +++ b/source/nodemcu/statusclient/config.h @@ -11,7 +11,7 @@ #define DEBUG true /* frequence to read the pin */ -#define FREQUENCY 10000 +#define FREQUENCY 5000 /* time server settings */ #define NTP_URL "pool.ntp.org" -- 2.39.5 From 8f4f6d82d4966ab39594afab7d8a84ea6c5b12e0 Mon Sep 17 00:00:00 2001 From: example Date: Thu, 7 Apr 2022 01:09:05 +0200 Subject: [PATCH 18/33] =?UTF-8?q?kommentare=20eingef=C3=BCgt,=20led=20blin?= =?UTF-8?q?kt=20fehlercodes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/nodemcu/statusclient/statusclient.ino | 184 ++++++++++++------- 1 file changed, 122 insertions(+), 62 deletions(-) diff --git a/source/nodemcu/statusclient/statusclient.ino b/source/nodemcu/statusclient/statusclient.ino index 6ef9ded..ed21eb0 100644 --- a/source/nodemcu/statusclient/statusclient.ino +++ b/source/nodemcu/statusclient/statusclient.ino @@ -18,9 +18,9 @@ const int LED_PIN = 16; // D0 const int REED_PIN = 5; // D1 typedef enum { - DOOR_CLOSED = 0, - DOOR_OPEN = 1 -} door_state; + DOOR_CLOSED = 0, + DOOR_OPEN = 1 +} door_state; door_state current_door_state = DOOR_CLOSED; door_state new_door_state = DOOR_CLOSED; @@ -41,30 +41,33 @@ void init_pins() { */ pinMode(REED_PIN, INPUT_PULLUP); pinMode(LED_PIN, OUTPUT); - digitalWrite(LED_PIN, LOW); + digitalWrite(LED_PIN, HIGH); Serial.println("[Pin] LED and REED initialized"); } void init_wifi() { - /* - * first turn wifi off and than in access point mode - * maybe turn of is not needed! - */ + /* + * Creates the ssl context. Turns wifi off and than into + * access point mode. + * TODO: is 'turn of' needed! + */ ESP8266WiFiMulti wifi; WiFi.mode(WIFI_OFF); WiFi.mode(WIFI_STA); wifi.addAP(SSID_1, PSK_1); - wifi.addAP(SSID_2, PSK_2); + wifi.addAP(SSID_2, NULL); Serial.println("[Wifi] Wifi initialized"); wifi.run(); - if (WiFi.status() == WL_CONNECTED) { - Serial.print("[Wif] Connected to "); - Serial.println(WiFi.SSID()); - Serial.print("[Wifi] IP: "); - Serial.println(WiFi.localIP()); + if (WiFi.status() == WL_CONNECTED) { + Serial.print("[Wif] Connected to "); + Serial.println(WiFi.SSID()); + Serial.print("[Wifi] IP: "); + Serial.println(WiFi.localIP()); + set_clock(); } else { - Serial.println("[Wifi] Error: Failed to connect"); + Serial.println("[Wifi] Error: Failed to connect"); + signal_wifi_failed(); } } @@ -83,41 +86,112 @@ door_state read_door_state() { return DOOR_CLOSED; } -void toggle_led(door_state state) { - /* - * turns onboard led on or depends on the door state +void signal_door_changed() { + /* + * LED signal, if door is opened */ - if (state == DOOR_OPEN) { + uint8_t count = 2; + for(uint8_t i=0; i!= count; ++i) { digitalWrite(LED_PIN, LOW); - } else { + delay(100); digitalWrite(LED_PIN, HIGH); + delay(100); } - delay(500); +} + +void signal_send_successful() { + /* + * LED signal, if new status was send successful + */ + uint8_t count = 5; + for(uint8_t i=0; i!= count; ++i) { + digitalWrite(LED_PIN, LOW); + delay(100); + digitalWrite(LED_PIN, HIGH); + delay(100); + } +} + +void signal_clock_failed() { + /* + * LED signal, if time setting failed + */ + uint8_t count = 2; + for(uint8_t i=0; i!= count; ++i) { + digitalWrite(LED_PIN, LOW); + delay(500); + digitalWrite(LED_PIN, HIGH); + delay(500); + } + delay(2000); +} + +void signal_wifi_failed() { + /* + * LED signal, if wifi initialication was failed + */ + uint8_t count = 3; + for(uint8_t i=0; i!= count; ++i) { + digitalWrite(LED_PIN, LOW); + delay(500); + digitalWrite(LED_PIN, HIGH); + delay(500); + } + delay(2000); +} + +void signal_connect_failed() { + /* + * LED signal, if door is opened + */ + uint8_t count = 4; + for(uint8_t i=0; i!= count; ++i) { + digitalWrite(LED_PIN, LOW); + delay(500); + digitalWrite(LED_PIN, HIGH); + delay(500); + } + delay(2000); +} + +void signal_send_failed() { + /* + * LED signal, if door is opened + */ + uint8_t count = 5; + for(uint8_t i=0; i!= count; ++i) { + digitalWrite(LED_PIN, LOW); + delay(500); + digitalWrite(LED_PIN, HIGH); + delay(500); + } + delay(2000); } void set_clock() { + /* + * We need time for certificate authorization + */ + configTime(TZ_STRING, NTP_URL); - configTime(TZ_STRING, NTP_URL); - - Serial.print("Waiting for NTP time sync: "); - time_t now = time(nullptr); - while (now < 8 * 3600 * 2) { - delay(500); - Serial.print("."); - now = time(nullptr); - } - Serial.println(""); - struct tm timeinfo; - gmtime_r(&now, &timeinfo); - Serial.print("Current time: "); - Serial.print(asctime(&timeinfo)); + Serial.print("[Clock] Waiting for NTP time sync"); + time_t now = time(nullptr); + while (now < 8 * 3600 * 2) { + delay(500); + Serial.print("."); + now = time(nullptr); + } + Serial.println(""); + struct tm timeinfo; + gmtime_r(&now, &timeinfo); + Serial.print("[Clock] Current time: "); + Serial.println(asctime(&timeinfo)); } int send_status(door_state state) { /* - * geht die initialisierung mit einem byte länge? - * terminiert strcpy den status mit \0? + * Inits wifi (if needed) and send the status */ char status[2] = ""; @@ -135,17 +209,19 @@ int send_status(door_state state) { BearSSL::PrivateKey client_key(CLIENT_KEY); client.setTrustAnchors(&server_cert); client.setClientRSACert(&client_cert, &client_key); - delay(500); + delay(200); Serial.println("[Ctx] SSL Context initialized"); - Serial.print("[Ctx] Free Heap: "); - Serial.println(ESP.getFreeHeap()); - delay(500); + delay(200); + if (WiFi.status() != WL_CONNECTED) { + init_wifi(); + } Serial.printf("[Send] Connect to %s:%i\n", SERVER_URL, SERVER_PORT); client.connect(SERVER_URL, SERVER_PORT); if (!client.connected()) { Serial.println("[Send] Can't connect to server"); Serial.print("[Send] SSL Error: "); Serial.println(client.getLastSSLError()); + signal_send_failed(); client.stop(); return 1; } else { @@ -154,34 +230,19 @@ int send_status(door_state state) { Serial.println("[Send] Connection successful established"); Serial.printf("[Send] Send status: %s\n", status); client.write(status); - + signal_send_successful(); } return 0; } - - - void setup() { /* * things to do once at boot time */ init_serial(); - Serial.print("[Init] Free Heap ( after serial init): "); - Serial.println(ESP.getFreeHeap()); init_pins(); - Serial.print("[Init] Free Heap (after pins init): "); - Serial.println(ESP.getFreeHeap()); - init_wifi(); - Serial.print("[Init] Free Heap (after wifi init): "); - Serial.println(ESP.getFreeHeap()); - delay(500); - set_clock(); - Serial.print("[Init] Free Heap (after setting clock): "); - Serial.println(ESP.getFreeHeap()); - delay(500); } void loop() { @@ -192,11 +253,10 @@ void loop() { new_door_state = read_door_state(); if (new_door_state != current_door_state) { Serial.printf("[Loop] Status has changed to %i\n", new_door_state); - toggle_led(new_door_state); - send_status(new_door_state); - current_door_state = new_door_state; + signal_door_changed();; + if (send_status(new_door_state) == 0) { + current_door_state = new_door_state; + } } - Serial.print("[Loop] Free Heap: "); - Serial.println(ESP.getFreeHeap()); delay(FREQUENCY); } -- 2.39.5 From a93bb9ea0a46328d5bf954a09bd03db6ceef17a9 Mon Sep 17 00:00:00 2001 From: +++ Date: Thu, 7 Apr 2022 23:05:46 +0200 Subject: [PATCH 19/33] struktureller umbau MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ClientSecure instanz jetzt global, init_wifi() ins setup verlagert, prüfung auf wifi in jedem loop, client.stop() auch bei erfolg --- source/nodemcu/statusclient/statusclient.ino | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/source/nodemcu/statusclient/statusclient.ino b/source/nodemcu/statusclient/statusclient.ino index ed21eb0..5faaa2e 100644 --- a/source/nodemcu/statusclient/statusclient.ino +++ b/source/nodemcu/statusclient/statusclient.ino @@ -22,8 +22,8 @@ typedef enum { DOOR_OPEN = 1 } door_state; door_state current_door_state = DOOR_CLOSED; -door_state new_door_state = DOOR_CLOSED; +BearSSL::WiFiClientSecure client; void init_serial() { /* @@ -45,7 +45,6 @@ void init_pins() { Serial.println("[Pin] LED and REED initialized"); } - void init_wifi() { /* * Creates the ssl context. Turns wifi off and than into @@ -203,18 +202,12 @@ int send_status(door_state state) { return 1; } - BearSSL::WiFiClientSecure client; BearSSL::X509List server_cert(SERVER_CERT); BearSSL::X509List client_cert(CLIENT_CERT); BearSSL::PrivateKey client_key(CLIENT_KEY); client.setTrustAnchors(&server_cert); client.setClientRSACert(&client_cert, &client_key); - delay(200); Serial.println("[Ctx] SSL Context initialized"); - delay(200); - if (WiFi.status() != WL_CONNECTED) { - init_wifi(); - } Serial.printf("[Send] Connect to %s:%i\n", SERVER_URL, SERVER_PORT); client.connect(SERVER_URL, SERVER_PORT); if (!client.connected()) { @@ -231,6 +224,7 @@ int send_status(door_state state) { Serial.printf("[Send] Send status: %s\n", status); client.write(status); signal_send_successful(); + client.stop(); } return 0; } @@ -243,6 +237,7 @@ void setup() { */ init_serial(); init_pins(); + init_wifi(); } void loop() { @@ -250,10 +245,13 @@ void loop() { /* * things are running in a endless loop */ - new_door_state = read_door_state(); + if (WiFi.status() != WL_CONNECTED) { + init_wifi(); + } + door_state new_door_state = read_door_state(); if (new_door_state != current_door_state) { Serial.printf("[Loop] Status has changed to %i\n", new_door_state); - signal_door_changed();; + signal_door_changed(); if (send_status(new_door_state) == 0) { current_door_state = new_door_state; } -- 2.39.5 From c4d02a73c9772b819f20d34ce992884eee2249c0 Mon Sep 17 00:00:00 2001 From: +++ Date: Sun, 10 Jul 2022 17:54:37 +0200 Subject: [PATCH 20/33] konfiguration fuer mastodon hinzu --- source/server/apistatusd.conf | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/source/server/apistatusd.conf b/source/server/apistatusd.conf index e0306de..177f44a 100644 --- a/source/server/apistatusd.conf +++ b/source/server/apistatusd.conf @@ -23,3 +23,9 @@ cert = ./certs/statusclient-pub.pem [api] api = ./api template = ./api_template + +[mastodon] +send = true +host = localhost +token = aaaaa-bbbbb-ccccc-ddddd-eeeee + -- 2.39.5 From edece83dd1c9c2ff6d341300f1c4d4a323b4b2e5 Mon Sep 17 00:00:00 2001 From: +++ Date: Sun, 10 Jul 2022 18:17:33 +0200 Subject: [PATCH 21/33] kleinere umstrukturierung auswertung der konfig fuer mastodon, default_config erweitert, set_values() in get_status_and_timestamp() umbenannt, statusstring und timestamp werden in main() aufgerufen und an andere funktionen uebergeben --- source/server/apistatusd.py | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/source/server/apistatusd.py b/source/server/apistatusd.py index a4c7244..fca5c21 100755 --- a/source/server/apistatusd.py +++ b/source/server/apistatusd.py @@ -14,6 +14,7 @@ import os import socket import ssl import sys +from mastodon import Mastodon from time import time, sleep import configparser @@ -102,7 +103,7 @@ def receive_buffer_is_valid(raw_data): return False -def change_status(raw_data, api): +def change_status(status, timestamp, filename): ''' Write the new status together with a timestamp into the Space API JSON. param 1: byte object @@ -112,14 +113,13 @@ def change_status(raw_data, api): logging.debug('Change status API') # todo: use walrus operator := when migrating to python >= 3.8 - data = read_api(api) + data = read_api(filename) if data is False: return False - status, timestamp = set_values(raw_data) - if os.access(api, os.W_OK): + if os.access(filename, os.W_OK): logging.debug('API file is writable') - with open(api, 'w') as api_file: + with open(filename, 'w') as api_file: logging.debug('API file open successfull') data["state"]["open"] = status data["state"]["lastchange"] = timestamp @@ -133,7 +133,7 @@ def change_status(raw_data, api): else: logging.error('API file is not writable. Wrong permissions?') return False - logging.info('Status successfull changed to {}'.format(status)) + logging.info('API file successfull changed to {}'.format(status)) return True @@ -164,7 +164,7 @@ def read_api(api): return api_json_data -def set_values(raw_data): +def get_status_and_time(raw_data): ''' Create a timestamp, changes the value of the given byte into a string and returns both. @@ -211,6 +211,11 @@ def main(): 'api': { 'api': './api', 'template': './api_template' + }, + 'mastodon': { + 'send': 'false', + 'host': 'localhost', + 'token': 'aaaaa-bbbbb-ccccc-ddddd-eeeee' } } configfile = './apistatusd.conf' @@ -293,8 +298,12 @@ def main(): raw_data = conn.recv(1) if receive_buffer_is_valid(raw_data) is True: - if change_status(raw_data, config['api']['api']) is True: + status, timestamp = get_status_and_time(raw_data) + if change_status(status, timestamp, config['api']['api']) is True: answer = raw_data + if config['mastodon']['send'].lower() == 'true': + logging.debug('Try to toot status') + else: logging.debug('Toot is set to false') if conn: logging.debug('Send {} back'.format(raw_data)) conn.send(answer) -- 2.39.5 From 930ab7eef3f834a46c523f11ce4e1289a4e4d9b9 Mon Sep 17 00:00:00 2001 From: +++ Date: Sun, 10 Jul 2022 19:38:29 +0200 Subject: [PATCH 22/33] funktion send_toot() hinzu --- source/server/apistatusd.py | 39 ++++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/source/server/apistatusd.py b/source/server/apistatusd.py index fca5c21..0cae07f 100755 --- a/source/server/apistatusd.py +++ b/source/server/apistatusd.py @@ -15,7 +15,7 @@ import socket import ssl import sys from mastodon import Mastodon -from time import time, sleep +from time import time, localtime, strftime, sleep import configparser @@ -103,6 +103,37 @@ def receive_buffer_is_valid(raw_data): return False +def send_toot(status, timestamp, host, token): + ''' + param1: boolean + param2: integer + param3: string + param4: string + return: boolean + ''' + msg = None + timeformat = '%d.%m.%Y %H:%M Uhr' + if status not in (True, False): + logging.error('Invalid status to toot') + return False + timestring = strftime(timeformat, localtime(timestamp)) + + logging.debug('Try to toot status to {}'.format(host)) + if status == True: + msg = 'The krautspace is open since: {}'.format(timestring) + elif status == False: + msg = 'The krautspace is closed since: {}'.format(timestring) + logging.debug('Send message: {}'.format(msg)) + try: + mastodon = Mastodon(api_base_url = host, + access_token = token) + mastodon.toot(mag) + except Exception as e: + logging.error('Failed to toot status') + return False + return False + + def change_status(status, timestamp, filename): ''' Write the new status together with a timestamp into the Space API JSON. @@ -169,7 +200,7 @@ def get_status_and_time(raw_data): Create a timestamp, changes the value of the given byte into a string and returns both. param 1: byte object - return: tuple + return: tuple (boolean, integer) ''' status = True if raw_data.decode('utf-8', 'strict') == '1' else False timestamp = int(str(time()).split('.')[0]) @@ -302,7 +333,9 @@ def main(): if change_status(status, timestamp, config['api']['api']) is True: answer = raw_data if config['mastodon']['send'].lower() == 'true': - logging.debug('Try to toot status') + send_toot(status, timestamp, + config['mastodon']['host'], + config['mastodon']['token']) else: logging.debug('Toot is set to false') if conn: logging.debug('Send {} back'.format(raw_data)) -- 2.39.5 From c7fc0b9eff256300191565c5e380b2322042f9d6 Mon Sep 17 00:00:00 2001 From: +++ Date: Tue, 12 Jul 2022 21:57:20 +0200 Subject: [PATCH 23/33] angefangen toot in thread auszulagern --- source/server/apistatusd.py | 95 ++++++++++++++++++++++++------------- 1 file changed, 61 insertions(+), 34 deletions(-) diff --git a/source/server/apistatusd.py b/source/server/apistatusd.py index 0cae07f..c4ece63 100755 --- a/source/server/apistatusd.py +++ b/source/server/apistatusd.py @@ -14,11 +14,69 @@ import os import socket import ssl import sys +import threading from mastodon import Mastodon from time import time, localtime, strftime, sleep import configparser +class Toot(threading.Thread): + ''' + The thread to toot the status to mastodon. + ''' + def __init__(self, status, timestamp, config): + ''' + param1: boolean + param2: integer + param3: dictionary + ''' + threading.Thread.__init__(self) + self.status = status + self.config = config + self.timestamp = timestamp + self.mastodon = Mastodon(api_base_url = self.config['mastodon']['host'], + access_token = self.config['mastodon']['token']) + + def run(self): + ''' + return: boolean + send_toot(status, timestamp, + config['mastodon']['host'], + config['mastodon']['token']) + ''' + msg = None + timeformat = '%d.%m.%Y %H:%M Uhr' + timestring = strftime(timeformat, localtime(self.timestamp)) + + if self.status not in (True, False): + logging.error('Invalid status to toot') + timestring = strftime(timeformat, localtime(self.timestamp)) + + logging.debug('Try to toot status to {}'.format(host)) + if self.status == True: + msg = 'The krautspace is open since: {}'.format(timestring) + elif self.status == False: + msg = 'The krautspace is closed since: {}'.format(timestring) + logging.debug('Send message: {}'.format(msg)) + try: + mastodon = Mastodon(api_base_url = host, + access_token = token) + mastodon.toot(mag) + except Exception as e: + logging.error('Failed to toot status') + return False + return False + + + def send_toot(self): + ''' + Starts the thread + ''' + send_toot(status, timestamp, + config['mastodon']['host'], + config['mastodon']['token']) + + def certs_readable(config): ''' checks at start, if the needed certificates defined (no nullstring) and @@ -103,37 +161,6 @@ def receive_buffer_is_valid(raw_data): return False -def send_toot(status, timestamp, host, token): - ''' - param1: boolean - param2: integer - param3: string - param4: string - return: boolean - ''' - msg = None - timeformat = '%d.%m.%Y %H:%M Uhr' - if status not in (True, False): - logging.error('Invalid status to toot') - return False - timestring = strftime(timeformat, localtime(timestamp)) - - logging.debug('Try to toot status to {}'.format(host)) - if status == True: - msg = 'The krautspace is open since: {}'.format(timestring) - elif status == False: - msg = 'The krautspace is closed since: {}'.format(timestring) - logging.debug('Send message: {}'.format(msg)) - try: - mastodon = Mastodon(api_base_url = host, - access_token = token) - mastodon.toot(mag) - except Exception as e: - logging.error('Failed to toot status') - return False - return False - - def change_status(status, timestamp, filename): ''' Write the new status together with a timestamp into the Space API JSON. @@ -333,9 +360,9 @@ def main(): if change_status(status, timestamp, config['api']['api']) is True: answer = raw_data if config['mastodon']['send'].lower() == 'true': - send_toot(status, timestamp, - config['mastodon']['host'], - config['mastodon']['token']) + toot_threat = Toot(status, timestamp, config) + toot_thread.run() + logging.debug('Toot thread started') else: logging.debug('Toot is set to false') if conn: logging.debug('Send {} back'.format(raw_data)) -- 2.39.5 From 666a997a90af229c92200be861f0e8ec1a0fef65 Mon Sep 17 00:00:00 2001 From: +++ Date: Wed, 13 Jul 2022 19:10:17 +0200 Subject: [PATCH 24/33] erste version eines toots --- source/server/apistatusd.py | 56 ++++++++++++++++++++----------------- 1 file changed, 31 insertions(+), 25 deletions(-) diff --git a/source/server/apistatusd.py b/source/server/apistatusd.py index c4ece63..eb59dc3 100755 --- a/source/server/apistatusd.py +++ b/source/server/apistatusd.py @@ -20,6 +20,14 @@ from time import time, localtime, strftime, sleep import configparser +class InitException(Exception): + ''' + If the initialisation from the mastodon instance failes then we raise + this exception. + ''' + def __init__(self, error): + self.error = error + class Toot(threading.Thread): ''' The thread to toot the status to mastodon. @@ -34,49 +42,43 @@ class Toot(threading.Thread): self.status = status self.config = config self.timestamp = timestamp - self.mastodon = Mastodon(api_base_url = self.config['mastodon']['host'], - access_token = self.config['mastodon']['token']) - + try: + self.mastodon = Mastodon( + api_base_url = self.config['mastodon']['host'], + access_token = self.config['mastodon']['token']) + except Exception as e: + logging.error('Exception occurred: {}'.format(e)) + raise InitException('Mastodon instance initialisation failed') + def run(self): ''' return: boolean - send_toot(status, timestamp, - config['mastodon']['host'], - config['mastodon']['token']) ''' msg = None timeformat = '%d.%m.%Y %H:%M Uhr' - timestring = strftime(timeformat, localtime(self.timestamp)) - if self.status not in (True, False): logging.error('Invalid status to toot') - timestring = strftime(timeformat, localtime(self.timestamp)) - - logging.debug('Try to toot status to {}'.format(host)) + return False + try: + timestring = strftime(timeformat, localtime(self.timestamp)) + except Exception as e: + logging.error('Can not convert timestamp into timestring') + return False + logging.debug('Try to toot status to {}'.format(self.config['mastodon']['host'])) if self.status == True: msg = 'The krautspace is open since: {}'.format(timestring) elif self.status == False: msg = 'The krautspace is closed since: {}'.format(timestring) logging.debug('Send message: {}'.format(msg)) try: - mastodon = Mastodon(api_base_url = host, - access_token = token) mastodon.toot(mag) + return True except Exception as e: logging.error('Failed to toot status') return False return False - def send_toot(self): - ''' - Starts the thread - ''' - send_toot(status, timestamp, - config['mastodon']['host'], - config['mastodon']['token']) - - def certs_readable(config): ''' checks at start, if the needed certificates defined (no nullstring) and @@ -360,9 +362,13 @@ def main(): if change_status(status, timestamp, config['api']['api']) is True: answer = raw_data if config['mastodon']['send'].lower() == 'true': - toot_threat = Toot(status, timestamp, config) - toot_thread.run() - logging.debug('Toot thread started') + try: + toot_thread = Toot(status, timestamp, config) + toot_thread.run() + except InitException as e: + logging.debug('InitException: {}'.format(e)) + except Exception as ex: + logging.debug('Exception: {}'.format(ex)) else: logging.debug('Toot is set to false') if conn: logging.debug('Send {} back'.format(raw_data)) -- 2.39.5 From 0eba169038c70b2a2a965a444697e9c645df2b59 Mon Sep 17 00:00:00 2001 From: +++ Date: Wed, 13 Jul 2022 22:10:07 +0200 Subject: [PATCH 25/33] kleine aenderung im logging --- source/server/apistatusd.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/server/apistatusd.py b/source/server/apistatusd.py index eb59dc3..c0d6e00 100755 --- a/source/server/apistatusd.py +++ b/source/server/apistatusd.py @@ -362,11 +362,12 @@ def main(): if change_status(status, timestamp, config['api']['api']) is True: answer = raw_data if config['mastodon']['send'].lower() == 'true': + logging.debug('Toot is set to true') try: toot_thread = Toot(status, timestamp, config) toot_thread.run() except InitException as e: - logging.debug('InitException: {}'.format(e)) + logging.error('InitException: {}'.format(e)) except Exception as ex: logging.debug('Exception: {}'.format(ex)) else: logging.debug('Toot is set to false') -- 2.39.5 From 9fe94d7e6e4ba5f4fb45b6cb4485cbd44cdb5c3b Mon Sep 17 00:00:00 2001 From: +++ Date: Thu, 14 Jul 2022 21:47:16 +0200 Subject: [PATCH 26/33] umstellung auf requests modul mastodon gegen requests getauscht, import exceptions eingefuegt, InitException wieder entfernt --- source/server/apistatusd.py | 173 ++++++++++++++++++++---------------- 1 file changed, 95 insertions(+), 78 deletions(-) diff --git a/source/server/apistatusd.py b/source/server/apistatusd.py index c0d6e00..461cad6 100755 --- a/source/server/apistatusd.py +++ b/source/server/apistatusd.py @@ -2,81 +2,25 @@ # file: apistatusd.py # date: 26.07.2019 -# email: berhsi@web.de +# mail: berhsi@web.de # Status server, listening for door status updates. The IPv4 address and port # to listen on are configurable, by default localhost:10001 is used. The # connection is secured by TLS and client side authentication. -import json -import logging -import os -import socket -import ssl -import sys -import threading -from mastodon import Mastodon -from time import time, localtime, strftime, sleep -import configparser - - -class InitException(Exception): - ''' - If the initialisation from the mastodon instance failes then we raise - this exception. - ''' - def __init__(self, error): - self.error = error - -class Toot(threading.Thread): - ''' - The thread to toot the status to mastodon. - ''' - def __init__(self, status, timestamp, config): - ''' - param1: boolean - param2: integer - param3: dictionary - ''' - threading.Thread.__init__(self) - self.status = status - self.config = config - self.timestamp = timestamp - try: - self.mastodon = Mastodon( - api_base_url = self.config['mastodon']['host'], - access_token = self.config['mastodon']['token']) - except Exception as e: - logging.error('Exception occurred: {}'.format(e)) - raise InitException('Mastodon instance initialisation failed') - - def run(self): - ''' - return: boolean - ''' - msg = None - timeformat = '%d.%m.%Y %H:%M Uhr' - if self.status not in (True, False): - logging.error('Invalid status to toot') - return False - try: - timestring = strftime(timeformat, localtime(self.timestamp)) - except Exception as e: - logging.error('Can not convert timestamp into timestring') - return False - logging.debug('Try to toot status to {}'.format(self.config['mastodon']['host'])) - if self.status == True: - msg = 'The krautspace is open since: {}'.format(timestring) - elif self.status == False: - msg = 'The krautspace is closed since: {}'.format(timestring) - logging.debug('Send message: {}'.format(msg)) - try: - mastodon.toot(mag) - return True - except Exception as e: - logging.error('Failed to toot status') - return False - return False +try: + import json + import logging + import os + import socket + import ssl + import sys + import requests + import threading + from time import time, localtime, strftime, sleep + import configparser +except ImportException as e: + print('Import error: {}'.format(e)) def certs_readable(config): @@ -93,7 +37,6 @@ def certs_readable(config): return False return True - def print_config(config): ''' Logs the config with level debug. @@ -102,7 +45,10 @@ def print_config(config): for section in config.sections(): logging.debug('Section {}'.format(section)) for i in config[section]: - logging.debug(' {}: {}'.format(i, config[section][i])) + if i == 'token': + logging.debug(' {}: {}'.format(i, 'aaaaa-bbbbb-ccccc-ddddd-eeeee')) + else: + logging.debug(' {}: {}'.format(i, config[section][i])) def print_ciphers(cipherlist): ''' @@ -162,7 +108,6 @@ def receive_buffer_is_valid(raw_data): logging.debug('Argument is not valid: {}'.format(raw_data)) return False - def change_status(status, timestamp, filename): ''' Write the new status together with a timestamp into the Space API JSON. @@ -170,7 +115,6 @@ def change_status(status, timestamp, filename): param 2: string return: boolean ''' - logging.debug('Change status API') # todo: use walrus operator := when migrating to python >= 3.8 data = read_api(filename) @@ -196,7 +140,6 @@ def change_status(status, timestamp, filename): logging.info('API file successfull changed to {}'.format(status)) return True - def read_api(api): ''' Reads the Space API JSON into a dict. Returns the dict on success and @@ -206,7 +149,6 @@ def read_api(api): return: dict or boolean ''' logging.debug('Open API file: {}'.format(api)) - # return early if the API JSON cannot be read if not os.access(api, os.R_OK): logging.error('Failed to read API file') @@ -223,7 +165,6 @@ def read_api(api): return False return api_json_data - def get_status_and_time(raw_data): ''' Create a timestamp, changes the value of the given byte into a string @@ -238,6 +179,83 @@ def get_status_and_time(raw_data): str(timestamp), str(status))) return (status, timestamp) +def join_path(host, api): + ''' + Becomes two parts (host and api) of the mastodon url and concanate them + param1: string + param2: string + return: string + ''' + url = '' + try: + if host[-1] == '/' and api[0] == '/': + url = ''.join((host, api[1:])) + elif host[-1] != '/' and api[0] != '/': + url = '/'.join((host, api)) + else: + url = ''.join((host, api)) + except TypeError as e: + logging.error('Can´t join path: {}'.format(e)) + return url + +class Toot(threading.Thread): + ''' + The thread to toot the status to mastodon. + ''' + def __init__(self, status, timestamp, config): + ''' + param1: boolean + param2: integer + param3: dictionary + ''' + threading.Thread.__init__(self) + self.status = status + self.config = config + self.timestamp = timestamp + self.api = '/api/v1/statuses' + self.auth = {'Authorization': ''} + self.data = {'status': ''} + self.url = '' + + def run(self): + ''' + return: boolean + ''' + timeformat = '%d.%m.%Y %H:%M Uhr' + # check if status is valid + if self.status not in (True, False): + logging.error('Invalid status to toot') + return False + # convert seconds into timestring + try: + timestring = strftime(timeformat, localtime(self.timestamp)) + except Exception as e: + logging.error('Can not convert timestamp into timestring') + return False + # set status message + if self.status == True: + self.data['status'] = 'Krautspace is open since: {}'.format(timestring) + elif self.status == False: + self.data['status'] = 'Krautspace is closed since: {}'.format(timestring) + logging.debug('Message: {}'.format(self.data['status'])) + # build mastodon api url + self.url = join_path(self.config['mastodon']['host'], self.api) + # build authentcation header + self.auth['Authorization'] = 'Bearer {}'.format( + self.config['mastodon']['token']) + # and finaly send request to mastodon + try: + logging.debug('Try to toot status') + response = requests.post(self.url, data = self.data, + headers = self.auth) + if response.status_code == 200: + logging.info('Toot successful send') + return True + else: + logging.error('Failed to toot. Response: {}'.format(response.status_code)) + except Exception as e: + logging.error('Exception occurred: {}'.format(e)) + return False def main(): ''' @@ -248,7 +266,6 @@ def main(): OP_DONT_ISERT_EMPTY_FRAGMENTS: prevention agains CBC 4 attack (cve-2011-3389) ''' - answer = '3'.encode(encoding='utf-8', errors='strict') loglevel = logging.WARNING formatstring = '%(asctime)s: %(levelname)s: %(message)s' -- 2.39.5 From c7bd0eafffb054c30c2bd9f0fc2633fd46acdddd Mon Sep 17 00:00:00 2001 From: +++ Date: Mon, 18 Jul 2022 20:15:25 +0200 Subject: [PATCH 27/33] default loglevel auf info gesetzt --- source/server/apistatusd.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/source/server/apistatusd.py b/source/server/apistatusd.py index 461cad6..2b0bbe8 100755 --- a/source/server/apistatusd.py +++ b/source/server/apistatusd.py @@ -267,14 +267,14 @@ def main(): (cve-2011-3389) ''' answer = '3'.encode(encoding='utf-8', errors='strict') - loglevel = logging.WARNING + loglevel = logging.INFO formatstring = '%(asctime)s: %(levelname)s: %(message)s' logging.basicConfig(format=formatstring, level=loglevel) default_config = { 'general': { 'timeout': 3.0, - 'loglevel': 'warning' + 'loglevel': 'info' }, 'server': { 'host': 'localhost', @@ -295,6 +295,7 @@ def main(): 'token': 'aaaaa-bbbbb-ccccc-ddddd-eeeee' } } + logging.info('Try to read config file') configfile = './apistatusd.conf' config = configparser.ConfigParser() config.read_dict(default_config) @@ -309,6 +310,7 @@ def main(): default_config['general']['loglevel']) config.set('general', 'loglevel', default_config['general']['loglevel']) + logging.info('Set loglevel to {}'.format(config['general']['loglevel'].upper())) logger.setLevel(config['general']['loglevel'].upper()) print_config(config) -- 2.39.5 From ef3981fe66b798b6df1343241d83aae8bf934cb8 Mon Sep 17 00:00:00 2001 From: example Date: Sat, 23 Jul 2022 01:42:10 +0200 Subject: [PATCH 28/33] funktion main() ueberarbeitet --- source/server/apistatusd.py | 118 ++++++++++++++++-------------------- 1 file changed, 51 insertions(+), 67 deletions(-) diff --git a/source/server/apistatusd.py b/source/server/apistatusd.py index 2b0bbe8..4a75570 100755 --- a/source/server/apistatusd.py +++ b/source/server/apistatusd.py @@ -331,76 +331,60 @@ def main(): logging.debug('SSL context created') print_context(context) - with socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) as mySocket: - logging.debug('Socket created') - mySocket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) - keep = mySocket.getsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE) - logging.debug('Socket keepalive: {}'.format(keep)) - try: - mySocket.bind((config['server']['host'], int(config['server']['port']))) - mySocket.listen(5) - except Exception as e: - logging.error('Unable to bind and listen') - logging.error('{}'.format(e)) - sys.exit(1) - logging.info('Listening on {} at Port {}'.format(config['server']['host'], - config['server']['port'])) - - while True: + try: + # normalen socket öffnen => MySocket + with socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) as MySocket: + logging.debug('Socket created') + MySocket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) + keep = MySocket.getsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE) + logging.debug('Socket keepalive: {}'.format(keep)) try: - fromSocket, fromAddr = mySocket.accept() - logging.info('Client connected: {}:{}'.format(fromAddr[0], fromAddr[1])) - try: - fromSocket.settimeout(float(config['general']['timeout'])) - logging.debug('Connection timeout set to {}'.format( - config['general']['timeout'])) - except Exception: - logging.error('Cannot set timeout to {}'.format( - config['general']['timeout'])) - try: - conn = context.wrap_socket(fromSocket, server_side=True) - conn.settimeout(float(config['general']['timeout'])) - except socket.timeout: - logging.error('Socket timeout') - continue - except Exception as e: - logging.error('Connection failed: {}'.format(e)) - continue - logging.info('Connection established') - try: - cert = conn.getpeercert(binary_form=False) - display_peercert(cert) - except ValueError: - logging.debug('SSL handshake has not been done yet') - except Exception as e: - logging.debug('Unexpected error: {}'.format(e)) - - raw_data = conn.recv(1) - if receive_buffer_is_valid(raw_data) is True: - status, timestamp = get_status_and_time(raw_data) - if change_status(status, timestamp, config['api']['api']) is True: - answer = raw_data - if config['mastodon']['send'].lower() == 'true': - logging.debug('Toot is set to true') - try: - toot_thread = Toot(status, timestamp, config) - toot_thread.run() - except InitException as e: - logging.error('InitException: {}'.format(e)) - except Exception as ex: - logging.debug('Exception: {}'.format(ex)) - else: logging.debug('Toot is set to false') - if conn: - logging.debug('Send {} back'.format(raw_data)) - conn.send(answer) - sleep(0.1) # protection against dos - except KeyboardInterrupt: - logging.info('Keyboard interrupt received') - sys.exit(1) + MySocket.bind((config['server']['host'], int(config['server']['port']))) + MySocket.listen(5) + logging.info('Listening on {} at Port {}'.format(config['server']['host'], + config['server']['port'])) except Exception as e: + logging.error('Unable to bind and listen') logging.error('{}'.format(e)) - continue - return 0 + sys.exit(1) + # den socket in den ssl-context einwickeln => MySecSocket + with context.wrap_socket(MySocket, server_side=True) as MySecSocket: + while True: + connection, remote = MySecSocket.accept() + logging.info('Client connected: {}:{}'.format(remote[0], remote[1])) + connection.settimeout(float(config['general']['timeout'])) + # die bestehende connection + with connection: + try: + cert = connection.getpeercert(binary_form=False) + display_peercert(cert) + except ValueError: + logging.debug('SSL handshake has not been done yet') + except Exception as e: + logging.debug('Unexpected error: {}'.format(e)) + + raw_data = connection.recv(1) + if receive_buffer_is_valid(raw_data) is True: + status, timestamp = get_status_and_time(raw_data) + if change_status(status, timestamp, config['api']['api']) is True: + answer = raw_data + if config['mastodon']['send'].lower() == 'true': + logging.debug('Toot is set to true') + try: + toot_thread = Toot(status, timestamp, config) + toot_thread.run() + except InitException as e: + logging.error('InitException: {}'.format(e)) + except Exception as ex: + logging.debug('Exception: {}'.format(ex)) + else: logging.debug('Toot is set to false') + logging.debug('Send {} back'.format(raw_data)) + connection.send(answer) + except KeyboardInterrupt: + logging.info('Keyboard interrupt received') + sys.exit(1) + except Exception as e: + logging.error('{}'.format(e)) if __name__ == '__main__': -- 2.39.5 From 7dd6dbab122d1250c038c9e4ad3506798acc0cc1 Mon Sep 17 00:00:00 2001 From: example Date: Sat, 30 Jul 2022 10:05:32 +0200 Subject: [PATCH 29/33] schweren fehler in main() beseitigt, finally klausel hinzu, SO_REUSEADDR hinzu --- source/server/apistatusd.py | 76 ++++++++++++++++++++----------------- 1 file changed, 41 insertions(+), 35 deletions(-) diff --git a/source/server/apistatusd.py b/source/server/apistatusd.py index 4a75570..aff8193 100755 --- a/source/server/apistatusd.py +++ b/source/server/apistatusd.py @@ -332,9 +332,10 @@ def main(): print_context(context) try: - # normalen socket öffnen => MySocket + # tcp socket öffnen => MySocket with socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) as MySocket: - logging.debug('Socket created') + logging.debug('TCP Socket created') + MySocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) MySocket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) keep = MySocket.getsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE) logging.debug('Socket keepalive: {}'.format(keep)) @@ -347,44 +348,49 @@ def main(): logging.error('Unable to bind and listen') logging.error('{}'.format(e)) sys.exit(1) - # den socket in den ssl-context einwickeln => MySecSocket - with context.wrap_socket(MySocket, server_side=True) as MySecSocket: - while True: - connection, remote = MySecSocket.accept() - logging.info('Client connected: {}:{}'.format(remote[0], remote[1])) - connection.settimeout(float(config['general']['timeout'])) - # die bestehende connection - with connection: - try: - cert = connection.getpeercert(binary_form=False) - display_peercert(cert) - except ValueError: - logging.debug('SSL handshake has not been done yet') - except Exception as e: - logging.debug('Unexpected error: {}'.format(e)) - - raw_data = connection.recv(1) - if receive_buffer_is_valid(raw_data) is True: - status, timestamp = get_status_and_time(raw_data) - if change_status(status, timestamp, config['api']['api']) is True: - answer = raw_data - if config['mastodon']['send'].lower() == 'true': - logging.debug('Toot is set to true') - try: - toot_thread = Toot(status, timestamp, config) - toot_thread.run() - except InitException as e: - logging.error('InitException: {}'.format(e)) - except Exception as ex: - logging.debug('Exception: {}'.format(ex)) - else: logging.debug('Toot is set to false') - logging.debug('Send {} back'.format(raw_data)) - connection.send(answer) + # endlos auf verbindungen warten => ClientSocket + while True: + ClientSocket, ClientAddress = MySocket.accept() + logging.info('Client connected: {}:{}'.format(ClientAddress[0], ClientAddress[1])) + # die verbindung in den ssl-context verpacken => Connection + with context.wrap_socket(ClientSocket, server_side=True) as Connection: + logging.info('SSL Connection established') + try: + Connection.settimeout(float(config['general']['timeout'])) + logging.debug('Connection timeout set to {}'.format(config['general']['timeout'])) + cert = Connection.getpeercert(binary_form=False) + display_peercert(cert) + except Exception as e: + logging.error('Unexpected error: {}'.format(e)) + continue + # empfangen und antworten + raw_data = Connection.recv(1) + if receive_buffer_is_valid(raw_data) is True: + status, timestamp = get_status_and_time(raw_data) + if change_status(status, timestamp, config['api']['api']) is True: + answer = raw_data + if config['mastodon']['send'].lower() == 'true': + logging.debug('Toot is set to true') + try: + toot_thread = Toot(status, timestamp, config) + toot_thread.run() + except InitException as e: + logging.error('InitException: {}'.format(e)) + except Exception as ex: + logging.debug('Exception: {}'.format(ex)) + else: logging.debug('Toot is set to false') + logging.debug('Send {} back'.format(raw_data)) + Connection.send(answer) + Connection.close() except KeyboardInterrupt: logging.info('Keyboard interrupt received') sys.exit(1) except Exception as e: logging.error('{}'.format(e)) + finally: + if MySocket: + MySocket.close() + logging.debug('TCP socket closed') if __name__ == '__main__': -- 2.39.5 From aed3616cf8626828f5d7f6cba7794a2b43581f62 Mon Sep 17 00:00:00 2001 From: example Date: Sat, 30 Jul 2022 10:32:03 +0200 Subject: [PATCH 30/33] kleine aenderungen der logmessages --- source/server/apistatusd.py | 68 ++++++++++++++++++------------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/source/server/apistatusd.py b/source/server/apistatusd.py index aff8193..4fe1eda 100755 --- a/source/server/apistatusd.py +++ b/source/server/apistatusd.py @@ -108,38 +108,6 @@ def receive_buffer_is_valid(raw_data): logging.debug('Argument is not valid: {}'.format(raw_data)) return False -def change_status(status, timestamp, filename): - ''' - Write the new status together with a timestamp into the Space API JSON. - param 1: byte object - param 2: string - return: boolean - ''' - logging.debug('Change status API') - # todo: use walrus operator := when migrating to python >= 3.8 - data = read_api(filename) - if data is False: - return False - - if os.access(filename, os.W_OK): - logging.debug('API file is writable') - with open(filename, 'w') as api_file: - logging.debug('API file open successfull') - data["state"]["open"] = status - data["state"]["lastchange"] = timestamp - try: - json.dump(data, api_file, indent=4) - except Exception as e: - logging.error('Failed to change API file') - logging.error('{}'.format(e)) - return False - logging.debug('API file changed') - else: - logging.error('API file is not writable. Wrong permissions?') - return False - logging.info('API file successfull changed to {}'.format(status)) - return True - def read_api(api): ''' Reads the Space API JSON into a dict. Returns the dict on success and @@ -156,15 +124,47 @@ def read_api(api): logging.debug('API is readable') with open(api, 'r') as api_file: - logging.debug('API file successfully opened') + logging.debug('API file successfully readable opened') try: api_json_data = json.load(api_file) - logging.debug('API file read successfull') + logging.debug('API file successfully read') except Exception as e: logging.error('Failed to read API file: {}'.format(e)) return False return api_json_data +def change_status(status, timestamp, filename): + ''' + Write the new status together with a timestamp into the Space API JSON. + param 1: byte object + param 2: string + return: boolean + ''' + logging.debug('Change status API') + # todo: use walrus operator := when migrating to python >= 3.8 + data = read_api(filename) + if data is False: + return False + + if os.access(filename, os.W_OK): + logging.debug('API file is writable') + with open(filename, 'w') as api_file: + logging.debug('API file successfull writable opened') + data["state"]["open"] = status + data["state"]["lastchange"] = timestamp + try: + json.dump(data, api_file, indent=4) + except Exception as e: + logging.error('Failed to change API file') + logging.error('{}'.format(e)) + return False + logging.debug('API file changed') + else: + logging.error('API file is not writable. Wrong permissions?') + return False + logging.info('API file successfull changed to {}'.format(status)) + return True + def get_status_and_time(raw_data): ''' Create a timestamp, changes the value of the given byte into a string -- 2.39.5 From 991eeea9f8f13717fc1c57870d0b0f5fca2fd716 Mon Sep 17 00:00:00 2001 From: example Date: Sat, 30 Jul 2022 12:09:38 +0200 Subject: [PATCH 31/33] verify_mode ueber config setzbar, ssl context in funktion ausgelagert --- source/server/apistatusd.conf | 4 ++- source/server/apistatusd.py | 52 ++++++++++++++++++++++++++--------- 2 files changed, 42 insertions(+), 14 deletions(-) diff --git a/source/server/apistatusd.conf b/source/server/apistatusd.conf index 177f44a..601204d 100644 --- a/source/server/apistatusd.conf +++ b/source/server/apistatusd.conf @@ -19,13 +19,15 @@ key = ./certs/statusd-key.pem [client] cert = ./certs/statusclient-pub.pem +# possible values: true, false, may +required = true [api] api = ./api template = ./api_template [mastodon] -send = true +send = false host = localhost token = aaaaa-bbbbb-ccccc-ddddd-eeeee diff --git a/source/server/apistatusd.py b/source/server/apistatusd.py index 4fe1eda..f8e3355 100755 --- a/source/server/apistatusd.py +++ b/source/server/apistatusd.py @@ -50,6 +50,36 @@ def print_config(config): else: logging.debug(' {}: {}'.format(i, config[section][i])) +def create_ssl_context(config): + ''' + Creates the ssl context. + return: context object or None + ''' + context = None + requirement = None + required = config['client']['required'].lower() + if required == 'false': + requirement = ssl.CERT_NONE + elif required == 'may': + requirement = ssl.CERT_OPTIONAL + else: requirement = ssl.CERT_REQUIRED + try: + context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) + context.verify_mode = requirement + context.load_cert_chain(certfile=config['server']['cert'], + keyfile=config['server']['key']) + context.load_verify_locations(cafile=config['client']['cert']) + # ensure, compression is disabled (disabled by default anyway at the moment) + context.options |= ssl.OP_NO_COMPRESSION + context.options = ssl.PROTOCOL_TLS_SERVER + context.options = ssl.OP_CIPHER_SERVER_PREFERENCE + logging.debug('SSL context created') + except Exception as e: + logging.error('Failed to create SSL context') + logging.error('Error: {}'.format(e)) + return None + return context + def print_ciphers(cipherlist): ''' Prints the list of allowed ciphers. @@ -283,7 +313,8 @@ def main(): 'key': './certs/server.key' }, 'client': { - 'cert': './certs/client.crt' + 'cert': './certs/client.crt', + 'required': 'true' }, 'api': { 'api': './api', @@ -320,16 +351,11 @@ def main(): logging.error('Cert check failed\nExit') sys.exit(1) - context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) - context.verify_mode = ssl.CERT_OPTIONAL - context.load_cert_chain(certfile=config['server']['cert'], - keyfile=config['server']['key']) - context.load_verify_locations(cafile=config['client']['cert']) - context.options = ssl.OP_CIPHER_SERVER_PREFERENCE - # ensure, compression is disabled (disabled by default anyway at the moment) - context.options |= ssl.OP_NO_COMPRESSION - logging.debug('SSL context created') - print_context(context) + # ssl context erstellen + context = create_ssl_context(config) + if context is not None: + print_context(context) + else: sys.exit(2) try: # tcp socket öffnen => MySocket @@ -347,7 +373,7 @@ def main(): except Exception as e: logging.error('Unable to bind and listen') logging.error('{}'.format(e)) - sys.exit(1) + sys.exit(3) # endlos auf verbindungen warten => ClientSocket while True: ClientSocket, ClientAddress = MySocket.accept() @@ -384,7 +410,7 @@ def main(): Connection.close() except KeyboardInterrupt: logging.info('Keyboard interrupt received') - sys.exit(1) + sys.exit(255) except Exception as e: logging.error('{}'.format(e)) finally: -- 2.39.5 From 6bd34360a6661b6ba26ac0ddabf2cf322302a408 Mon Sep 17 00:00:00 2001 From: example Date: Wed, 3 Aug 2022 17:58:27 +0200 Subject: [PATCH 32/33] secure socket wieder in try-except; finally-klauses wieder raus --- source/server/apistatusd.py | 67 +++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 32 deletions(-) diff --git a/source/server/apistatusd.py b/source/server/apistatusd.py index f8e3355..9a89b4a 100755 --- a/source/server/apistatusd.py +++ b/source/server/apistatusd.py @@ -379,44 +379,47 @@ def main(): ClientSocket, ClientAddress = MySocket.accept() logging.info('Client connected: {}:{}'.format(ClientAddress[0], ClientAddress[1])) # die verbindung in den ssl-context verpacken => Connection - with context.wrap_socket(ClientSocket, server_side=True) as Connection: + try: + Connection = context.wrap_socket(ClientSocket, server_side=True) logging.info('SSL Connection established') - try: - Connection.settimeout(float(config['general']['timeout'])) - logging.debug('Connection timeout set to {}'.format(config['general']['timeout'])) - cert = Connection.getpeercert(binary_form=False) - display_peercert(cert) - except Exception as e: - logging.error('Unexpected error: {}'.format(e)) - continue - # empfangen und antworten - raw_data = Connection.recv(1) - if receive_buffer_is_valid(raw_data) is True: - status, timestamp = get_status_and_time(raw_data) - if change_status(status, timestamp, config['api']['api']) is True: - answer = raw_data - if config['mastodon']['send'].lower() == 'true': - logging.debug('Toot is set to true') - try: - toot_thread = Toot(status, timestamp, config) - toot_thread.run() - except InitException as e: - logging.error('InitException: {}'.format(e)) - except Exception as ex: - logging.debug('Exception: {}'.format(ex)) - else: logging.debug('Toot is set to false') - logging.debug('Send {} back'.format(raw_data)) - Connection.send(answer) - Connection.close() + Connection.settimeout(float(config['general']['timeout'])) + logging.debug('Connection timeout set to {}'.format(config['general']['timeout'])) + cert = Connection.getpeercert(binary_form=False) + display_peercert(cert) + except Exception as e: + logging.error('Unexpected error: {}'.format(e)) + continue + # empfangen und antworten + raw_data = Connection.recv(1) + if receive_buffer_is_valid(raw_data) is True: + status, timestamp = get_status_and_time(raw_data) + if change_status(status, timestamp, config['api']['api']) is True: + answer = raw_data + if config['mastodon']['send'].lower() == 'true': + logging.debug('Toot is set to true') + try: + toot_thread = Toot(status, timestamp, config) + toot_thread.run() + except InitException as e: + logging.error('InitException: {}'.format(e)) + except Exception as ex: + logging.debug('Exception: {}'.format(ex)) + else: logging.debug('Toot is set to false') + logging.debug('Send {} back'.format(raw_data)) + Connection.send(answer) + Connection.close() except KeyboardInterrupt: logging.info('Keyboard interrupt received') - sys.exit(255) - except Exception as e: - logging.error('{}'.format(e)) - finally: if MySocket: MySocket.close() logging.debug('TCP socket closed') + sys.exit(255) + except Exception as e: + logging.error('{}'.format(e)) + if MySocket: + MySocket.close() + logging.debug('TCP socket closed') + sys.exit(254) if __name__ == '__main__': -- 2.39.5 From 381bd390df27a57fedf240d6d4d4bfe4961e997d Mon Sep 17 00:00:00 2001 From: example Date: Wed, 31 Aug 2022 18:21:21 +0200 Subject: [PATCH 33/33] restart on failure --- source/server/apistatusd.service | 1 + 1 file changed, 1 insertion(+) diff --git a/source/server/apistatusd.service b/source/server/apistatusd.service index a76f4dc..33111bc 100644 --- a/source/server/apistatusd.service +++ b/source/server/apistatusd.service @@ -4,6 +4,7 @@ After=systemd-network.service network.target [Service] Type=simple +Restart=on-failure WorkingDirectory=/opt/doorstatus/ ExecStart=/opt/doorstatus/apistatusd.py SyslogIdentifier=doorstatus -- 2.39.5