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

@@ -25,7 +25,8 @@ add_custom_command(
-P ${CMAKE_CURRENT_SOURCE_DIR}/prepend_include.cmake -P ${CMAKE_CURRENT_SOURCE_DIR}/prepend_include.cmake
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
DEPENDS ${CMAKE_SOURCE_DIR}/fs/index.html 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 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) # Standard Ein-/Ausgabe über USB (du siehst printf im Terminal)
pico_enable_stdio_usb(sensor-pico 1) pico_enable_stdio_usb(sensor-pico 1)

View File

@@ -60,11 +60,15 @@ sensor-pico/ ← Git Repo Root
│ ├── bme280/ ← Git Submodule (lafftale1999/bme280_driver) │ ├── bme280/ ← Git Submodule (lafftale1999/bme280_driver)
│ └── dhcp_server/ ← DHCP Server (von pico-examples, kein Submodule) │ └── dhcp_server/ ← DHCP Server (von pico-examples, kein Submodule)
├── src/ ├── src/
│ ├── main.cpp ← Hauptprogramm │ ├── main.cpp ← Hauptprogramm (WiFi + MQTT Verbindungslogik)
│ ├── webserver.c ← lwIP httpd Initialisierung + CGI-Handler │ ├── webserver.c ← lwIP POST-Handler + Formular-Parsing
│ ├── webserver.h ← Exports: saved_ssid, saved_mqtt_* etc.
│ └── fsdata.c ← Generiert von makefsdata, nicht im Git! │ └── fsdata.c ← Generiert von makefsdata, nicht im Git!
├── fs/ ├── 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 ├── build/ ← Von cmake generiert, nicht im Git
├── CMakeLists.txt ├── CMakeLists.txt
├── lwipopts.h ← lwIP Konfiguration ├── lwipopts.h ← lwIP Konfiguration
@@ -125,7 +129,7 @@ pico_sdk_init()
add_subdirectory(lib/bme280 bme280_build) add_subdirectory(lib/bme280 bme280_build)
add_subdirectory(lib/dhcp_server dhcp_server_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( add_custom_command(
OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/src/fsdata.c OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/src/fsdata.c
COMMAND perl ${PICO_SDK_PATH}/lib/lwip/src/apps/http/makefsdata/makefsdata 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 COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_SOURCE_DIR}/prepend_include.cmake
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
DEPENDS ${CMAKE_SOURCE_DIR}/fs/index.html 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 add_executable(sensor-pico
src/main.cpp src/main.cpp
src/webserver.c src/webserver.c
src/fsdata.c
) )
pico_enable_stdio_usb(sensor-pico 1) pico_enable_stdio_usb(sensor-pico 1)
@@ -153,6 +160,7 @@ target_link_libraries(sensor-pico
pico_cyw43_arch_lwip_threadsafe_background pico_cyw43_arch_lwip_threadsafe_background
pico_lwip pico_lwip
pico_lwip_http pico_lwip_http
pico_lwip_mqtt
bme280 bme280
dhcp_server dhcp_server
) )
@@ -260,16 +268,36 @@ Der Pico hat zwei Betriebsmodi:
- ✅ Access Point (`SensorAP`, WPA2) öffnet sich - ✅ Access Point (`SensorAP`, WPA2) öffnet sich
- ✅ DHCP-Server läuft Clients bekommen IP - ✅ DHCP-Server läuft Clients bekommen IP
- ✅ Pico erreichbar unter `192.168.4.1` - ✅ 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 -`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 ### Geplante Anforderungen
1. Beim Erststart: Access Point für Erstkonfiguration 1. Beim Erststart: Access Point für Erstkonfiguration
2. Weboberfläche zum Einstellen von: 2. Weboberfläche zum Einstellen von:
- WLAN SSID + Passwort - WLAN SSID + Passwort
- MQTT Broker (Adresse, Port, User, Passwort) - MQTT Broker (Adresse, Port, User, Passwort)
- Sendefrequenz - ✅ Messfrequenz + Pushfrequenz
3. Im Produktionsbetrieb: Live-Daten + Einstellungen änderbar 3. Im Produktionsbetrieb: Live-Daten + Einstellungen änderbar
### makefsdata wie es funktioniert ### 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 ### lwipopts.h
```c ```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 NO_SYS 1
#define LWIP_SOCKET 0 #define LWIP_SOCKET 0
#define LWIP_NETCONN 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 1
#define LWIP_HTTPD_CGI 1 #define LWIP_HTTPD_CGI 1
#define LWIP_HTTPD_SSI 1 #define LWIP_HTTPD_SSI 1
#define LWIP_HTTPD_SUPPORT_POST 1 // POST-Body Callbacks aktivieren
``` ```
--- ---
## Nächste Schritte ## Nächste Schritte
- [x] `makefsdata` automatisch in cmake einbinden damit `fs/index.html` beim Build in die Firmware eingebaut wird - [x] `makefsdata` automatisch in cmake einbinden
- [ ] Konfigurationsformular in `index.html` (WLAN + MQTT + Sendefrequenz) - [x] WLAN Konfigurationsformular + POST-Handler
- [ ] CGI-Handler in `webserver.c` für POST-Request vom Formular - [x] WLAN Station Mode
- [ ] Einstellungen im Flash speichern - [x] MQTT Konfigurationsformular + POST-Handler
- [ ] WLAN Station Mode (Verbindung mit bestehendem Netz) - [x] MQTT Verbindung zum Broker
- [ ] MQTT einbinden - [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} -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 - **${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 - **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 ### 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 - `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 - 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 - `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

View File

@@ -20,10 +20,10 @@
<label>MQTT Passwort:</label> <label>MQTT Passwort:</label>
<input type="password" name="mqtt-password"> <input type="password" name="mqtt-password">
<br> <br>
<label>Messfrequenz (sekunden)</label> <label>Messfrequenz (ms)</label>
<input type="number" name="measure-frequency"> <input type="number" name="measure-frequency">
<br> <br>
<label>Pushfrequenz (sekunden)</label> <label>Pushfrequenz (s)</label>
<input type="number" name="push-frequency"> <input type="number" name="push-frequency">
<br> <br>
<input type="submit" value="Speichern"> <input type="submit" value="Speichern">

View File

@@ -7,7 +7,7 @@
</head> </head>
<body> <body>
<h1>WLAN Einstellungen</h1> <h1>WLAN Einstellungen</h1>
<form action="/config" method="post"> <form action="/wlan-config" method="post">
<label>SSID:</label> <label>SSID:</label>
<input type="text" name="ssid"> <input type="text" name="ssid">
<br> <br>

View File

@@ -29,6 +29,7 @@
#define MEM_SIZE 4000 #define MEM_SIZE 4000
#define MEMP_NUM_TCP_SEG 32 #define MEMP_NUM_TCP_SEG 32
#define PBUF_POOL_SIZE 24 #define PBUF_POOL_SIZE 24
#define MEMP_NUM_SYS_TIMEOUT 20
#define TCP_MSS 1460 #define TCP_MSS 1460
#define TCP_WND (8 * TCP_MSS) #define TCP_WND (8 * TCP_MSS)

View File

@@ -10,6 +10,8 @@
#include <cstdio> #include <cstdio>
#include <cstring> #include <cstring>
static dhcp_server_t dhcp_server{};
void ap_init() { void ap_init() {
cyw43_arch_enable_ap_mode("SensorAP", "passwort123", CYW43_AUTH_WPA2_AES_PSK); 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(&gw, 192, 168, 4, 1);
IP4_ADDR(&mask, 255, 255, 255, 0); 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); dhcp_server_init(&dhcp_server, &gw, &mask);
} }
@@ -39,12 +41,14 @@ int connect_to_wifi() {
cyw43_arch_poll(); cyw43_arch_poll();
sleep_ms(100); sleep_ms(100);
} }
dhcp_server_deinit(&dhcp_server);
cyw43_arch_disable_ap_mode(); cyw43_arch_disable_ap_mode();
cyw43_arch_disable_sta_mode();
sleep_ms(500); sleep_ms(500);
cyw43_arch_enable_sta_mode(); cyw43_arch_enable_sta_mode();
ret = cyw43_arch_wifi_connect_timeout_ms(saved_ssid, saved_password, 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) { if (ret == 0) {
return 0; return 0;
} }
@@ -73,6 +77,7 @@ int connect_to_mqtt() {
info.client_user = saved_mqtt_user; info.client_user = saved_mqtt_user;
info.client_pass = saved_mqtt_password; info.client_pass = saved_mqtt_password;
mqtt_status = -1;
mqtt_client_connect(client, &broker_ip, saved_mqtt_port, mqtt_cb, mqtt_client_connect(client, &broker_ip, saved_mqtt_port, mqtt_cb,
&mqtt_status, &info); &mqtt_status, &info);
while (mqtt_status == -1) { while (mqtt_status == -1) {
@@ -82,9 +87,33 @@ int connect_to_mqtt() {
return mqtt_status; 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 main() {
int mqtt_ret{-1}; int mqtt_ret{-1};
int wifi_status{1}; int wifi_status{1};
int bme_status{-1};
bme280_handle_t handle{};
stdio_init_all(); stdio_init_all();
sleep_ms(3000); sleep_ms(3000);
cyw43_arch_init(); cyw43_arch_init();
@@ -92,7 +121,10 @@ int main() {
while (wifi_status != 0) { while (wifi_status != 0) {
wifi_status = (connect_to_wifi() == 0) ? 0 : 1; 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 (true) {
while (saved_mqtt_address[0] == '\0') { while (saved_mqtt_address[0] == '\0') {
@@ -100,10 +132,23 @@ int main() {
sleep_ms(200); sleep_ms(200);
} }
mqtt_ret = connect_to_mqtt(); mqtt_ret = connect_to_mqtt();
if (mqtt_ret) { if (!mqtt_ret) {
printf("Connected to mqtt!\n");
break; break;
} else { } else {
printf("Mqtt Status: %d\n", mqtt_ret);
reset_mqtt_config(); 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);
}
}
} }

View File

@@ -45,15 +45,15 @@ err_t httpd_post_begin(void *connection, const char *uri,
const char *http_request, u16_t http_request_len, const char *http_request, u16_t http_request_len,
int content_len, char *response_uri, int content_len, char *response_uri,
u16_t response_uri_len, u8_t *post_auto_wnd) { u16_t response_uri_len, u8_t *post_auto_wnd) {
if (strcmp(uri, "/config") == 0) { if (strcmp(uri, "/wlan-config") == 0) {
strncpy(response_uri, "/index.html", response_uri_len); strncpy(response_uri, "/wlan_config.html", response_uri_len);
*post_auto_wnd = 1; *post_auto_wnd = 1;
post_state = 0; post_state = 0;
return ERR_OK; return ERR_OK;
} }
if (strcmp(uri, "/mqtt-config") == 0) { if (strcmp(uri, "/mqtt-config") == 0) {
post_state = 1; post_state = 1;
strncpy(response_uri, "/config.html", response_uri_len); strncpy(response_uri, "/mqtt_config.html", response_uri_len);
*post_auto_wnd = 1; *post_auto_wnd = 1;
return ERR_OK; 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, void httpd_post_finished(void *connection, char *response_uri,
u16_t response_uri_len) { u16_t response_uri_len) {
if (post_state == 0) { 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("ssid=", '&', saved_ssid, 32);
parse_post("password=", '\0', saved_password, 64); parse_post("password=", '\0', saved_password, 64);
} else if (post_state == 1) { } else if (post_state == 1) {
char temp[16]; 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-address=", '&', saved_mqtt_address, 64);
parse_post("mqtt-user=", '&', saved_mqtt_user, 32); parse_post("mqtt-user=", '&', saved_mqtt_user, 32);