diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 7274ad1..0000000 --- a/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/documentation/build/ -/source/arduino/build/ diff --git a/README.md b/README.md index 863398e..ec54111 100644 --- a/README.md +++ b/README.md @@ -21,126 +21,24 @@ includes a field indicating whether the space is open or closed. ## Arduino -### 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 +## Python Script +## 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 preambles of each -source file. +`LICENSE.AGPL`). The respective authors are listed in the 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 SAMD Boards* published by the copyright holders under the LGPL 2.1 or later. +_Arduino Core_ 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. -*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 +_Library for Arduino WiFi Shield_ published by the copyright holders under the LGPL 2.1 or later. diff --git a/documentation/Makefile b/documentation/Makefile deleted file mode 100644 index f4a2f05..0000000 --- a/documentation/Makefile +++ /dev/null @@ -1,33 +0,0 @@ -# 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 deleted file mode 100644 index efa3f65..0000000 Binary files a/documentation/arduino.png and /dev/null differ diff --git a/documentation/arduino.tex b/documentation/arduino.tex deleted file mode 100644 index 0ddf92f..0000000 --- a/documentation/arduino.tex +++ /dev/null @@ -1,74 +0,0 @@ -% 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} -\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 deleted file mode 100755 index f0cc5a6..0000000 --- a/scripts/test_udp_api.py +++ /dev/null @@ -1,81 +0,0 @@ -#!/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. -""" - -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 deleted file mode 100644 index 0f54ea4..0000000 --- a/source/arduino/Makefile +++ /dev/null @@ -1,33 +0,0 @@ -# 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 deleted file mode 100644 index f6a3477..0000000 --- a/source/arduino/arduino.ino +++ /dev/null @@ -1,70 +0,0 @@ -/* -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 2cb04dc..49db287 100644 --- a/source/arduino/config.h +++ b/source/arduino/config.h @@ -19,15 +19,14 @@ 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. -const char SSID[] = ""; -const char PASSWORD[] = ""; +char SSID[] = ""; +char PASSWORD[] = ""; + // Port on which to listen for status requests -const unsigned int SERVER_PORT = 12345; +unsigned int PORT = 12345; // Pin to which the reed switch is connected -const uint8_t SENSOR_PIN = 0; +uint8_t REED_PIN = 0; diff --git a/source/arduino/wifi.cpp b/source/arduino/door_status.ino similarity index 52% rename from source/arduino/wifi.cpp rename to source/arduino/door_status.ino index a604d12..fd81a16 100644 --- a/source/arduino/wifi.cpp +++ b/source/arduino/door_status.ino @@ -25,43 +25,84 @@ with the Clean CommonMark library. If not, see . #include "config.h" +uint8_t OPEN = 1; +uint8_t CLOSED = 0; + +int wifi_status = WL_IDLE_STATUS; + WiFiUDP Udp; -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); -} +void setup() { + pinMode(REED_PIN, INPUT_PULLUP); + + Serial.begin(9600); + while (!Serial); -boolean wifi_setup() { if (WiFi.status() == WL_NO_SHIELD) { Serial.println("No WiFI shield present"); - return false; + // TODO: Create noShieldLoop with visual indication. + while (true); } - 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 (WiFi.begin(SSID, PASSWORD) == WL_CONNECTED) { + if (status == WL_CONNECTED) { break; } + // TODO: Visually indicate that we waiting for trying to connect again delay(10000); } - Serial.println("Connect to WiFi"); - Serial.print("IP Address: "); - Serial.println(WiFi.localIP()); + 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.print("IP Address: "); + Serial.println(ip); + + 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/matrix.cpp b/source/arduino/matrix.cpp deleted file mode 100644 index 38f9ace..0000000 --- a/source/arduino/matrix.cpp +++ /dev/null @@ -1,87 +0,0 @@ -/* -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 deleted file mode 100644 index 0b4029f..0000000 --- a/source/arduino/matrix.h +++ /dev/null @@ -1,33 +0,0 @@ -/* -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 deleted file mode 100644 index 173b04a..0000000 --- a/source/arduino/sensor.cpp +++ /dev/null @@ -1,35 +0,0 @@ -/* -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 deleted file mode 100644 index e6e5d4b..0000000 --- a/source/arduino/sensor.h +++ /dev/null @@ -1,29 +0,0 @@ -/* -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 deleted file mode 100644 index d394e4d..0000000 --- a/source/arduino/serial.cpp +++ /dev/null @@ -1,26 +0,0 @@ -/* -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 deleted file mode 100644 index 4e9ed58..0000000 --- a/source/arduino/serial.h +++ /dev/null @@ -1,22 +0,0 @@ -/* -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/wifi.h b/source/arduino/wifi.h deleted file mode 100644 index 17185b6..0000000 --- a/source/arduino/wifi.h +++ /dev/null @@ -1,27 +0,0 @@ -/* -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(); diff --git a/source/nodemcu/statusclient/certs.template b/source/nodemcu/statusclient/certs.template deleted file mode 100644 index 07679bb..0000000 --- a/source/nodemcu/statusclient/certs.template +++ /dev/null @@ -1,36 +0,0 @@ -/* - * 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 deleted file mode 100644 index b078888..0000000 --- a/source/nodemcu/statusclient/config.h +++ /dev/null @@ -1,18 +0,0 @@ -/* - * 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 5000 - -/* 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 deleted file mode 100644 index 2ba711d..0000000 --- a/source/nodemcu/statusclient/credentials.template +++ /dev/null @@ -1,17 +0,0 @@ -/* - * 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 deleted file mode 100644 index 5faaa2e..0000000 --- a/source/nodemcu/statusclient/statusclient.ino +++ /dev/null @@ -1,260 +0,0 @@ -/* - * 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; - -BearSSL::WiFiClientSecure client; - -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, HIGH); - Serial.println("[Pin] LED and REED initialized"); -} - -void init_wifi() { - /* - * 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, 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()); - set_clock(); - } else { - Serial.println("[Wifi] Error: Failed to connect"); - signal_wifi_failed(); - } -} - -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 signal_door_changed() { - /* - * LED signal, if door is opened - */ - uint8_t count = 2; - for(uint8_t i=0; i!= count; ++i) { - digitalWrite(LED_PIN, LOW); - delay(100); - digitalWrite(LED_PIN, HIGH); - delay(100); - } -} - -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); - - 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) { - - /* - * Inits wifi (if needed) and send the status - */ - char status[2] = ""; - - if (state == DOOR_CLOSED) { - strncpy(status, "0", 1); - } else if (state == DOOR_OPEN) { - strncpy(status, "1", 1); - } else { - return 1; - } - - 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); - Serial.println("[Ctx] SSL Context initialized"); - 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 { - ESP.resetFreeContStack(); - uint32_t freeStackStart = ESP.getFreeContStack(); - Serial.println("[Send] Connection successful established"); - Serial.printf("[Send] Send status: %s\n", status); - client.write(status); - signal_send_successful(); - client.stop(); - } - return 0; -} - - -void setup() { - - /* - * things to do once at boot time - */ - init_serial(); - init_pins(); - init_wifi(); -} - -void loop() { - - /* - * things are running in a endless loop - */ - 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(); - if (send_status(new_door_state) == 0) { - current_door_state = new_door_state; - } - } - delay(FREQUENCY); -} diff --git a/source/server/apistatusd.conf b/source/server/apistatusd.conf index 601204d..73433e8 100644 --- a/source/server/apistatusd.conf +++ b/source/server/apistatusd.conf @@ -14,20 +14,12 @@ loglevel = debug [server] host = localhost port = 10001 -cert = ./certs/statusd-pub.pem -key = ./certs/statusd-key.pem +cert = ./certs/server-pub.pem +key = ./certs/server-key.pem [client] -cert = ./certs/statusclient-pub.pem -# possible values: true, false, may -required = true +cert = ./certs/client-pub.pem [api] api = ./api template = ./api_template - -[mastodon] -send = false -host = localhost -token = aaaaa-bbbbb-ccccc-ddddd-eeeee - diff --git a/source/server/apistatusd.py b/source/server/apistatusd.py index 9a89b4a..eff1010 100755 --- a/source/server/apistatusd.py +++ b/source/server/apistatusd.py @@ -1,26 +1,21 @@ #!/usr/bin/python3 -# file: apistatusd.py +# file: statusd.py # date: 26.07.2019 -# mail: berhsi@web.de +# email: 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. -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)) +import json +import logging +import os +import socket +import ssl +import sys +from time import time, sleep +import configparser def certs_readable(config): @@ -37,6 +32,7 @@ def certs_readable(config): return False return True + def print_config(config): ''' Logs the config with level debug. @@ -45,40 +41,8 @@ def print_config(config): for section in config.sections(): logging.debug('Section {}'.format(section)) for i in config[section]: - if i == 'token': - logging.debug(' {}: {}'.format(i, 'aaaaa-bbbbb-ccccc-ddddd-eeeee')) - else: - logging.debug(' {}: {}'.format(i, config[section][i])) + 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): ''' @@ -86,58 +50,77 @@ def print_ciphers(cipherlist): param1: dictionary return: boolean ''' - logging.debug('Available ciphers') + print('Available ciphers') for i in cipherlist: + print('\n') for j in i.keys(): - if j in ('name', 'protocol'): - logging.debug('{}: {}'.format(j, i[j])) + print('{}: {}'.format(j, i[j])) + print('\n') -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 or none + param1: dictionary + return: boolean ''' - 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'])) + 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])) + def receive_buffer_is_valid(raw_data): ''' Checks validity of the received buffer contents. - param 1: byte object + param 1: byte return: boolean ''' - if raw_data.decode('utf-8', 'strict') in ('0', '1'): + if raw_data in (b'\x00', b'\x01'): logging.debug('Argument is valid: {}'.format(raw_data)) return True + logging.debug('Argument is not valid: {}'.format(raw_data)) return False + +def change_status(raw_data, api): + ''' + Write the new status together with a timestamp into the Space API JSON. + param 1: byte + param 2: string + return: boolean + ''' + + logging.debug('Change status API') + # todo: use walrus operator := when migrating to python >= 3.8 + data = read_api(api) + if data is False: + return False + + status, timestamp = set_values(raw_data) + if os.access(api, os.W_OK): + logging.debug('API file is writable') + with open(api, '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)) + logging.debug('API file changed') + else: + logging.error('API file is not writable. Wrong permissions?') + return False + logging.info('Status 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 @@ -147,6 +130,7 @@ 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') @@ -154,138 +138,30 @@ def read_api(api): logging.debug('API is readable') with open(api, 'r') as api_file: - logging.debug('API file successfully readable opened') + logging.debug('API file successfully opened') try: api_json_data = json.load(api_file) - logging.debug('API file successfully read') + logging.debug('API file read successfull') 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): +def set_values(raw_data): ''' Create a timestamp, changes the value of the given byte into a string and returns both. - param 1: byte object - return: tuple (boolean, integer) + param 1: byte + return: tuple ''' - status = True if raw_data.decode('utf-8', 'strict') == '1' else False + status = True if raw_data == b'\x01' else False timestamp = int(str(time()).split('.')[0]) logging.debug('Set values for timestamp: {} and status: {}'.format( 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(): ''' @@ -296,15 +172,15 @@ 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.INFO + + loglevel = logging.WARNING formatstring = '%(asctime)s: %(levelname)s: %(message)s' logging.basicConfig(format=formatstring, level=loglevel) default_config = { 'general': { 'timeout': 3.0, - 'loglevel': 'info' + 'loglevel': 'warning' }, 'server': { 'host': 'localhost', @@ -313,21 +189,14 @@ def main(): 'key': './certs/server.key' }, 'client': { - 'cert': './certs/client.crt', - 'required': 'true' + 'cert': './certs/client.crt' }, 'api': { 'api': './api', 'template': './api_template' - }, - 'mastodon': { - 'send': 'false', - 'host': 'localhost', - 'token': 'aaaaa-bbbbb-ccccc-ddddd-eeeee' } } - logging.info('Try to read config file') - configfile = './apistatusd.conf' + configfile = './statusd.conf' config = configparser.ConfigParser() config.read_dict(default_config) if not config.read(configfile): @@ -341,7 +210,6 @@ 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) @@ -351,75 +219,88 @@ def main(): logging.error('Cert check failed\nExit') sys.exit(1) - # ssl context erstellen - context = create_ssl_context(config) - if context is not None: - print_context(context) - else: sys.exit(2) + context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) + context.verify_mode = ssl.CERT_REQUIRED + 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()) - try: - # tcp socket öffnen => MySocket - with socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) as MySocket: - 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)) + 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: - 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)) - sys.exit(3) - # 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 + fromSocket, fromAddr = mySocket.accept() + logging.info('Client connected: {}:{}'.format(fromAddr[0], fromAddr[1])) try: - Connection = context.wrap_socket(ClientSocket, server_side=True) - logging.info('SSL Connection established') - 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) + 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') except Exception as e: - logging.error('Unexpected error: {}'.format(e)) - continue - # empfangen und antworten - raw_data = Connection.recv(1) + logging.error('Connection failed: {}'.format(e)) + 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'])) + + 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') - logging.debug('Send {} back'.format(raw_data)) - Connection.send(answer) - Connection.close() - except KeyboardInterrupt: - logging.info('Keyboard interrupt received') - 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 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') + sleep(0.1) # protection against dos + except KeyboardInterrupt: + logging.info('Keyboard interrupt received') + sys.exit(1) + 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 if __name__ == '__main__': diff --git a/source/server/apistatusd.service b/source/server/apistatusd.service index 33111bc..a76f4dc 100644 --- a/source/server/apistatusd.service +++ b/source/server/apistatusd.service @@ -4,7 +4,6 @@ After=systemd-network.service network.target [Service] Type=simple -Restart=on-failure WorkingDirectory=/opt/doorstatus/ ExecStart=/opt/doorstatus/apistatusd.py SyslogIdentifier=doorstatus diff --git a/source/server/setstatus.py b/source/server/setstatus.py index e6fccfa..f62a2e3 100755 --- a/source/server/setstatus.py +++ b/source/server/setstatus.py @@ -9,7 +9,8 @@ # krautspaces doorstatus. # client, who connects to the statusserver at port 10001 to update the -# krautspace door status. +# krautspace door status. If no status is given as argument, he reads from +# stdin until input is 0 or 1. import os import ssl @@ -57,19 +58,16 @@ class SetStatus: def check_status(self): """ - checkes, if the self.status variable is a valid value return: boolean """ - if self.status in ('0', '1'): - self.log.debug('Set status to {}'.format(self.status)) + if self.status in (0, 1): + self.log.debug('Set value to {}'.format(self.status)) + self.status = bytes([self.status]) return True - self.log.debug('{} is not a valid status'.format(self.status)) return False 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 @@ -91,8 +89,7 @@ class SetStatus: def check_certs(self, certs): """ - Check if certs are readable. - return: boolean + Check if certs readable. """ self.log.debug('Check certificates') for certfile in certs: @@ -114,28 +111,25 @@ class SetStatus: def create_ssl_context(self): """ - Creates SSL context - return: context object or false """ - try: - context = ssl.create_default_context(purpose=ssl.Purpose.SERVER_AUTH) - except Exception as e: + context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH, + cafile=self.config['server']['cert']) + if not context: 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: @@ -198,6 +192,7 @@ 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 @@ -216,20 +211,18 @@ class SetStatus: if self.context is False: exit(3) - # get a ssl encrypted connection + # get connection self.connection = self.create_ssl_connection() # send status try: self.log.debug('Send new status: {}'.format(self.status)) - self.connection.send(self.status.encode(encoding='utf-8', - errors='strict')) + self.connection.send(self.status) except Exception as e: self.log.error('Error: {}'.format(e)) exit(6) try: - response = self.connection.recv(1).decode(encoding='utf-8', - errors='strict') + response = self.connection.recv(1) self.log.debug('Server returns: {}'.format(response)) if response == self.status: self.log.info('Status sucessfull updated') @@ -240,12 +233,3 @@ 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]) -