Added the webserver and the wifi handling

This commit is contained in:
2026-03-24 15:03:02 +01:00
parent 8bebe5228b
commit 67f2baabbb
14 changed files with 543 additions and 39 deletions

377
PROJEKT.md Normal file
View File

@@ -0,0 +1,377 @@
# Raspberry Pi Pico W Sensor Projekt
## Regeln
- Claude erklärt Konzepte und gibt Hinweise, aber **kein fertiger Code**
- Ich schreibe den Code selbst
- Bei Fehlern analysieren wir gemeinsam
- Ausnahme: Setup und Boilerplate darf vorgegeben werden
---
## Hardware
| Komponente | Protokoll | Misst |
|---|---|---|
| Raspberry Pi Pico W | | Mikrocontroller mit WLAN |
| BME280 (GY-BME280) | I2C (Adresse 0x76) | Temperatur + Luftdruck + Luftfeuchtigkeit |
> AM2302 und DS18B20 werden nicht verwendet BME280 liefert alle benötigten Messwerte.
---
## Entwicklungsumgebung
### System
- CachyOS (Arch-basiert)
- VS Code
### Installierte Tools
```bash
sudo pacman -S arm-none-eabi-gcc arm-none-eabi-newlib cmake ninja git python
sudo pacman -S minicom
```
### VS Code Extensions
- C/C++ (ms-vscode.cpptools)
- CMake Tools (ms-vscode.cmake-tools)
- CMake (twxs.cmake)
- Serial Monitor (ms-vscode.vscode-serial-monitor)
- Raspberry Pi Pico (raspberry-pi.raspberry-pi-pico)
### Serieller Monitor
- Port: `/dev/ttyACM0`
- Baud Rate: `115200`
- Gruppe für Zugriff: `uucp`
```bash
sudo usermod -aG uucp $USER
# Dann neu einloggen!
```
---
## Projekt Struktur
Das Repo heißt `sensor-pico` und ist gleichzeitig der Root keine extra Ebene darüber.
```
sensor-pico/ ← Git Repo Root
├── pico-sdk/ ← Git Submodule (Raspberry Pi Pico SDK)
├── lib/
│ ├── bme280/ ← Git Submodule (lafftale1999/bme280_driver)
│ └── dhcp_server/ ← DHCP Server (von pico-examples, kein Submodule)
├── src/
│ ├── main.cpp ← Hauptprogramm
│ ├── webserver.c ← lwIP httpd Initialisierung + CGI-Handler
│ └── fsdata.c ← Generiert von makefsdata, nicht im Git!
├── fs/
│ └── index.html ← Weboberfläche, wird beim Build eingebaut
├── build/ ← Von cmake generiert, nicht im Git
├── CMakeLists.txt
├── lwipopts.h ← lwIP Konfiguration
└── prepend_include.cmake ← Hilfsskript für makefsdata-Build-Schritt
```
### Git Submodule
```
[submodule "lib/bme280"]
path = lib/bme280
url = git@github.com:lafftale1999/bme280_driver.git
[submodule "pico-sdk"]
path = pico-sdk
url = https://github.com/raspberrypi/pico-sdk.git
```
### .gitignore
```
build/
.vscode/
CMakeCache.txt
CMakeFiles/
cmake_install.cmake
Makefile
*.uf2
*.elf
*.bin
*.hex
*.map
*.dis
__pycache__/
*.pyc
src/fsdata.c
```
> `src/fsdata.c` wird beim Build automatisch generiert und gehört nicht ins Repo.
---
## CMakeLists.txt (aktueller Stand)
```cmake
cmake_minimum_required(VERSION 3.13)
set(PICO_BOARD pico_w)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(PICO_SDK_PATH ${CMAKE_SOURCE_DIR}/pico-sdk)
include(${PICO_SDK_PATH}/external/pico_sdk_import.cmake)
project(sensor-pico C CXX ASM)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
pico_sdk_init()
add_subdirectory(lib/bme280 bme280_build)
add_subdirectory(lib/dhcp_server dhcp_server_build)
# makefsdata: fs/index.html → src/fsdata.c
add_custom_command(
OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/src/fsdata.c
COMMAND perl ${PICO_SDK_PATH}/lib/lwip/src/apps/http/makefsdata/makefsdata
COMMAND ${CMAKE_COMMAND} -E rename fsdata.c src/fsdata.c
COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_SOURCE_DIR}/prepend_include.cmake
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
DEPENDS ${CMAKE_SOURCE_DIR}/fs/index.html
)
add_executable(sensor-pico
src/main.cpp
src/webserver.c
src/fsdata.c
)
pico_enable_stdio_usb(sensor-pico 1)
pico_enable_stdio_uart(sensor-pico 0)
target_include_directories(sensor-pico PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
)
target_link_libraries(sensor-pico
pico_stdlib
pico_cyw43_arch_lwip_threadsafe_background
pico_lwip
pico_lwip_http
bme280
dhcp_server
)
pico_add_extra_outputs(sensor-pico)
```
---
## prepend_include.cmake
Dieses Script wird von CMake als Build-Schritt ausgeführt und fügt den fehlenden `#include` an den Anfang der generierten `fsdata.c` ein. Nötig weil das alte Perl-Script von lwIP keinen Header einfügt.
```cmake
file(READ "src/fsdata.c" CONTENT)
file(WRITE "src/fsdata.c" "#include \"lwip/apps/fs.h\"\n${CONTENT}")
```
> Warum nicht direkt Perl dafür? CMake-Scripts haben kein Shell-Quoting-Problem `file(READ/WRITE)` arbeitet direkt mit dem Dateisystem, ohne Shell-Umweg.
---
## Build & Flash Workflow
```bash
# Erstes Setup (nur einmal)
mkdir build
# Bauen
cd build
cmake .. -DPICO_BOARD=pico_w # PICO_BOARD muss explizit angegeben werden!
make -j$(nproc)
# Was passiert beim Build automatisch:
# 1. makefsdata liest fs/index.html
# 2. Generiert fsdata.c mit HTML-Inhalt als Byte-Array
# 3. Verschiebt fsdata.c nach src/fsdata.c
# 4. prepend_include.cmake fügt #include "lwip/apps/fs.h" ein
# 5. fsdata.c wird mitkompiliert
# Flashen
# 1. BOOTSEL gedrückt halten
# 2. USB einstecken
# 3. Taste loslassen
# 4. RPI-RP2 erscheint als Laufwerk
cp sensor-pico.uf2 /run/media/$USER/RPI-RP2/
```
---
## Verkabelung
**I2C (BME280):**
```
VCC → 3.3V (Pin 36)
GND → GND
SDA → GPIO 14 (Pin 19) ← Vorgabe der bme280-Bibliothek (i2c1)
SCL → GPIO 15 (Pin 20) ← Vorgabe der bme280-Bibliothek (i2c1)
```
---
## BME280 Messwerte
Die Bibliothek gibt skalierte Integer zurück kein Floating Point, üblich auf Embedded-Systemen wegen Speicher und Geschwindigkeit.
| Wert | Skalierung | Beispiel | Umrechnung |
|---|---|---|---|
| Temperatur | × 100 | 2494 | `/ 100` → 24.94°C |
| Luftfeuchtigkeit | × 1024 | 30759 | `/ 1024` → 30.0% |
| Luftdruck | × 256 (Pa) | 26074767 | `/ 256 / 100` → 1018.54 hPa |
JSON-Ausgabe per `bme280_get_json(handle)`:
```json
{"temperature":2494,"humidity":30759,"pressure":26074767}
```
Die Umrechnung kann auf dem Empfänger (z.B. Home Assistant) gemacht werden der Pico sendet die Rohwerte.
Lesbare Ausgabe per printf möglich:
```c
printf("Temp: %d.%02d C\n", temperature / 100, temperature % 100);
```
---
## Netzwerk / Webserver
### Architektur
Der Pico hat zwei Betriebsmodi:
**Modus 1 Erststart / Konfiguration (Access Point)**
- Pico öffnet einen eigenen Access Point (`SensorAP`, WPA2)
- DHCP-Server läuft Clients bekommen automatisch eine IP (`192.168.4.x`)
- Pico ist erreichbar unter `192.168.4.1`
- Weboberfläche zeigt Konfigurationsformular
**Modus 2 Produktionsbetrieb (Station Mode)**
- Pico verbindet sich mit bestehendem WLAN (gespeicherte SSID + Passwort)
- Publisht Sensordaten per MQTT an Broker
- Webserver läuft weiterhin: Live-Daten + Einstellungen änderbar
### Aktueller Stand
- ✅ Access Point (`SensorAP`, WPA2) öffnet sich
- ✅ DHCP-Server läuft Clients bekommen IP
- ✅ Pico erreichbar unter `192.168.4.1`
- ✅ lwIP httpd läuft Index-Seite wird ausgeliefert
-`makefsdata` automatisch im Build-Prozess eingebunden
-`fs/index.html` wird beim Build als Byte-Array in Firmware eingebaut
### Geplante Anforderungen
1. Beim Erststart: Access Point für Erstkonfiguration
2. Weboberfläche zum Einstellen von:
- WLAN SSID + Passwort
- MQTT Broker (Adresse, Port, User, Passwort)
- Sendefrequenz
3. Im Produktionsbetrieb: Live-Daten + Einstellungen änderbar
### makefsdata wie es funktioniert
`makefsdata` ist ein Perl-Script im lwIP-Quellcode:
```
pico-sdk/lib/lwip/src/apps/http/makefsdata/makefsdata
```
Es liest alle Dateien aus dem `fs/`-Ordner und erzeugt daraus eine `fsdata.c` mit dem Dateiinhalt als statische Byte-Arrays. lwIP httpd schaut zur Laufzeit in dieser Datei nach statt auf einem echten Dateisystem.
**Problem:** Das Script generiert `fsdata.c` ohne den nötigen `#include "lwip/apps/fs.h"` der Compiler kennt dann weder `struct fsdata_file` noch `FS_FILE_FLAGS_*`.
**Lösung:** `prepend_include.cmake` fügt den Include als CMake-Build-Schritt nach der Generierung ein.
### lwipopts.h
```c
#define NO_SYS 1
#define LWIP_SOCKET 0
#define LWIP_NETCONN 0
#define LWIP_IPV4 1
#define LWIP_TCP 1
#define LWIP_UDP 1
#define LWIP_DHCP 1
#define LWIP_DNS 1
#define LWIP_RAW 1
#define LWIP_ARP 1
#define LWIP_ETHERNET 1
#define LWIP_ICMP 1
#define LWIP_NETIF_HOSTNAME 1
#define LWIP_NETIF_STATUS_CALLBACK 1
#define MEM_ALIGNMENT 4
#define MEM_SIZE 4000
#define MEMP_NUM_TCP_SEG 32
#define PBUF_POOL_SIZE 24
#define TCP_MSS 1460
#define TCP_WND (8 * TCP_MSS)
#define TCP_SND_BUF (8 * TCP_MSS)
#define LWIP_HTTPD 1
#define LWIP_HTTPD_CGI 1
#define LWIP_HTTPD_SSI 1
```
---
## Nächste Schritte
- [x] `makefsdata` automatisch in cmake einbinden damit `fs/index.html` beim Build in die Firmware eingebaut wird
- [ ] Konfigurationsformular in `index.html` (WLAN + MQTT + Sendefrequenz)
- [ ] CGI-Handler in `webserver.c` für POST-Request vom Formular
- [ ] Einstellungen im Flash speichern
- [ ] WLAN Station Mode (Verbindung mit bestehendem Netz)
- [ ] MQTT einbinden
---
## Gelerntes
### Konzepte
- **I2C** Bus-Protokoll, alle Geräte an 2 Drähten (SDA + SCL), jedes Gerät hat eine Adresse
- **Hexadezimal** `0x` Prefix, 16 Ziffern (0-9, A-F)
- **ACK/NACK** Antwort/keine Antwort bei I2C Kommunikation
- **Handle** "Ticket" auf ein intern verwaltetes Objekt (Garderobe-Analogie)
- **Pull-up Widerstand** nötig bei I2C und OneWire
- **printf** Formatstring mit Platzhaltern (`%d`, `%s`, `%02X`), Zeilenumbruch mit `\n`
- **&variable** Adressoperator, gibt Speicheradresse zurück
- **uint8_t** 8-bit unsigned integer, genau 1 Byte
- **void** Rückgabetyp wenn Funktion nichts zurückgibt
- **Embedded** kein `std::cout`, `printf` bevorzugen wegen Speicher
- **IP-Adresse** eindeutige Adresse im Netzwerk, 4 Zahlen (0-255)
- **Netzmaske** definiert welcher Teil der IP das Netzwerk ist (`255.255.255.0`)
- **DHCP** verteilt automatisch IP-Adressen an Geräte im Netzwerk
- **lwIP** schlanker TCP/IP Stack für Embedded-Systeme
- **fsdata** HTML-Dateien werden beim Build als C-Bytes in die Firmware eingebaut
- **makefsdata** Perl-Script das `fs/`-Ordner in `fsdata.c` Byte-Array konvertiert
- **extern "C"** verhindert C++ Name Mangling bei C-Bibliotheken
- **Name Mangling** C++ kodiert Funktionsnamen mit Typinfo im Symbol (für Overloading); C nicht das verursacht Linker-Fehler wenn man C-Bibliotheken aus C++ nutzt ohne `extern "C"`
- **-j$(nproc)** paralleles Bauen mit allen CPU-Kernen
- **add_custom_command** CMake-Befehl um externe Programme als Build-Schritt auszuführen; mit `OUTPUT` weiß CMake welche Datei erzeugt wird und wann neu gebaut werden muss
- **${CMAKE_COMMAND} -E** plattformunabhängige CMake-Dateibefehle (rename, copy, remove) ohne Shell-Abhängigkeit
- **${CMAKE_COMMAND} -P** führt ein CMake-Script direkt aus, ohne Shell-Umweg und ohne Quoting-Probleme
- **GET vs POST** GET-Parameter landen in der URL (sichtbar, in Logs), POST-Daten im Body für Passwörter immer POST
### CMake-Konzepte
- **add_subdirectory** bindet einen Unterordner mit eigenem CMakeLists.txt ein; zweiter Parameter ist der Build-Unterordner
- **target_include_directories** sagt dem Compiler wo er Header-Dateien suchen soll; `PRIVATE` = nur für dieses Target
- **target_link_libraries** verknüpft Bibliotheken mit dem Executable
- **add_custom_command OUTPUT** definiert wie eine generierte Datei erzeugt wird; CMake führt den Befehl aus wenn die Datei fehlt oder Abhängigkeiten neuer sind
- **DEPENDS in add_custom_command** wenn diese Dateien sich ändern, wird der Befehl beim nächsten Build neu ausgeführt
### Pico W Besonderheiten
- Interne LED hängt am WLAN-Chip (CYW43), nicht an GPIO 25
- Braucht `pico_cyw43_arch_none` (ohne Netzwerk) oder `pico_cyw43_arch_lwip_threadsafe_background` (mit lwIP)
- `PICO_BOARD=pico_w` muss **vor** `pico_sdk_import.cmake` gesetzt werden
- `MEM_LIBC_MALLOC` ist inkompatibel mit `threadsafe_background`
- `lwipopts.h` muss selbst erstellt werden lwIP hat keine Defaults
- lwIP httpd CGI erwartet C-Funktionen in `.cpp`-Dateien mit `extern "C"` wrappen
### Bekannte Fallstricke
- `makefsdata` generiert `fsdata.c` ohne `#include "lwip/apps/fs.h"` → Compiler kennt `struct fsdata_file` nicht → `prepend_include.cmake` als Workaround
- Shell-Quoting in CMake `COMMAND` ist fehleranfällig bei komplexen Perl-Ausdrücken → CMake-Scripts (`-P`) verwenden statt Shell-Befehle
- `add_custom_command OUTPUT` mit relativem Pfad legt die Datei im Build-Verzeichnis ab, nicht im Source-Verzeichnis → immer absoluten Pfad mit `${CMAKE_CURRENT_SOURCE_DIR}` angeben