diff --git a/include/EggDuino.h b/include/EggDuino.h index 591fe91..3458b69 100644 --- a/include/EggDuino.h +++ b/include/EggDuino.h @@ -2,10 +2,16 @@ #define EGGDUINO_H #include +#ifdef ESP32 +#include +#include +#include +#include +#else #include -#include +#endif -#include "AccelStepper.h" +#include #include "SerialCommand.h" #include "button.h" @@ -26,8 +32,12 @@ #define servoPin 4 -#define penUpPosEEAddress ((uint16_t *)0) -#define penDownPosEEAddress ((uint16_t *)2) +struct ConfigParameter { + const char *key; + int *value; + String description; + int defaultValue; +}; extern AccelStepper rotMotor; extern AccelStepper penMotor; @@ -52,6 +62,9 @@ extern float rotSpeed; extern float penSpeed; extern boolean motorsEnabled; +extern ConfigParameter configParameters[]; +extern const size_t configParameterCount; + void makeComInterface(); void initHardware(); void moveOneStep(); @@ -68,4 +81,12 @@ void prepareMove(uint16_t duration, int penStepsEBB, int rotStepsEBB); void storePenUpPosInEE(); void storePenDownPosInEE(); +bool initConfigStore(); +bool loadConfigFromFile(); +bool saveConfigToFile(); +String buildConfigJson(); +bool applyConfigJson(const String &payload, String &errorMessage); +void startWebInterface(); +void handleWebInterface(); + #endif diff --git a/platformio.ini b/platformio.ini index 69efc37..be20c1f 100644 --- a/platformio.ini +++ b/platformio.ini @@ -9,10 +9,12 @@ ; https://docs.platformio.org/page/projectconf.html [env:uno] -platform = atmelavr -board = uno +platform = platformio/espressif32 +board = esp32dev framework = arduino -monitor_speed = 9600 -upload_speed = 115200 -upload_port= COM3 -lib_deps = arduino-libraries/Servo@^1.3.0 +monitor_speed = 115200 +upload_speed = 576000 +upload_port = /dev/ttyUSB* +lib_deps = + madhephaestus/ESP32Servo@^3.0.6 + bblanchon/ArduinoJson@^6.21.5 diff --git a/src/Config_Web.cpp b/src/Config_Web.cpp new file mode 100644 index 0000000..ffe2a50 --- /dev/null +++ b/src/Config_Web.cpp @@ -0,0 +1,311 @@ +#include "EggDuino.h" +#include +#include + +namespace { +const char *kConfigPath = "/config.json"; +const char *kApSsid = "EggDuino"; +const uint16_t kDnsPort = 53; +const IPAddress kApIp(192, 168, 4, 1); +const IPAddress kApSubnet(255, 255, 255, 0); + +WebServer server(80); +DNSServer dnsServer; +bool configStoreReady = false; + +ConfigParameter *findParameter(const String &key) { + for (size_t i = 0; i < configParameterCount; ++i) { + if (key.equals(configParameters[i].key)) { + return &configParameters[i]; + } + } + return nullptr; +} + +void applyDefaults() { + for (size_t i = 0; i < configParameterCount; ++i) { + *configParameters[i].value = configParameters[i].defaultValue; + } +} + +void handleRoot() { + static const char kPage[] PROGMEM = R"HTML( + + + + + +EggDuino Konfiguration + + + +
+

EggDuino Parameter

+
+ +
+
+ + + +)HTML"; + + server.send(200, "text/html", kPage); +} + +void redirectToPortal() { + server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate"); + server.sendHeader("Pragma", "no-cache"); + server.sendHeader("Expires", "-1"); + server.sendHeader("Location", String("http://") + WiFi.softAPIP().toString() + "/", true); + server.send(302, "text/plain", ""); +} + +void handleGetConfig() { + if (!configStoreReady && !initConfigStore()) { + server.send(500, "text/plain", "Config storage not available"); + return; + } + server.send(200, "application/json", buildConfigJson()); +} + +void handlePostConfig() { + if (!configStoreReady && !initConfigStore()) { + server.send(500, "text/plain", "Config storage not available"); + return; + } + + if (!server.hasArg("plain")) { + server.send(400, "text/plain", "Missing JSON body"); + return; + } + + String error; + if (!applyConfigJson(server.arg("plain"), error)) { + server.send(400, "text/plain", error); + return; + } + + if (!saveConfigToFile()) { + server.send(500, "text/plain", "Could not save config"); + return; + } + + // penState = penUpPos; + // penServo.write(penState); + server.send(200, "application/json", buildConfigJson()); +} +} // namespace + +ConfigParameter configParameters[] = { + {"penUpPos", &penUpPos, "Pen Up Position", 5}, + {"penDownPos", &penDownPos, "Pen Down Position", 20}, +}; + +const size_t configParameterCount = sizeof(configParameters) / sizeof(configParameters[0]); + +bool initConfigStore() { + if (!SPIFFS.begin(true)) { + configStoreReady = false; + return false; + } + configStoreReady = loadConfigFromFile(); + return configStoreReady; +} + +bool loadConfigFromFile() { + applyDefaults(); + + File file = SPIFFS.open(kConfigPath, "r"); + if (!file) { + return saveConfigToFile(); + } + + StaticJsonDocument<1024> doc; + DeserializationError err = deserializeJson(doc, file); + file.close(); + if (err) { + return saveConfigToFile(); + } + + JsonArray params = doc["parameters"].as(); + for (JsonObject item : params) { + const char *key = item["key"]; + if (key == nullptr) { + continue; + } + ConfigParameter *param = findParameter(String(key)); + if (param == nullptr) { + continue; + } + if (item.containsKey("value")) { + *param->value = item["value"].as(); + } + if (item.containsKey("description")) { + param->description = item["description"].as(); + } + } + + return true; +} + +bool saveConfigToFile() { + File file = SPIFFS.open(kConfigPath, "w"); + if (!file) { + return false; + } + + StaticJsonDocument<1024> doc; + JsonArray params = doc.createNestedArray("parameters"); + for (size_t i = 0; i < configParameterCount; ++i) { + JsonObject item = params.createNestedObject(); + item["key"] = configParameters[i].key; + item["value"] = *configParameters[i].value; + item["description"] = configParameters[i].description; + } + + bool ok = serializeJsonPretty(doc, file) > 0; + file.close(); + return ok; +} + +String buildConfigJson() { + StaticJsonDocument<1024> doc; + JsonArray params = doc.createNestedArray("parameters"); + for (size_t i = 0; i < configParameterCount; ++i) { + JsonObject item = params.createNestedObject(); + item["key"] = configParameters[i].key; + item["value"] = *configParameters[i].value; + item["description"] = configParameters[i].description; + } + + String output; + serializeJson(doc, output); + return output; +} + +bool applyConfigJson(const String &payload, String &errorMessage) { + StaticJsonDocument<1024> doc; + DeserializationError err = deserializeJson(doc, payload); + if (err) { + errorMessage = "Invalid JSON payload"; + return false; + } + + JsonArray params = doc["parameters"].as(); + if (params.isNull()) { + errorMessage = "JSON must contain 'parameters' array"; + return false; + } + + for (JsonObject item : params) { + const char *key = item["key"]; + if (key == nullptr || !item.containsKey("value")) { + continue; + } + + ConfigParameter *param = findParameter(String(key)); + if (param == nullptr) { + continue; + } + + *param->value = item["value"].as(); + if (item.containsKey("description")) { + param->description = item["description"].as(); + } + } + + return true; +} + +void startWebInterface() { + WiFi.mode(WIFI_AP); + WiFi.softAPConfig(kApIp, kApIp, kApSubnet); + WiFi.softAP(kApSsid); + dnsServer.start(kDnsPort, "*", WiFi.softAPIP()); + initConfigStore(); + Serial.print("Config AP IP: "); + Serial.println(WiFi.softAPIP()); + + server.on("/", HTTP_GET, handleRoot); + server.on("/api/config", HTTP_GET, handleGetConfig); + server.on("/api/config", HTTP_POST, handlePostConfig); + server.on("/generate_204", HTTP_GET, redirectToPortal); + server.on("/gen_204", HTTP_GET, redirectToPortal); + server.on("/hotspot-detect.html", HTTP_GET, redirectToPortal); + server.on("/library/test/success.html", HTTP_GET, redirectToPortal); + server.on("/ncsi.txt", HTTP_GET, redirectToPortal); + server.on("/connecttest.txt", HTTP_GET, redirectToPortal); + server.onNotFound(redirectToPortal); + server.begin(); +} + +void handleWebInterface() { + dnsServer.processNextRequest(); + server.handleClient(); +} diff --git a/src/Helper_Functions.cpp b/src/Helper_Functions.xcpp similarity index 91% rename from src/Helper_Functions.cpp rename to src/Helper_Functions.xcpp index 6d1d7eb..03b546b 100644 --- a/src/Helper_Functions.cpp +++ b/src/Helper_Functions.xcpp @@ -1,16 +1,11 @@ #include "EggDuino.h" -inline void loadPenPosFromEE() { - penUpPos = eeprom_read_word(penUpPosEEAddress); - penDownPos = eeprom_read_word(penDownPosEEAddress); - penState = penUpPos; -} - void initHardware(){ - // enable eeprom wait in avr/eeprom.h functions - SPMCSR &= ~SELFPRGEN; - - loadPenPosFromEE(); + if (!initConfigStore()) { + penUpPos = 5; + penDownPos = 20; + } + penState = penUpPos; pinMode(enableRotMotor, OUTPUT); pinMode(enablePenMotor, OUTPUT); @@ -24,14 +19,12 @@ void initHardware(){ penServo.write(penState); } - - void storePenUpPosInEE() { - eeprom_update_word(penUpPosEEAddress, penUpPos); + saveConfigToFile(); } void storePenDownPosInEE() { - eeprom_update_word(penDownPosEEAddress, penDownPos); + saveConfigToFile(); } void sendAck(){ diff --git a/src/main.cpp b/src/main.cpp index ae092ce..9a6efd3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -30,10 +30,10 @@ //----------------------------------------------------------------------------------------------------------- //make Objects -AccelStepper rotMotor(AccelStepper::DRIVER, step1, dir1); -AccelStepper penMotor(AccelStepper::DRIVER, step2, dir2); -Servo penServo; -SerialCommand SCmd; +// AccelStepper rotMotor(AccelStepper::DRIVER, step1, dir1); +// AccelStepper penMotor(AccelStepper::DRIVER, step2, dir2); +// Servo penServo; +// SerialCommand SCmd; //create Buttons #ifdef prgButton Button prgButtonToggle(prgButton, setprgButtonState); @@ -63,19 +63,18 @@ float rotSpeed=0; float penSpeed=0; // these are local variables for Function SteppermotorMove-Command, but for performance-reasons it will be initialized here boolean motorsEnabled = 0; -void setup() { - Serial.begin(9600); - makeComInterface(); - initHardware(); +void setup() { + Serial.begin(115200); + Serial.print("Starting..."); + //makeComInterface(); + //initHardware(); + startWebInterface(); } void loop() { - - - moveOneStep(); - SCmd.readSerial(); - - + // moveOneStep(); + // SCmd.readSerial(); + handleWebInterface(); #ifdef penToggleButton penToggle.check();