diff --git a/EmergencyStopButton/EmergencyStopButton.ino b/EmergencyStopButton/EmergencyStopButton.ino new file mode 100644 index 0000000..efd274c --- /dev/null +++ b/EmergencyStopButton/EmergencyStopButton.ino @@ -0,0 +1,89 @@ +// EmergencyStopButton.ino +// ESP32 – WiFi Light Sleep, wacht per GPIO-Interrupt auf Knopfdruck auf +// und sendet sofort einen API-Call. Ziel: <250ms von Knopfdruck bis API. + +#include +#include +#include "esp_wifi.h" +#include "esp_sleep.h" +#include "driver/gpio.h" + +// ── Konfiguration ──────────────────────────────────────────────────────────── +#define BUTTON_PIN 9 // GPIO-Pin des Tasters (gegen GND) +#define WIFI_SSID "DEIN_SSID" +#define WIFI_PASSWORD "DEIN_PASSWORT" +#define API_URL "https://deine-api.example.com/emergency-stop" +#define API_TOKEN "DEIN_API_TOKEN" +// ───────────────────────────────────────────────────────────────────────────── + +void connectWiFi() { + WiFi.mode(WIFI_STA); + WiFi.begin(WIFI_SSID, WIFI_PASSWORD); + Serial.print("WiFi verbinden"); + while (WiFi.status() != WL_CONNECTED) { + delay(100); + Serial.print("."); + } + Serial.printf("\nVerbunden. IP: %s\n", WiFi.localIP().toString().c_str()); +} + +void sendEmergencyStop() { + if (WiFi.status() != WL_CONNECTED) { + Serial.println("WiFi nicht verbunden – API-Call abgebrochen"); + return; + } + + HTTPClient http; + http.begin(API_URL); + http.addHeader("Content-Type", "application/json"); + http.addHeader("Authorization", "Bearer " API_TOKEN); + http.setTimeout(3000); + + String body = "{\"event\":\"emergency_stop\",\"device\":\"esp32-estop\"}"; + int httpCode = http.POST(body); + + if (httpCode > 0) { + Serial.printf("API Response: %d\n", httpCode); + } else { + Serial.printf("HTTP Fehler: %s\n", http.errorToString(httpCode).c_str()); + } + http.end(); +} + +void enterLightSleep() { + // Wakeup bei LOW-Pegel (Taster gegen GND, Pull-Up aktiv) + gpio_wakeup_enable((gpio_num_t)BUTTON_PIN, GPIO_INTR_LOW_LEVEL); + esp_sleep_enable_gpio_wakeup(); + esp_light_sleep_start(); + // Ab hier läuft der Code weiter, sobald der Taster gedrückt wird +} + +void setup() { + Serial.begin(115200); + + pinMode(BUTTON_PIN, INPUT_PULLUP); + + connectWiFi(); + + // DTIM=10: ESP32 wacht alle ~1000ms kurz für Beacon auf → ~0.5–1 mA + esp_wifi_set_ps(WIFI_PS_MAX_MODEM); + + Serial.println("Bereit. Warte auf Knopfdruck..."); +} + +void loop() { + enterLightSleep(); + + // Wakeup → Taster prüfen (Low-aktiv wegen Pull-Up) + if (digitalRead(BUTTON_PIN) == LOW) { + unsigned long t0 = millis(); + sendEmergencyStop(); + Serial.printf("Latenz API-Call: %lu ms\n", millis() - t0); + + // Warten bis Taster losgelassen (Entprellung) + while (digitalRead(BUTTON_PIN) == LOW) { + delay(10); + } + delay(50); + } +} diff --git a/EmergencyStopButton/eStopESP32.pdf b/EmergencyStopButton/eStopESP32.pdf new file mode 100644 index 0000000..9ea87b1 Binary files /dev/null and b/EmergencyStopButton/eStopESP32.pdf differ diff --git a/EmergencyStopButton/eStopESP32.tex b/EmergencyStopButton/eStopESP32.tex new file mode 100644 index 0000000..809c9aa --- /dev/null +++ b/EmergencyStopButton/eStopESP32.tex @@ -0,0 +1,237 @@ +\documentclass[a4paper,11pt]{article} +\usepackage[utf8]{inputenc} +\usepackage[T1]{fontenc} +\usepackage[ngerman]{babel} +\usepackage{geometry} +\usepackage{amsmath} +\usepackage{booktabs} +\usepackage{listings} +\usepackage{xcolor} +\usepackage[unicode=true]{hyperref} +\usepackage{enumitem} + + +\lstset{ + basicstyle=\ttfamily\small, + keywordstyle=\color{blue}, + commentstyle=\color{gray}, + stringstyle=\color{orange}, + backgroundcolor=\color{gray!10}, + frame=single, + breaklines=true, + columns=fullflexible +} + +\title{\textbf{Emergency Stop Button -- ESP32}\\ + \large Technische Dokumentation} +\author{} +\date{Juni 2026} + +\begin{document} +\maketitle +\tableofcontents +\newpage + +% ----------------------------------------------- +\section{Ziel und Anforderungen} + +Ein physischer Notaus-Taster soll beim Drücken so schnell wie möglich einen +API-Call absetzen. Der ESP32 befindet sich im Ruhezustand und wird durch den +Tastendruck geweckt. + +\begin{itemize} + \item Latenz Knopfdruck $\rightarrow$ API-Call: \textbf{< 250\,ms} + \item Stromversorgung: LiPo 1000\,mAh (kabellos, batteriebetrieben) + \item Möglichst lange Akkulaufzeit (Taster wird selten gedrückt, $\leq$1\,×/h) + \item Einfache, wartungsarme Architektur +\end{itemize} + +% ----------------------------------------------- +\section{Architekturentscheidung} + +\subsection{Bewertete Optionen} + +\begin{center} +\begin{tabular}{lllll} +\toprule +Option & Latenz & Ø Strom & Gateway nötig & Komplexität \\ +\midrule +Deep Sleep + WiFi (RTC-Memory) & 600--1300\,ms & $\approx$0{,}02\,mA & nein & niedrig \\ +\textbf{WiFi Light Sleep (DTIM=10)} & \textbf{150--250\,ms} & \textbf{0{,}5--1\,mA} & \textbf{nein} & \textbf{niedrig} \\ +BLE Light Sleep + Gateway & 100--300\,ms & 0{,}5--2\,mA & ja & hoch \\ +\bottomrule +\end{tabular} +\end{center} + +\subsection{Entscheidung: WiFi Light Sleep} + +WiFi Light Sleep erfüllt alle Anforderungen ohne zusätzliche Infrastruktur: +\begin{itemize} + \item Der ESP32 hält die WiFi-Verbindung aufrecht und schläft nur die CPU + \item Ein GPIO-Interrupt weckt den ESP32 sofort beim Tastendruck + \item Der API-Call geht direkt vom ESP32 -- kein Container, kein Gateway +\end{itemize} + +\textbf{Warum nicht BLE?} Bluetooth LE wäre minimal sparsamer +($\approx$0{,}5\,mA vs. $\approx$1\,mA), erfordert aber ein dauerhaft laufendes +Gateway-Gerät (Raspberry Pi, PC), das seinerseits Strom verbraucht und eine +Fehlerquelle darstellt. + +\textbf{Warum nicht Deep Sleep?} Deep Sleep braucht beim Aufwachen +600--1300\,ms (WiFi-Reconnect), was die Latenzanforderung von 250\,ms +verfehlt. + +% ----------------------------------------------- +\section{WiFi Light Sleep -- Funktionsprinzip} + +Im WiFi Light Sleep (Modem Sleep) schläft die CPU, während der WiFi-Stack +aktiv bleibt. Der Router sendet alle 100\,ms einen Beacon; mit DTIM=10 wacht +der ESP32 automatisch alle $\approx$1000\,ms für 1--2\,ms auf, um gepufferte +Pakete abzuholen. Die Verbindungsassoziation bleibt dabei erhalten. + +\subsection{DTIM-Einstellung und Stromverbrauch} + +\begin{center} +\begin{tabular}{lll} +\toprule +DTIM & Wakeup-Intervall & Ø Strom \\ +\midrule +1 & 100\,ms & 5--10\,mA \\ +3 & 300\,ms & 2--4\,mA \\ +10 & 1000\,ms & 0{,}5--1\,mA \\ +\bottomrule +\end{tabular} +\end{center} + +Empfohlen: \texttt{DTIM=10} -- sparsamste Option, Verbindung bleibt stabil. + +\subsection{Akkulaufzeit (1000 mAh LiPo)} + +Bei $\approx$1\,mA Durchschnittsstrom (DTIM=10, selten gedrückt): +\[ +t = \frac{1000~\mathrm{mAh}}{1~\mathrm{mA}} \approx 40~\mathrm{Tage} +\] + +% ----------------------------------------------- +\section{Latenzbudget} + +\begin{center} +\begin{tabular}{ll} +\toprule +Schritt & Zeit \\ +\midrule +ESP32 Wakeup aus Light Sleep & 1--5\,ms \\ +WiFi-Verbindung prüfen (bereits aktiv) & 0\,ms \\ +HTTP-Request aufbauen & 20--50\,ms \\ +TLS-Handshake (HTTPS) & 50--150\,ms \\ +Server-Antwort & 20--50\,ms \\ +\midrule +\textbf{Gesamt} & \textbf{$\approx$100--250\,ms} \\ +\bottomrule +\end{tabular} +\end{center} + +% ----------------------------------------------- +\section{Hardware} + +\subsection{Empfohlene Boards} + +\begin{center} +\begin{tabular}{llll} +\toprule +Board & Lader & Preis & Bemerkung \\ +\midrule +\textbf{FireBeetle ESP32-E} (DFRobot) & integriert & 8--12\,€ & Low-Power optimiert \\ +LOLIN D32 (Wemos) & TP4054 & 5--8\,€ & günstig, bewährt \\ +Adafruit HUZZAH32 Feather & MCP73831 & $\approx$20\,€ & gute Dokumentation \\ +\bottomrule +\end{tabular} +\end{center} + +\subsection{Schaltung} + +\begin{lstlisting} +ESP32 Taster +GPIO 9 ---- [Taster] ---- GND + (interner Pull-Up aktiv: HIGH = offen, LOW = gedrueckt) + +LiPo 1000mAh ---- BAT+ / BAT- des Boards +\end{lstlisting} + +Kein weiteres Bauteil nötig -- der interne Pull-Up des ESP32 reicht. + +% ----------------------------------------------- +\section{Software} + +\subsection{Abhängigkeiten (Arduino IDE)} + +\begin{itemize} + \item Board-Package: \texttt{esp32} by Espressif (Boards Manager) + \item Bibliotheken: \texttt{WiFi.h}, \texttt{HTTPClient.h} (im esp32-Package enthalten) +\end{itemize} + +\subsection{Konfiguration in \texttt{EmergencyStopButton.ino}} + +\begin{lstlisting}[language=C] +#define BUTTON_PIN 9 +#define WIFI_SSID "DEIN_SSID" +#define WIFI_PASSWORD "DEIN_PASSWORT" +#define API_URL "https://deine-api.example.com/emergency-stop" +#define API_TOKEN "DEIN_API_TOKEN" +\end{lstlisting} + +\subsection{Ablauf} + +\begin{enumerate} + \item \texttt{setup()}: WiFi verbinden, \texttt{WIFI\_PS\_MAX\_MODEM} aktivieren + \item \texttt{loop()}: \texttt{esp\_light\_sleep\_start()} -- CPU schläft, WiFi aktiv + \item Tastendruck löst GPIO-Interrupt aus -- ESP32 wacht auf + \item \texttt{sendEmergencyStop()} sendet HTTP-POST an API + \item Warten bis Taster losgelassen, zurück zu Schritt 2 +\end{enumerate} + +\subsection{Kritische API-Funktion} + +\begin{lstlisting}[language=C] +void sendEmergencyStop() { + HTTPClient http; + http.begin(API_URL); + http.addHeader("Content-Type", "application/json"); + http.addHeader("Authorization", "Bearer " API_TOKEN); + http.setTimeout(3000); + String body = "{\"event\":\"emergency_stop\",\"device\":\"esp32-estop\"}"; + int httpCode = http.POST(body); + http.end(); +} +\end{lstlisting} + +% ----------------------------------------------- +\section{Deployment-Hinweise} + +\begin{itemize} + \item \textbf{HTTPS}: TLS-Handshake kostet 50--150\,ms. Falls die Latenz + kritisch ist und das Netz vertrauenswürdig, kann HTTP verwendet werden + (dann $\approx$50--100\,ms Gesamt). + \item \textbf{DTIM am Router}: Manche Router ignorieren den DTIM-Wunsch des + Clients. Im Zweifelsfall DTIM=3 am Router einstellen. + \item \textbf{API-Token}: Nicht im Quellcode einchecken -- z.\,B. in eine + separate \texttt{secrets.h} auslagern, die im \texttt{.gitignore} steht. + \item \textbf{Watchdog}: Bei produktivem Einsatz einen Hardware-Watchdog + aktivieren, damit der ESP32 sich bei WiFi-Verlust selbst neu startet. +\end{itemize} + +% ----------------------------------------------- +\section{Dateien} + +\begin{center} +\begin{tabular}{ll} +\toprule +Datei & Beschreibung \\ +\midrule +\texttt{EmergencyStopButton.ino} & Arduino-Sketch (Hauptprogramm) \\ +\texttt{eStopESP32.tex} & Diese Dokumentation \\ +\bottomrule +\end{tabular} +\end{center} + +\end{document}