diff --git a/include/EggDuino.h b/include/EggDuino.h index 4b3703a..0b53642 100644 --- a/include/EggDuino.h +++ b/include/EggDuino.h @@ -59,10 +59,13 @@ static const int kDefaultServoPin = 4; #endif struct ConfigParameter { + const char *type; const char *key; - int *value; + void *value; String description; int defaultValue; + const char *defaultText; + bool secret; }; extern FastAccelStepperEngine g_stepEngine; @@ -94,6 +97,8 @@ extern int g_iPenEnablePin; extern int g_iServoPin; extern int g_iRotMicrostep; extern int g_iPenMicrostep; +extern String g_sWifiSsid; +extern String g_sWifiPassword; extern ConfigParameter configParameters[]; extern const size_t configParameterCount; diff --git a/src/Config_Web.cpp b/src/Config_Web.cpp index bce888e..dc8e4dd 100644 --- a/src/Config_Web.cpp +++ b/src/Config_Web.cpp @@ -1,6 +1,6 @@ #include "EggDuino.h" #include -#include "credentials.h" +#include namespace { @@ -10,6 +10,21 @@ namespace WebServer server(80); bool configStoreReady = false; + bool isIntType(const ConfigParameter ¶m) + { + return strcmp(param.type, "int") == 0; + } + + int *asIntPtr(ConfigParameter ¶m) + { + return static_cast(param.value); + } + + String *asStringPtr(ConfigParameter ¶m) + { + return static_cast(param.value); + } + ConfigParameter *findParameter(const String &key) { for (size_t i = 0; i < configParameterCount; ++i) @@ -26,7 +41,15 @@ namespace { for (size_t i = 0; i < configParameterCount; ++i) { - *configParameters[i].value = configParameters[i].defaultValue; + if (isIntType(configParameters[i])) + { + *asIntPtr(configParameters[i]) = configParameters[i].defaultValue; + } + else + { + const char *fallback = configParameters[i].defaultText == nullptr ? "" : configParameters[i].defaultText; + *asStringPtr(configParameters[i]) = String(fallback); + } } } @@ -44,7 +67,7 @@ body { font-family: "Segoe UI", sans-serif; margin: 20px; background: #f3f6fb; c main { max-width: 760px; margin: 0 auto; background: #fff; border-radius: 12px; padding: 20px; box-shadow: 0 8px 24px rgba(0,0,0,0.08); } h1 { margin-top: 0; font-size: 1.35rem; } label { display: block; margin: 14px 0 6px; font-weight: 600; } -input[type='number'] { width: 100%; padding: 10px; border: 1px solid #c7d2e5; border-radius: 8px; box-sizing: border-box; } +input[type='number'], input[type='text'], input[type='password'] { width: 100%; padding: 10px; border: 1px solid #c7d2e5; border-radius: 8px; box-sizing: border-box; } button { margin-top: 18px; border: 0; background: #0b5ed7; color: white; padding: 10px 14px; border-radius: 8px; cursor: pointer; } #status { margin-top: 12px; min-height: 1.2em; } #log { margin-top: 20px; border: 1px solid #d6dfef; border-radius: 8px; background: #0f172a; color: #d2e3ff; padding: 10px; height: 220px; overflow-y: auto; white-space: pre-wrap; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; font-size: 0.9rem; } @@ -76,9 +99,23 @@ function renderForm(config) { const label = document.createElement('label'); label.textContent = p.description || p.key; const input = document.createElement('input'); - input.type = 'number'; - input.value = p.value; + const paramType = p.type || 'int'; + input.type = paramType === 'int' ? 'number' : paramType; + if (paramType === 'password') { + input.value = ''; + if (p.hasValue) { + input.placeholder = '(gespeichert)'; + } + } else { + input.value = p.value ?? ''; + } input.dataset.key = p.key; + input.dataset.ptype = paramType; + input.dataset.secret = p.secret ? '1' : '0'; + input.dataset.touched = '0'; + input.addEventListener('input', () => { + input.dataset.touched = '1'; + }); form.appendChild(label); form.appendChild(input); }); @@ -88,7 +125,19 @@ async function saveConfig() { const status = document.getElementById('status'); const inputs = [...document.querySelectorAll('input[data-key]')]; const payload = { - parameters: inputs.map(i => ({ key: i.dataset.key, value: Number(i.value) })) + parameters: inputs + .map(i => { + const type = i.dataset.ptype || 'int'; + const secret = i.dataset.secret === '1'; + if (type === 'int') { + return { key: i.dataset.key, value: Number(i.value) }; + } + if (secret && i.dataset.touched !== '1') { + return null; + } + return { key: i.dataset.key, value: i.value }; + }) + .filter(Boolean) }; const resp = await fetch('/api/config', { method: 'POST', @@ -214,17 +263,19 @@ async function pollLogs() { } // namespace ConfigParameter configParameters[] = { - {"penUpPos", &g_iPenUpPos, "Pen Up Position", 40}, - {"penDownPos", &g_iPenDownPos, "Pen Down Position", 10}, - {"rotStepPin", &g_iRotStepPin, "Rotational Stepper Step Pin", kDefaultRotStepPin}, - {"rotDirPin", &g_iRotDirPin, "Rotational Stepper Direction Pin", kDefaultRotDirPin}, - {"rotEnablePin", &g_iRotEnablePin, "Rotational Stepper Enable Pin", kDefaultRotEnablePin}, - {"penStepPin", &g_iPenStepPin, "Pen Stepper Step Pin", kDefaultPenStepPin}, - {"penDirPin", &g_iPenDirPin, "Pen Stepper Direction Pin", kDefaultPenDirPin}, - {"penEnablePin", &g_iPenEnablePin, "Pen Stepper Enable Pin", kDefaultPenEnablePin}, - {"rotMicrostep", &g_iRotMicrostep, "Rotational Stepper Microsteps", kDefaultRotMicrostep}, - {"penMicrostep", &g_iPenMicrostep, "Pen Stepper Microsteps", kDefaultPenMicrostep}, - {"servoPin", &g_iServoPin, "Servo Pin", kDefaultServoPin}, + {"int", "penUpPos", &g_iPenUpPos, "Pen Up Position", 40, "", false}, + {"int", "penDownPos", &g_iPenDownPos, "Pen Down Position", 10, "", false}, + {"int", "rotStepPin", &g_iRotStepPin, "Rotational Stepper Step Pin", kDefaultRotStepPin, "", false}, + {"int", "rotDirPin", &g_iRotDirPin, "Rotational Stepper Direction Pin", kDefaultRotDirPin, "", false}, + {"int", "rotEnablePin", &g_iRotEnablePin, "Rotational Stepper Enable Pin", kDefaultRotEnablePin, "", false}, + {"int", "penStepPin", &g_iPenStepPin, "Pen Stepper Step Pin", kDefaultPenStepPin, "", false}, + {"int", "penDirPin", &g_iPenDirPin, "Pen Stepper Direction Pin", kDefaultPenDirPin, "", false}, + {"int", "penEnablePin", &g_iPenEnablePin, "Pen Stepper Enable Pin", kDefaultPenEnablePin, "", false}, + {"int", "rotMicrostep", &g_iRotMicrostep, "Rotational Stepper Microsteps", kDefaultRotMicrostep, "", false}, + {"int", "penMicrostep", &g_iPenMicrostep, "Pen Stepper Microsteps", kDefaultPenMicrostep, "", false}, + {"int", "servoPin", &g_iServoPin, "Servo Pin", kDefaultServoPin, "", false}, + {"text", "wifiSsid", &g_sWifiSsid, "WLAN SSID", 0, "", false}, + {"password", "wifiPassword", &g_sWifiPassword, "WLAN Passwort", 0, "", true}, }; const size_t configParameterCount = sizeof(configParameters) / sizeof(configParameters[0]); @@ -275,7 +326,14 @@ bool loadConfigFromFile() } if (item.containsKey("value")) { - *param->value = item["value"].as(); + if (isIntType(*param)) + { + *asIntPtr(*param) = item["value"].as(); + } + else + { + *asStringPtr(*param) = item["value"].as(); + } } if (item.containsKey("description")) { @@ -303,7 +361,16 @@ bool saveConfigToFile() { JsonObject item = params.createNestedObject(); item["key"] = configParameters[i].key; - item["value"] = *configParameters[i].value; + item["type"] = configParameters[i].type; + item["secret"] = configParameters[i].secret; + if (isIntType(configParameters[i])) + { + item["value"] = *asIntPtr(configParameters[i]); + } + else + { + item["value"] = *asStringPtr(configParameters[i]); + } item["description"] = configParameters[i].description; } if (doc.overflowed()) @@ -331,7 +398,21 @@ String buildConfigJson() { JsonObject item = params.createNestedObject(); item["key"] = configParameters[i].key; - item["value"] = *configParameters[i].value; + item["type"] = configParameters[i].type; + item["secret"] = configParameters[i].secret; + if (isIntType(configParameters[i])) + { + item["value"] = *asIntPtr(configParameters[i]); + } + else if (configParameters[i].secret) + { + item["value"] = ""; + item["hasValue"] = !asStringPtr(configParameters[i])->isEmpty(); + } + else + { + item["value"] = *asStringPtr(configParameters[i]); + } item["description"] = configParameters[i].description; } if (doc.overflowed()) @@ -365,7 +446,7 @@ bool applyConfigJson(const String &payload, String &errorMessage) for (JsonObject item : params) { const char *key = item["key"]; - if (key == nullptr || !item.containsKey("value")) + if (key == nullptr) { continue; } @@ -376,7 +457,18 @@ bool applyConfigJson(const String &payload, String &errorMessage) continue; } - *param->value = item["value"].as(); + if (!item.containsKey("value")) + { + continue; + } + if (isIntType(*param)) + { + *asIntPtr(*param) = item["value"].as(); + } + else + { + *asStringPtr(*param) = item["value"].as(); + } if (item.containsKey("description")) { param->description = item["description"].as(); @@ -390,11 +482,12 @@ bool applyConfigJson(const String &payload, String &errorMessage) void startWebInterface() { initConfigStore(); - if (kWifiSsid != 0) - { + bool staConnected = false; + if (!g_sWifiSsid.isEmpty()) + { WiFi.mode(WIFI_STA); - WiFi.begin(kWifiSsid, kWifiPassword); + WiFi.begin(g_sWifiSsid.c_str(), g_sWifiPassword.c_str()); const unsigned long connectStart = millis(); const unsigned long connectTimeoutMs = 10000; @@ -402,9 +495,8 @@ void startWebInterface() { delay(250); } - - - if (WiFi.status() == WL_CONNECTED) + staConnected = (WiFi.status() == WL_CONNECTED); + if (staConnected) { Serial.println(String("http://") + WiFi.localIP().toString()); } @@ -412,24 +504,29 @@ void startWebInterface() { Serial.println("WLAN Verbindung fehlgeschlagen"); } - - server.on("/", HTTP_GET, handleRoot); - server.on("/api/config", HTTP_GET, handleGetConfig); - server.on("/api/config", HTTP_POST, handlePostConfig); - server.on("/api/logs", HTTP_GET, handleGetLogs); - server.onNotFound(handleNotFound); - server.begin(); } - else + if (!staConnected) { - Serial.println("Verwende kein WLAN."); + WiFi.mode(WIFI_AP); + if (WiFi.softAP("EggDuino")) + { + Serial.println(String("AP aktiv: EggDuino / http://") + WiFi.softAPIP().toString()); + } + else + { + Serial.println("AP konnte nicht gestartet werden"); + } } + + server.on("/", HTTP_GET, handleRoot); + server.on("/api/config", HTTP_GET, handleGetConfig); + server.on("/api/config", HTTP_POST, handlePostConfig); + server.on("/api/logs", HTTP_GET, handleGetLogs); + server.onNotFound(handleNotFound); + server.begin(); } void handleWebInterface() { - if (kWifiSsid != NULL) - { - server.handleClient(); - } + server.handleClient(); } diff --git a/src/main.cpp b/src/main.cpp index b631998..050ae9b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -71,6 +71,8 @@ int g_iRotMicrostep = kDefaultRotMicrostep; int g_iPenMicrostep = kDefaultPenMicrostep; float fROT_STEP_CORRECTION = 16.0 / kDefaultRotMicrostep; // devide EBB-Coordinates by this factor to get EGGduino-Steps float fPEN_STEP_CORRECTION = 16.0 / kDefaultPenMicrostep; // devide EBB-Coordinates by this factor to get EGGduino-Steps +String g_sWifiSsid = ""; +String g_sWifiPassword = ""; // Stepper Test #ifdef TEST