diff --git a/CMakeLists.txt b/CMakeLists.txt index 3ba7e46..d7952c8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,7 +25,8 @@ add_custom_command( -P ${CMAKE_CURRENT_SOURCE_DIR}/prepend_include.cmake WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} DEPENDS ${CMAKE_SOURCE_DIR}/fs/index.html - DEPENDS ${CMAKE_SOURCE_DIR}/fs/config.html + DEPENDS ${CMAKE_SOURCE_DIR}/fs/mqtt_config.html + DEPENDS ${CMAKE_SOURCE_DIR}/fs/wlan_config.html ) @@ -34,6 +35,11 @@ add_executable(sensor-pico src/webserver.c ) +add_dependencies(sensor-pico generate_fsdata) + +add_custom_target(generate_fsdata + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/src/fsdata.c +) # Standard Ein-/Ausgabe über USB (du siehst printf im Terminal) pico_enable_stdio_usb(sensor-pico 1) diff --git a/PROJEKT.md b/PROJEKT.md index 8324ba4..ab29f94 100644 --- a/PROJEKT.md +++ b/PROJEKT.md @@ -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 `` +- **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(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 diff --git a/fs/mqtt_config.html b/fs/mqtt_config.html index ebc6a08..0a80464 100644 --- a/fs/mqtt_config.html +++ b/fs/mqtt_config.html @@ -20,10 +20,10 @@
- +
- +
diff --git a/fs/wlan_config.html b/fs/wlan_config.html index bbf8d63..e310c89 100644 --- a/fs/wlan_config.html +++ b/fs/wlan_config.html @@ -7,7 +7,7 @@

WLAN Einstellungen

-
+
diff --git a/lwipopts.h b/lwipopts.h index 7515944..dfea587 100644 --- a/lwipopts.h +++ b/lwipopts.h @@ -29,6 +29,7 @@ #define MEM_SIZE 4000 #define MEMP_NUM_TCP_SEG 32 #define PBUF_POOL_SIZE 24 +#define MEMP_NUM_SYS_TIMEOUT 20 #define TCP_MSS 1460 #define TCP_WND (8 * TCP_MSS) diff --git a/src/main.cpp b/src/main.cpp index f95399a..e02f6b4 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -10,6 +10,8 @@ #include #include +static dhcp_server_t dhcp_server{}; + void ap_init() { cyw43_arch_enable_ap_mode("SensorAP", "passwort123", CYW43_AUTH_WPA2_AES_PSK); @@ -18,7 +20,7 @@ void ap_init() { IP4_ADDR(&gw, 192, 168, 4, 1); IP4_ADDR(&mask, 255, 255, 255, 0); - static dhcp_server_t dhcp_server{}; + dhcp_server_deinit(&dhcp_server); dhcp_server_init(&dhcp_server, &gw, &mask); } @@ -39,12 +41,14 @@ int connect_to_wifi() { cyw43_arch_poll(); sleep_ms(100); } + dhcp_server_deinit(&dhcp_server); cyw43_arch_disable_ap_mode(); + cyw43_arch_disable_sta_mode(); sleep_ms(500); cyw43_arch_enable_sta_mode(); ret = cyw43_arch_wifi_connect_timeout_ms(saved_ssid, saved_password, - CYW43_AUTH_WPA2_MIXED_PSK, 10000); + CYW43_AUTH_WPA2_MIXED_PSK, 30000); if (ret == 0) { return 0; } @@ -73,6 +77,7 @@ int connect_to_mqtt() { info.client_user = saved_mqtt_user; info.client_pass = saved_mqtt_password; + mqtt_status = -1; mqtt_client_connect(client, &broker_ip, saved_mqtt_port, mqtt_cb, &mqtt_status, &info); while (mqtt_status == -1) { @@ -82,9 +87,33 @@ int connect_to_mqtt() { return mqtt_status; } +BME280_READING_INTERVALS_MS convert_Interval(int interval) { + if (interval < 1 && interval > 0) { + return INTERVAL_0_5MS; + } else if (interval < 20 && interval > 1) { + return INTERVAL_10MS; + } else if (interval < 30 && interval > 10) { + return INTERVAL_20MS; + } else if (interval < 125 && interval > 20) { + return INTERVAL_62_5MS; + } else if (interval < 250 && interval > 62.5) { + return INTERVAL_125MS; + } else if (interval < 500 && interval > 125) { + return INTERVAL_250MS; + } else if (interval < 1000 && interval > 250) { + return INTERVAL_500MS; + } else if (interval > 1000) { + return INTERVAL_1000MS; + } else { + return INTERVAL_500MS; + } +} + int main() { int mqtt_ret{-1}; int wifi_status{1}; + int bme_status{-1}; + bme280_handle_t handle{}; stdio_init_all(); sleep_ms(3000); cyw43_arch_init(); @@ -92,7 +121,10 @@ int main() { while (wifi_status != 0) { wifi_status = (connect_to_wifi() == 0) ? 0 : 1; + printf("Wifi status: %d\n", wifi_status); } + printf("Connected to wifi!\n"); + printf("IP: %s\n", ip4addr_ntoa(netif_ip4_addr(netif_default))); while (true) { while (saved_mqtt_address[0] == '\0') { @@ -100,10 +132,23 @@ int main() { sleep_ms(200); } mqtt_ret = connect_to_mqtt(); - if (mqtt_ret) { + if (!mqtt_ret) { + printf("Connected to mqtt!\n"); break; } else { + printf("Mqtt Status: %d\n", mqtt_ret); reset_mqtt_config(); } } + bme_status = + bme280_init(&handle, 0x76, convert_Interval(saved_measure_frequency)); + if (!bme_status) { + while (true) { + bme280_read_data(handle); + printf("Messwerte: %s\n", bme280_get_json(handle)); + // publish_mqtt(); + cyw43_arch_poll(); + sleep_ms(500); + } + } } diff --git a/src/webserver.c b/src/webserver.c index 22b7759..a22664a 100644 --- a/src/webserver.c +++ b/src/webserver.c @@ -45,15 +45,15 @@ err_t httpd_post_begin(void *connection, const char *uri, const char *http_request, u16_t http_request_len, int content_len, char *response_uri, u16_t response_uri_len, u8_t *post_auto_wnd) { - if (strcmp(uri, "/config") == 0) { - strncpy(response_uri, "/index.html", response_uri_len); + if (strcmp(uri, "/wlan-config") == 0) { + strncpy(response_uri, "/wlan_config.html", response_uri_len); *post_auto_wnd = 1; post_state = 0; return ERR_OK; } if (strcmp(uri, "/mqtt-config") == 0) { post_state = 1; - strncpy(response_uri, "/config.html", response_uri_len); + strncpy(response_uri, "/mqtt_config.html", response_uri_len); *post_auto_wnd = 1; return ERR_OK; } @@ -71,14 +71,14 @@ err_t httpd_post_receive_data(void *connection, struct pbuf *p) { void httpd_post_finished(void *connection, char *response_uri, u16_t response_uri_len) { if (post_state == 0) { - strncpy(response_uri, "/index.html", response_uri_len); + strncpy(response_uri, "/wlan_config.html", response_uri_len); parse_post("ssid=", '&', saved_ssid, 32); parse_post("password=", '\0', saved_password, 64); } else if (post_state == 1) { char temp[16]; - strncpy(response_uri, "/config.html", response_uri_len); + strncpy(response_uri, "/mqtt_config.html", response_uri_len); parse_post("mqtt-address=", '&', saved_mqtt_address, 64); parse_post("mqtt-user=", '&', saved_mqtt_user, 32);