Added config for web interface.
This commit is contained in:
@@ -59,10 +59,13 @@ static const int kDefaultServoPin = 4;
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
struct ConfigParameter {
|
struct ConfigParameter {
|
||||||
|
const char *type;
|
||||||
const char *key;
|
const char *key;
|
||||||
int *value;
|
void *value;
|
||||||
String description;
|
String description;
|
||||||
int defaultValue;
|
int defaultValue;
|
||||||
|
const char *defaultText;
|
||||||
|
bool secret;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern FastAccelStepperEngine g_stepEngine;
|
extern FastAccelStepperEngine g_stepEngine;
|
||||||
@@ -94,6 +97,8 @@ extern int g_iPenEnablePin;
|
|||||||
extern int g_iServoPin;
|
extern int g_iServoPin;
|
||||||
extern int g_iRotMicrostep;
|
extern int g_iRotMicrostep;
|
||||||
extern int g_iPenMicrostep;
|
extern int g_iPenMicrostep;
|
||||||
|
extern String g_sWifiSsid;
|
||||||
|
extern String g_sWifiPassword;
|
||||||
|
|
||||||
extern ConfigParameter configParameters[];
|
extern ConfigParameter configParameters[];
|
||||||
extern const size_t configParameterCount;
|
extern const size_t configParameterCount;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#include "EggDuino.h"
|
#include "EggDuino.h"
|
||||||
#include <ArduinoJson.h>
|
#include <ArduinoJson.h>
|
||||||
#include "credentials.h"
|
#include <string.h>
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
@@ -10,6 +10,21 @@ namespace
|
|||||||
WebServer server(80);
|
WebServer server(80);
|
||||||
bool configStoreReady = false;
|
bool configStoreReady = false;
|
||||||
|
|
||||||
|
bool isIntType(const ConfigParameter ¶m)
|
||||||
|
{
|
||||||
|
return strcmp(param.type, "int") == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int *asIntPtr(ConfigParameter ¶m)
|
||||||
|
{
|
||||||
|
return static_cast<int *>(param.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
String *asStringPtr(ConfigParameter ¶m)
|
||||||
|
{
|
||||||
|
return static_cast<String *>(param.value);
|
||||||
|
}
|
||||||
|
|
||||||
ConfigParameter *findParameter(const String &key)
|
ConfigParameter *findParameter(const String &key)
|
||||||
{
|
{
|
||||||
for (size_t i = 0; i < configParameterCount; ++i)
|
for (size_t i = 0; i < configParameterCount; ++i)
|
||||||
@@ -26,7 +41,15 @@ namespace
|
|||||||
{
|
{
|
||||||
for (size_t i = 0; i < configParameterCount; ++i)
|
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); }
|
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; }
|
h1 { margin-top: 0; font-size: 1.35rem; }
|
||||||
label { display: block; margin: 14px 0 6px; font-weight: 600; }
|
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; }
|
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; }
|
#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; }
|
#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');
|
const label = document.createElement('label');
|
||||||
label.textContent = p.description || p.key;
|
label.textContent = p.description || p.key;
|
||||||
const input = document.createElement('input');
|
const input = document.createElement('input');
|
||||||
input.type = 'number';
|
const paramType = p.type || 'int';
|
||||||
input.value = p.value;
|
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.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(label);
|
||||||
form.appendChild(input);
|
form.appendChild(input);
|
||||||
});
|
});
|
||||||
@@ -88,7 +125,19 @@ async function saveConfig() {
|
|||||||
const status = document.getElementById('status');
|
const status = document.getElementById('status');
|
||||||
const inputs = [...document.querySelectorAll('input[data-key]')];
|
const inputs = [...document.querySelectorAll('input[data-key]')];
|
||||||
const payload = {
|
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', {
|
const resp = await fetch('/api/config', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -214,17 +263,19 @@ async function pollLogs() {
|
|||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
ConfigParameter configParameters[] = {
|
ConfigParameter configParameters[] = {
|
||||||
{"penUpPos", &g_iPenUpPos, "Pen Up Position", 40},
|
{"int", "penUpPos", &g_iPenUpPos, "Pen Up Position", 40, "", false},
|
||||||
{"penDownPos", &g_iPenDownPos, "Pen Down Position", 10},
|
{"int", "penDownPos", &g_iPenDownPos, "Pen Down Position", 10, "", false},
|
||||||
{"rotStepPin", &g_iRotStepPin, "Rotational Stepper Step Pin", kDefaultRotStepPin},
|
{"int", "rotStepPin", &g_iRotStepPin, "Rotational Stepper Step Pin", kDefaultRotStepPin, "", false},
|
||||||
{"rotDirPin", &g_iRotDirPin, "Rotational Stepper Direction Pin", kDefaultRotDirPin},
|
{"int", "rotDirPin", &g_iRotDirPin, "Rotational Stepper Direction Pin", kDefaultRotDirPin, "", false},
|
||||||
{"rotEnablePin", &g_iRotEnablePin, "Rotational Stepper Enable Pin", kDefaultRotEnablePin},
|
{"int", "rotEnablePin", &g_iRotEnablePin, "Rotational Stepper Enable Pin", kDefaultRotEnablePin, "", false},
|
||||||
{"penStepPin", &g_iPenStepPin, "Pen Stepper Step Pin", kDefaultPenStepPin},
|
{"int", "penStepPin", &g_iPenStepPin, "Pen Stepper Step Pin", kDefaultPenStepPin, "", false},
|
||||||
{"penDirPin", &g_iPenDirPin, "Pen Stepper Direction Pin", kDefaultPenDirPin},
|
{"int", "penDirPin", &g_iPenDirPin, "Pen Stepper Direction Pin", kDefaultPenDirPin, "", false},
|
||||||
{"penEnablePin", &g_iPenEnablePin, "Pen Stepper Enable Pin", kDefaultPenEnablePin},
|
{"int", "penEnablePin", &g_iPenEnablePin, "Pen Stepper Enable Pin", kDefaultPenEnablePin, "", false},
|
||||||
{"rotMicrostep", &g_iRotMicrostep, "Rotational Stepper Microsteps", kDefaultRotMicrostep},
|
{"int", "rotMicrostep", &g_iRotMicrostep, "Rotational Stepper Microsteps", kDefaultRotMicrostep, "", false},
|
||||||
{"penMicrostep", &g_iPenMicrostep, "Pen Stepper Microsteps", kDefaultPenMicrostep},
|
{"int", "penMicrostep", &g_iPenMicrostep, "Pen Stepper Microsteps", kDefaultPenMicrostep, "", false},
|
||||||
{"servoPin", &g_iServoPin, "Servo Pin", kDefaultServoPin},
|
{"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]);
|
const size_t configParameterCount = sizeof(configParameters) / sizeof(configParameters[0]);
|
||||||
@@ -275,7 +326,14 @@ bool loadConfigFromFile()
|
|||||||
}
|
}
|
||||||
if (item.containsKey("value"))
|
if (item.containsKey("value"))
|
||||||
{
|
{
|
||||||
*param->value = item["value"].as<int>();
|
if (isIntType(*param))
|
||||||
|
{
|
||||||
|
*asIntPtr(*param) = item["value"].as<int>();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
*asStringPtr(*param) = item["value"].as<String>();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (item.containsKey("description"))
|
if (item.containsKey("description"))
|
||||||
{
|
{
|
||||||
@@ -303,7 +361,16 @@ bool saveConfigToFile()
|
|||||||
{
|
{
|
||||||
JsonObject item = params.createNestedObject();
|
JsonObject item = params.createNestedObject();
|
||||||
item["key"] = configParameters[i].key;
|
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;
|
item["description"] = configParameters[i].description;
|
||||||
}
|
}
|
||||||
if (doc.overflowed())
|
if (doc.overflowed())
|
||||||
@@ -331,7 +398,21 @@ String buildConfigJson()
|
|||||||
{
|
{
|
||||||
JsonObject item = params.createNestedObject();
|
JsonObject item = params.createNestedObject();
|
||||||
item["key"] = configParameters[i].key;
|
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;
|
item["description"] = configParameters[i].description;
|
||||||
}
|
}
|
||||||
if (doc.overflowed())
|
if (doc.overflowed())
|
||||||
@@ -365,7 +446,7 @@ bool applyConfigJson(const String &payload, String &errorMessage)
|
|||||||
for (JsonObject item : params)
|
for (JsonObject item : params)
|
||||||
{
|
{
|
||||||
const char *key = item["key"];
|
const char *key = item["key"];
|
||||||
if (key == nullptr || !item.containsKey("value"))
|
if (key == nullptr)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -376,7 +457,18 @@ bool applyConfigJson(const String &payload, String &errorMessage)
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
*param->value = item["value"].as<int>();
|
if (!item.containsKey("value"))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (isIntType(*param))
|
||||||
|
{
|
||||||
|
*asIntPtr(*param) = item["value"].as<int>();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
*asStringPtr(*param) = item["value"].as<String>();
|
||||||
|
}
|
||||||
if (item.containsKey("description"))
|
if (item.containsKey("description"))
|
||||||
{
|
{
|
||||||
param->description = item["description"].as<String>();
|
param->description = item["description"].as<String>();
|
||||||
@@ -390,11 +482,12 @@ bool applyConfigJson(const String &payload, String &errorMessage)
|
|||||||
void startWebInterface()
|
void startWebInterface()
|
||||||
{
|
{
|
||||||
initConfigStore();
|
initConfigStore();
|
||||||
if (kWifiSsid != 0)
|
bool staConnected = false;
|
||||||
{
|
|
||||||
|
|
||||||
|
if (!g_sWifiSsid.isEmpty())
|
||||||
|
{
|
||||||
WiFi.mode(WIFI_STA);
|
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 connectStart = millis();
|
||||||
const unsigned long connectTimeoutMs = 10000;
|
const unsigned long connectTimeoutMs = 10000;
|
||||||
@@ -402,9 +495,8 @@ void startWebInterface()
|
|||||||
{
|
{
|
||||||
delay(250);
|
delay(250);
|
||||||
}
|
}
|
||||||
|
staConnected = (WiFi.status() == WL_CONNECTED);
|
||||||
|
if (staConnected)
|
||||||
if (WiFi.status() == WL_CONNECTED)
|
|
||||||
{
|
{
|
||||||
Serial.println(String("http://") + WiFi.localIP().toString());
|
Serial.println(String("http://") + WiFi.localIP().toString());
|
||||||
}
|
}
|
||||||
@@ -412,6 +504,19 @@ void startWebInterface()
|
|||||||
{
|
{
|
||||||
Serial.println("WLAN Verbindung fehlgeschlagen");
|
Serial.println("WLAN Verbindung fehlgeschlagen");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if (!staConnected)
|
||||||
|
{
|
||||||
|
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("/", HTTP_GET, handleRoot);
|
||||||
server.on("/api/config", HTTP_GET, handleGetConfig);
|
server.on("/api/config", HTTP_GET, handleGetConfig);
|
||||||
@@ -419,17 +524,9 @@ void startWebInterface()
|
|||||||
server.on("/api/logs", HTTP_GET, handleGetLogs);
|
server.on("/api/logs", HTTP_GET, handleGetLogs);
|
||||||
server.onNotFound(handleNotFound);
|
server.onNotFound(handleNotFound);
|
||||||
server.begin();
|
server.begin();
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Serial.println("Verwende kein WLAN.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleWebInterface()
|
void handleWebInterface()
|
||||||
{
|
{
|
||||||
if (kWifiSsid != NULL)
|
|
||||||
{
|
|
||||||
server.handleClient();
|
server.handleClient();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,6 +71,8 @@ int g_iRotMicrostep = kDefaultRotMicrostep;
|
|||||||
int g_iPenMicrostep = kDefaultPenMicrostep;
|
int g_iPenMicrostep = kDefaultPenMicrostep;
|
||||||
float fROT_STEP_CORRECTION = 16.0 / kDefaultRotMicrostep; // devide EBB-Coordinates by this factor to get EGGduino-Steps
|
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
|
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
|
// Stepper Test
|
||||||
#ifdef TEST
|
#ifdef TEST
|
||||||
|
|||||||
Reference in New Issue
Block a user