\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 2000\,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 (2000 mAh LiPo)}

Bei $\approx$1\,mA Durchschnittsstrom (DTIM=10, selten gedrückt) und 80\,\% nutzbarer Kapazität:
\[
t = \frac{1600~\mathrm{mAh}}{1~\mathrm{mA}} \approx 67\text{--}80~\mathrm{Tage}
\]

Die \textbf{Latenz von 250\,ms} hat Vorrang vor maximaler Akkulaufzeit -- Deep Sleep scheidet
daher aus (WiFi-Reconnect 600--1300\,ms). Mit 2000\,mAh und WiFi Light Sleep sind
ca.\ 2,5 Monate Betrieb ohne Laden realistisch.

% -----------------------------------------------
\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{Akku-Spezifikation}

Für den FireBeetle\,2 wird ein \textbf{1S LiPo mit JST-PH-2,0-Stecker und BMS} benötigt:

\begin{center}
\begin{tabular}{ll}
\toprule
Merkmal & Wert \\
\midrule
Typ & LiPo / Li-Polymer, 1 Zelle (1S) \\
Nennspannung & 3{,}7\,V (voll: 4{,}2\,V) \\
Stecker & JST PH, 2{,}0\,mm Raster, 2-polig \\
Schutzschaltung & Ja (BMS/PCM) -- Pflicht bei Deep-Sleep-Betrieb \\
Kapazität & 2000\,mAh \\
\bottomrule
\end{tabular}
\end{center}

\textbf{Achtung Polarität:} JST-PH-Stecker sind nicht normiert -- Polung vor dem
Einstecken mit Multimeter prüfen. Sicherste Quelle: Akku direkt bei DFRobot kaufen.

\subsection{Schaltung}

\begin{lstlisting}
ESP32         Taster
GPIO 9  ---- [Taster] ---- GND
             (interner Pull-Up aktiv: HIGH = offen, LOW = gedrueckt)

LiPo 2000mAh (JST PH 2.0, mit BMS) ---- 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}
