got mqtt and wifi connection working reliably

This commit is contained in:
2026-03-27 16:33:49 +01:00
parent 5f49a5c1fe
commit 12873bae43
7 changed files with 139 additions and 30 deletions

View File

@@ -60,11 +60,15 @@ sensor-pico/ ← Git Repo Root
│ ├── 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
│ ├── main.cpp ← Hauptprogramm (WiFi + MQTT Verbindungslogik)
│ ├── webserver.c ← lwIP POST-Handler + Formular-Parsing
│ ├── webserver.h ← Exports: saved_ssid, saved_mqtt_* etc.
│ └── fsdata.c ← Generiert von makefsdata, nicht im Git!
├── fs/
── index.html ← Weboberfläche, wird beim Build eingebaut
── index.html ← Navigations-Hub (Links zu den Unterseiten)
│ ├── wlan_config.html ← WLAN SSID + Passwort Formular (POST /config)
│ ├── mqtt_config.html ← MQTT + Frequenz Formular (POST /mqtt-config)
│ └── live_stats.html ← (geplant) Live-Sensordaten
├── build/ ← Von cmake generiert, nicht im Git
├── CMakeLists.txt
├── lwipopts.h ← lwIP Konfiguration
@@ -125,7 +129,7 @@ pico_sdk_init()
add_subdirectory(lib/bme280 bme280_build)
add_subdirectory(lib/dhcp_server dhcp_server_build)
# makefsdata: fs/index.html → src/fsdata.c
# makefsdata: fs/ → src/fsdata.c (wird von fs.c via #include eingebunden)
add_custom_command(
OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/src/fsdata.c
COMMAND perl ${PICO_SDK_PATH}/lib/lwip/src/apps/http/makefsdata/makefsdata
@@ -133,12 +137,15 @@ add_custom_command(
COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_SOURCE_DIR}/prepend_include.cmake
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
DEPENDS ${CMAKE_SOURCE_DIR}/fs/index.html
${CMAKE_SOURCE_DIR}/fs/wlan_config.html
${CMAKE_SOURCE_DIR}/fs/mqtt_config.html
)
# fsdata.c wird NICHT in add_executable gelistet fs.c inkludiert sie via
# #include HTTPD_FSDATA_FILE (definiert in lwipopts.h als "src/fsdata.c")
add_executable(sensor-pico
src/main.cpp
src/webserver.c
src/fsdata.c
)
pico_enable_stdio_usb(sensor-pico 1)
@@ -153,6 +160,7 @@ target_link_libraries(sensor-pico
pico_cyw43_arch_lwip_threadsafe_background
pico_lwip
pico_lwip_http
pico_lwip_mqtt
bme280
dhcp_server
)
@@ -260,16 +268,36 @@ Der Pico hat zwei Betriebsmodi:
- ✅ 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
- ✅ lwIP httpd läuft Seiten werden ausgeliefert
-`makefsdata` automatisch im Build-Prozess eingebunden
-`fs/index.html` wird beim Build als Byte-Array in Firmware eingebaut
-Alle HTML-Seiten aus `fs/` werden als Byte-Array in Firmware eingebaut
- ✅ WLAN-Konfiguration über Captive Portal (POST Handler)
- ✅ Pico verbindet sich mit bestehendem WLAN (Station Mode)
- ✅ MQTT-Konfiguration über Webformular (Adresse, Port, User, Passwort, Frequenzen)
- ✅ MQTT-Verbindung zum Broker steht
- ✅ BME280 Sensor liest Messwerte aus und gibt JSON per printf aus
- ⬜ Sensordaten per MQTT publishen
- ⬜ Live-Daten Seite (`live_stats.html`)
### Programm-Ablauf (main.cpp)
```
1. cyw43_arch_init() + httpd_init()
2. WiFi-Schleife:
└─ ap_init() → warte auf SSID+Passwort → verbinde mit WLAN
└─ bei Erfolg: weiter; bei Fehler: neu versuchen
3. MQTT-Config-Schleife:
└─ warte bis saved_mqtt_address gesetzt ist (Formular abgeschickt)
└─ connect_to_mqtt() → bei Fehler: Config zurücksetzen + neu warten
4. Sensor-Loop (TODO):
└─ BME280 auslesen → per MQTT publishen
```
### 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
1. Beim Erststart: Access Point für Erstkonfiguration
2. Weboberfläche zum Einstellen von:
- WLAN SSID + Passwort
- MQTT Broker (Adresse, Port, User, Passwort)
- ✅ Messfrequenz + Pushfrequenz
3. Im Produktionsbetrieb: Live-Daten + Einstellungen änderbar
### makefsdata wie es funktioniert
@@ -286,6 +314,9 @@ Es liest alle Dateien aus dem `fs/`-Ordner und erzeugt daraus eine `fsdata.c` mi
### lwipopts.h
```c
// fsdata.c wird von lwIP's fs.c via #include eingebunden Pfad relativ zum Include-Root
#define HTTPD_FSDATA_FILE "src/fsdata.c"
#define NO_SYS 1
#define LWIP_SOCKET 0
#define LWIP_NETCONN 0
@@ -310,18 +341,22 @@ Es liest alle Dateien aus dem `fs/`-Ordner und erzeugt daraus eine `fsdata.c` mi
#define LWIP_HTTPD 1
#define LWIP_HTTPD_CGI 1
#define LWIP_HTTPD_SSI 1
#define LWIP_HTTPD_SUPPORT_POST 1 // POST-Body Callbacks aktivieren
```
---
## 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
- [x] `makefsdata` automatisch in cmake einbinden
- [x] WLAN Konfigurationsformular + POST-Handler
- [x] WLAN Station Mode
- [x] MQTT Konfigurationsformular + POST-Handler
- [x] MQTT Verbindung zum Broker
- [x] BME280 Sensor auslesen
- [ ] BME280 Sensordaten per MQTT publishen
- [ ] Live-Daten Seite (`live_stats.html`)
- [ ] Einstellungen im Flash speichern (Neustart ohne Neukonfiguration)
---
@@ -352,6 +387,22 @@ Es liest alle Dateien aus dem `fs/`-Ordner und erzeugt daraus eine `fsdata.c` mi
- **${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
- **Buffer** Array als temporärer Zwischenspeicher; in C immer manuell null-terminieren (`buf[len] = '\0'`)
- **pbuf** lwIPs internes Paket-Buffer-Format; Inhalt mit `pbuf_copy_partial()` extrahieren, danach `pbuf_free()` aufrufen
- **strstr** sucht Substring in String, gibt Zeiger auf Fundstelle zurück oder NULL
- **strchr** sucht einzelnes Zeichen in String, gibt Zeiger darauf zurück
- **Zeigerarithmetik** zwei Zeiger auf denselben String subtrahieren gibt die Länge zwischen ihnen
- **strlen** Länge eines C-Strings ohne Null-Terminator
- **atoi** wandelt C-String (`"30"`) in Integer (`30`) um; aus `<stdlib.h>`
- **memset** setzt alle Bytes eines Arrays auf einen Wert; `memset(arr, 0, sizeof(arr))` zum Nullen
- **static (lokale Variable)** lebt für die gesamte Programmlaufzeit, wird nicht bei jedem Aufruf neu initialisiert
- **Asynchrone Callbacks** Funktion wird nicht direkt aufgerufen sondern von lwIP registriert und später automatisch aufgerufen (z.B. bei MQTT-Verbindung)
- **void *arg** generischer Kontext-Zeiger in C-Callbacks; übergib `&deine_variable`, caste in Callback zurück mit `static_cast<typ*>(arg)`
- **static_cast<>** C++ Cast-Operator; typsicherer als C-Style-Cast `(typ)`
- **Ternary Operator** `bedingung ? wert_true : wert_false`; Kurzform für einfache if/else-Zuweisungen
- **mqtt_client_t** lwIP MQTT Client-Objekt; erstellt mit `mqtt_client_new()`
- **mqtt_connect_client_info_t** Struct mit Client-ID, Username, Passwort für MQTT-Verbindung
- **MQTT Verbindungsflow** `mqtt_client_connect()` kehrt sofort zurück; Ergebnis kommt asynchron im Callback
### CMake-Konzepte
@@ -375,3 +426,9 @@ Es liest alle Dateien aus dem `fs/`-Ordner und erzeugt daraus eine `fsdata.c` mi
- `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
- `pico_lwip_http` ist ein INTERFACE-Target `target_include_directories` mit `PRIVATE` schlägt fehl; stattdessen `HTTPD_FSDATA_FILE` in `lwipopts.h` überschreiben
- lwIP `fs.c` inkludiert `fsdata.c` direkt via `#include HTTPD_FSDATA_FILE` ohne Override wird die Default-Seite aus dem pico-sdk verwendet statt der eigenen
- `strcmp` gibt `0` zurück wenn Strings gleich sind, nicht `true` `== true` prüft auf Verschiedenheit
- lwIP CGI-Handler parst nur GET-Parameter aus der URL, nicht POST-Body → für POST-Formulare `LWIP_HTTPD_SUPPORT_POST` + die drei Callbacks (`httpd_post_begin`, `httpd_post_receive_data`, `httpd_post_finished`) verwenden
- `dhcp_server_t` in `ap_init()` muss `static` sein sonst wird der Stack-Speicher nach Funktionsrückkehr freigegeben, der DHCP-Server läuft aber weiter und greift auf ungültigen Speicher zu
- `strncpy` fügt **kein** Null-Byte hinzu wenn exakte Länge übergeben wird → immer `dest[len] = '\0'` danach setzen