wenig
This commit is contained in:
89
EmergencyStopButton/EmergencyStopButton.ino
Normal file
89
EmergencyStopButton/EmergencyStopButton.ino
Normal file
@@ -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 <WiFi.h>
|
||||||
|
#include <HTTPClient.h>
|
||||||
|
#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);
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
EmergencyStopButton/eStopESP32.pdf
Normal file
BIN
EmergencyStopButton/eStopESP32.pdf
Normal file
Binary file not shown.
237
EmergencyStopButton/eStopESP32.tex
Normal file
237
EmergencyStopButton/eStopESP32.tex
Normal file
@@ -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}
|
||||||
Reference in New Issue
Block a user