diff --git a/data/Fermenter/config.json b/data/Fermenter/config.json new file mode 100644 index 0000000..9da52bb --- /dev/null +++ b/data/Fermenter/config.json @@ -0,0 +1,8 @@ +{ + "pid": { + "p": 5, + "i": 0, + "d": 0, + "target": 0 + } +} \ No newline at end of file diff --git a/data/Fermenter/program/demo.json b/data/Fermenter/program/demo.json new file mode 100644 index 0000000..466ee95 --- /dev/null +++ b/data/Fermenter/program/demo.json @@ -0,0 +1,24 @@ +{ + "name": "Demo", + "iterations": 1, + "points": [ + { + "name": "Aufwärmen", + "start": 20, + "end": 28, + "seconds": 3 + }, + { + "name": "Halten", + "start": 28, + "end": 28, + "seconds": 3 + }, + { + "name": "Abkühlen", + "start": 28, + "end": 20, + "seconds": 3 + } + ] +} \ No newline at end of file diff --git a/data/Fermenter/program/demo2.json b/data/Fermenter/program/demo2.json new file mode 100644 index 0000000..24a30df --- /dev/null +++ b/data/Fermenter/program/demo2.json @@ -0,0 +1,24 @@ +{ + "name": "Demo", + "iterations": 2, + "points": [ + { + "name": "Aufwärmen", + "start": 20, + "end": 28, + "seconds": 3 + }, + { + "name": "Halten", + "start": 28, + "end": 28, + "seconds": 3 + }, + { + "name": "Abkühlen", + "start": 28, + "end": 20, + "seconds": 3 + } + ] +} \ No newline at end of file diff --git a/data/Fermenter/program/endless.json b/data/Fermenter/program/endless.json new file mode 100644 index 0000000..daa8541 --- /dev/null +++ b/data/Fermenter/program/endless.json @@ -0,0 +1,24 @@ +{ + "name": "Demo", + "iterations": -1, + "points": [ + { + "name": "Aufwärmen", + "start": 20, + "end": 28, + "seconds": 3 + }, + { + "name": "Halten", + "start": 28, + "end": 28, + "seconds": 3 + }, + { + "name": "Abkühlen", + "start": 28, + "end": 20, + "seconds": 3 + } + ] +} \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index 096c23f..66b201d 100644 --- a/platformio.ini +++ b/platformio.ini @@ -49,6 +49,6 @@ board_build.filesystem = ${common.board_build.filesystem} monitor_speed = ${common.monitor_speed} upload_flags = --auth=OtaAuthPatrixFermenter upload_protocol = ${common.upload_protocol} -upload_port = 10.0.0.164 +upload_port = 10.0.0.169 ;upload_port = ${common.upload_port} ;upload_speed = ${common.upload_speed} diff --git a/src/node/Fermenter/Fermenter.cpp b/src/node/Fermenter/Fermenter.cpp index 0d287a3..81721d6 100644 --- a/src/node/Fermenter/Fermenter.cpp +++ b/src/node/Fermenter/Fermenter.cpp @@ -1,6 +1,13 @@ #ifdef NODE_FERMENTER -#include "Fermenter.h" +#include + +#include "config.h" +#include "display.h" +#include "http.h" +#include "pid.h" +#include "Program.h" +#include "rotary.h" void patrixSetup() { config.read(); @@ -18,9 +25,9 @@ void patrixLoop() { ds18b20.loop(); temperature.loop(); - rotary.loop(); - pid.loop(); + rotary.loop(); + program.loop(); displayLoop(); } diff --git a/src/node/Fermenter/Fermenter.h b/src/node/Fermenter/Fermenter.h deleted file mode 100644 index 4ba5a2d..0000000 --- a/src/node/Fermenter/Fermenter.h +++ /dev/null @@ -1,68 +0,0 @@ -#ifndef NODE_FERMENTER_H -#define NODE_FERMENTER_H - -#define HEATER_POWER_W 30 - -/* rotary ----------------------------------------------------------------------------------------- */ - -#include "patrix/Rotary.h" - -extern Rotary rotary; - -void rotaryCallback(int delta); - -/* display ---------------------------------------------------------------------------------------- */ - -#include - -extern unsigned long displayModifyTarget; - -extern LedControl display; - -void displayPrintf(const char* format, ...); - -void displayLoop(); - -/* config ----------------------------------------------------------------------------------------- */ - -#include "patrix/Config.h" - -extern Config config; - -void configCollect(JsonDocument& json); - -void configApply(JsonDocument& json); - -/* pid -------------------------------------------------------------------------------------------- */ - -#include "patrix/DS18B20Sensor.h" -#include "patrix/PIDController.h" -#include "patrix/PWMOutput.h" - -extern DS18B20 ds18b20; - -extern DS18B20Sensor temperature; - -extern PWMOutput heater; - -extern PIDController pid; - -void addTarget(double delta); - -/* http ------------------------------------------------------------------------------------------- */ - -#include "patrix/http.h" - -void httpStatus(AsyncWebServerRequest* request); - -void httpTargetAdd(AsyncWebServerRequest* request); - -void httpSetup2(); - -/* patrix ----------------------------------------------------------------------------------------- */ - -void patrixSetup(); - -void patrixLoop(); - -#endif diff --git a/src/node/Fermenter/Program.cpp b/src/node/Fermenter/Program.cpp new file mode 100644 index 0000000..f406f68 --- /dev/null +++ b/src/node/Fermenter/Program.cpp @@ -0,0 +1,7 @@ +#ifdef NODE_FERMENTER + +#include "Program.h" + +Program program; + +#endif diff --git a/src/node/Fermenter/Program.h b/src/node/Fermenter/Program.h new file mode 100644 index 0000000..ba21738 --- /dev/null +++ b/src/node/Fermenter/Program.h @@ -0,0 +1,234 @@ +#ifndef PROGRAM_H +#define PROGRAM_H + +#include + +#include "pid.h" +#include "ProgramPoint.h" + +class Program { + + String name = ""; + + int iterations = 0; + + int iteration = 0; + + size_t pointCount = 0; + + ProgramPoint* pointList = nullptr; + + ProgramPoint* point = nullptr; + + bool running = false; + + bool paused = false; + + unsigned long pauseAlreadyProgressedMillis = 0; + + void reset() { + name = ""; + iterations = 0; + iteration = 0; + pointCount = 0; + if (pointList != nullptr) { + free(pointList); + pointList = nullptr; + } + point = nullptr; + running = false; + paused = false; + pauseAlreadyProgressedMillis = 0; + } + +public: + + String wantedName = ""; + + bool start() { + if (pointList == nullptr || point == nullptr) { + Log.warn("No program loaded."); + return false; + } + if (running) { + Log.warn("Program already running."); + return false; + } + Log.info("Program started: \"%s\"", name.c_str()); + running = true; + iteration = 1; + point = pointList; + point->start(0); + return true; + } + + bool stop() { + if (pointList == nullptr || point == nullptr) { + Log.warn("No program loaded."); + return false; + } + if (!running) { + Log.warn("Program not running."); + return false; + } + running = false; + pid.setTarget(0); + Log.info("Program stopped."); + return true; + } + + bool pause() { + if (pointList == nullptr || point == nullptr) { + Log.warn("No program loaded."); + return false; + } + if (!running) { + Log.warn("Program not running."); + return false; + } + if (paused) { + Log.warn("Program already paused."); + return false; + } + paused = true; + pauseAlreadyProgressedMillis = point->getProgressMillis(); + Log.info("Program paused."); + return true; + } + + bool resume() { + if (pointList == nullptr || point == nullptr) { + Log.warn("No program loaded."); + return false; + } + if (!running) { + Log.warn("Program not running."); + return false; + } + if (!paused) { + Log.warn("Program not paused."); + return false; + } + paused = false; + point->start(pauseAlreadyProgressedMillis); + Log.info("Program resumed."); + return true; + } + + void loop() { + if (!wantedName.isEmpty()) { + load(wantedName); + wantedName = ""; + } + + if (!running || paused || pointList == nullptr || point == nullptr) { + return; + } + + if (point->isComplete()) { + point = (point - pointList + 1) % pointCount + pointList; + if (point == pointList) { + if (iterations < 0) { + Log.info("Program repeat (endless)"); + } else if (iteration < iterations) { + iteration++; + Log.info("Program iteration %d/%d", iteration, iterations); + } else { + running = false; + applyTemperature(0, true); + Log.info("Program terminated"); + return; + } + } + point->start(0); + } + applyTemperature(point->getTemperature(), false); + } + +private: + + static void applyTemperature(const double temperature, bool forceLog) { + static auto last = temperature; + forceLog |= abs(temperature - last) >= 1; + if (forceLog) { + last = temperature; + } + pid.setTarget(temperature, forceLog); + } + + bool load(const String& programName) { + reset(); + + const String path = String("/program/") + programName + ".json"; + Log.info("Loading program: %s", programName.c_str()); + + File file = LittleFS.open(path, "r"); + if (!file) { + Log.error(" Can't open file: %s", path.c_str()); + reset(); + return false; + } + + JsonDocument json; + deserializeJson(json, file); + file.close(); + + if (!json.is()) { + Log.error(" Not a JsonObject."); + reset(); + return false; + } + + if (json["name"].is()) { + Log.info(" %-10s %s", "name", json["name"].as()); + name = json["name"].as(); + } else { + Log.error(" Missing attribute: %s", "name"); + reset(); + return false; + } + + if (json["iterations"].is()) { + Log.info(" %-10s %d", "iterations", json["iterations"].as()); + iterations = json["iterations"].as(); + } else { + Log.error(" Missing attribute: %s", "iterations"); + reset(); + return false; + } + + const JsonArray& jsonPoints = json["points"].as(); + pointCount = jsonPoints.size(); + if (pointCount > 0) { + Log.info(" %-10s %d", "points", pointCount); + pointList = static_cast(malloc(sizeof(ProgramPoint) * pointCount)); + if (pointList == nullptr) { + Log.error("Failed to allocate ProgramPoint memory"); + reset(); + return false; + } + point = pointList; + } else { + Log.error(" Missing attribute: %s", "points"); + reset(); + return false; + } + + int index = 0; + for (JsonVariant point : jsonPoints) { + if (!pointList[index].load(point, index)) { + reset(); + return false; + } + index++; + } + + Log.info("Program loaded: %s", path.c_str()); + return true; + } + +}; + +extern Program program; + +#endif diff --git a/src/node/Fermenter/ProgramPoint.h b/src/node/Fermenter/ProgramPoint.h new file mode 100644 index 0000000..c8ee42e --- /dev/null +++ b/src/node/Fermenter/ProgramPoint.h @@ -0,0 +1,130 @@ +#ifndef PROGRAM_POINT_H +#define PROGRAM_POINT_H + +#include +#include + +inline String durationString(const unsigned long millis) { + const unsigned long seconds = millis / 1000; + const unsigned long minutes = seconds / 60; + const unsigned long hours = minutes / 60; + const unsigned long days = hours / 24; + char buffer[15]; + if (days > 0) { + snprintf(buffer, sizeof buffer, "%d. %2d:%02d:%02d", days, hours % 24, minutes % 60, seconds % 60); + } else { + snprintf(buffer, sizeof buffer, "%d:%02d:%02d", hours % 24, minutes % 60, seconds % 60); + } + return {buffer}; +} + +class ProgramPoint { + +public: + + int index = -1; + + String name = ""; + + double startTemperature = 0; + + double endTemperature = 0; + + unsigned long startMillis = 0; + + unsigned long durationMillis = 0; + + bool load(const JsonVariant& point, const int index) { + reset(); + + this->index = index; + Log.info(" #%d:", index); + if (!point.is()) { + Log.error(" Not a JsonObject"); + reset(); + return false; + } + + if (point["name"].is()) { + Log.info(" %-10s %s", "name", point["name"].as()); + name = point["name"].as(); + } else { + Log.error(" Missing attribute: %s", "name"); + reset(); + return false; + } + + if (point["start"].is()) { + Log.info(" %-10s %.1f%cC", "start", point["start"].as(), 176); + startTemperature = point["start"].as(); + } else { + Log.error(" Missing attribute: %s", "start"); + reset(); + return false; + } + + if (point["end"].is()) { + Log.info(" %-10s %.1f%cC", "end", point["end"].as(), 176); + endTemperature = point["end"].as(); + } else { + Log.error(" Missing attribute: %s", "end"); + reset(); + return false; + } + + if (point["seconds"].is()) { + Log.info(" %-10s %d", "seconds", point["seconds"].as()); + durationMillis = point["seconds"].as() * 1000; + } else if (point["minutes"].is()) { + Log.info(" %-10s %d", "minutes", point["minutes"].as()); + durationMillis = point["minutes"].as() * 60 * 1000; + } else if (point["hours"].is()) { + Log.info(" %-10s %d", "hours", point["hours"].as()); + durationMillis = point["hours"].as() * 60 * 60 * 1000; + } else if (point["days"].is()) { + Log.info(" %-10s %d", "days", point["days"].as()); + durationMillis = point["days"].as() * 24 * 60 * 60 * 1000; + } else { + Log.error(" Missing attribute: %s", "seconds/minutes/hours/days"); + reset(); + return false; + } + + return true; + } + + void reset() { + index = -1; + name = ""; + startTemperature = 0; + endTemperature = 0; + startMillis = 0; + durationMillis = 0; + } + + void start(const unsigned long alreadyProgressedMillis) { + startMillis = max(1UL, millis() - alreadyProgressedMillis); + if (alreadyProgressedMillis == 0) { + Log.info("Starting ProgramPoint: #%d %s, %.1f%cC -> %.1f%cC: \"%s\"", index, durationString(durationMillis), startTemperature, 176, endTemperature, 176, name.c_str()); + } else { + Log.info("Resuming ProgramPoint: #%d %s, %.1f%cC -> %.1f%cC: \"%s\", resumeAt=%s", index, durationString(durationMillis), startTemperature, 176, endTemperature, 176, name.c_str(), durationString(alreadyProgressedMillis)); + } + } + + [[nodiscard]] bool isComplete() const { + return millis() - startMillis >= durationMillis; + } + + [[nodiscard]] unsigned long getProgressMillis() const { + return max(0UL, min(durationMillis, millis() - startMillis)); + } + + [[nodiscard]] double getTemperature() const { + const auto progressMillis = getProgressMillis(); + const auto progressRatio = static_cast(progressMillis) / static_cast(durationMillis); + return startTemperature + (endTemperature - startTemperature) * progressRatio; + } + +}; + +#endif diff --git a/src/node/Fermenter/config.cpp b/src/node/Fermenter/config.cpp index bd7d850..4566493 100644 --- a/src/node/Fermenter/config.cpp +++ b/src/node/Fermenter/config.cpp @@ -1,6 +1,7 @@ #ifdef NODE_FERMENTER -#include "Fermenter.h" +#include "config.h" +#include "pid.h" Config config("/config.json", configCollect, configApply); diff --git a/src/node/Fermenter/config.h b/src/node/Fermenter/config.h new file mode 100644 index 0000000..38e5f29 --- /dev/null +++ b/src/node/Fermenter/config.h @@ -0,0 +1,12 @@ +#ifndef CONFIG_H +#define CONFIG_H + +#include "patrix/Config.h" + +extern Config config; + +void configCollect(JsonDocument& json); + +void configApply(JsonDocument& json); + +#endif diff --git a/src/node/Fermenter/display.cpp b/src/node/Fermenter/display.cpp index 5ffc3c3..2bf2e26 100644 --- a/src/node/Fermenter/display.cpp +++ b/src/node/Fermenter/display.cpp @@ -1,6 +1,7 @@ #ifdef NODE_FERMENTER -#include "Fermenter.h" +#include "display.h" +#include "pid.h" LedControl display(D7, D5, D8, 1); @@ -16,8 +17,7 @@ void displayPrintf(const char* format, ...) { auto thisChar = *b; if (thisChar == 'z' || thisChar == 'Z') { thisChar = '2'; - } - else if (thisChar == 'i' || thisChar == 'I') { + } else if (thisChar == 'i' || thisChar == 'I') { thisChar = '1'; } const auto nextIsDot = *(b + 1) == '.'; @@ -47,8 +47,7 @@ void displayLoop() { if (displayModifyTarget != 0) { displayPrintf("ZIEL %4.1f", pid.getTarget()); - } - else { + } else { displayPrintf("%4.1f %4.1f", temperature.getValue(), pid.getTarget()); } } diff --git a/src/node/Fermenter/display.h b/src/node/Fermenter/display.h new file mode 100644 index 0000000..526190a --- /dev/null +++ b/src/node/Fermenter/display.h @@ -0,0 +1,14 @@ +#ifndef DISPLAY_H +#define DISPLAY_H + +#include + +extern unsigned long displayModifyTarget; + +extern LedControl display; + +void displayPrintf(const char* format, ...); + +void displayLoop(); + +#endif diff --git a/src/node/Fermenter/http.cpp b/src/node/Fermenter/http.cpp index a3656cb..9cfed2a 100644 --- a/src/node/Fermenter/http.cpp +++ b/src/node/Fermenter/http.cpp @@ -1,16 +1,19 @@ #ifdef NODE_FERMENTER -#include "Fermenter.h" +#include "http.h" +#include "config.h" +#include "pid.h" +#include "Program.h" void httpStatus(AsyncWebServerRequest* request) { JsonDocument json; - json["pid"]["p"] = pid.p; - json["pid"]["i"] = pid.i; - json["pid"]["d"] = pid.d; - json["pid"]["target"] = pid.getTarget(); - json["temperature"] = temperature.getValue(); + json["pid"]["p"] = pid.p; + json["pid"]["i"] = pid.i; + json["pid"]["d"] = pid.d; + json["pid"]["target"] = pid.getTarget(); + json["temperature"] = temperature.getValue(); json["heater"]["percent"] = heater.getPercent(); - json["heater"]["powerW"] = heater.getPercent() / 100.0 * HEATER_POWER_W; + json["heater"]["powerW"] = heater.getPercent() / 100.0 * HEATER_POWER_W; AsyncResponseStream* stream = request->beginResponseStream("application/json"); serializeJson(json, *stream); @@ -89,6 +92,43 @@ void httpConfigSet(AsyncWebServerRequest* request) { httpStatus(request); } +void httpProgramLoad(AsyncWebServerRequest* request) { + const auto nameParam = request->getParam("name"); + if (nameParam == nullptr) { + Log.error("Missing parameter: name (1)"); + return; + } + + const auto nameString = nameParam->value(); + if (nameString == nullptr) { + Log.error("Missing parameter: name (2)"); + return; + } + + program.wantedName = nameString; + request->send(200); +} + +void httpProgramStart(AsyncWebServerRequest* request) { + program.start(); + request->send(200); +} + +void httpProgramStop(AsyncWebServerRequest* request) { + program.stop(); + request->send(200); +} + +void httpProgramPause(AsyncWebServerRequest* request) { + program.pause(); + request->send(200); +} + +void httpProgramResume(AsyncWebServerRequest* request) { + program.resume(); + request->send(200); +} + void httpSetup2() { server.on("/status", httpStatus); server.on("/status/", httpStatus); @@ -96,6 +136,16 @@ void httpSetup2() { server.on("/target/add/", httpTargetAdd); server.on("/config/set", httpConfigSet); server.on("/config/set/", httpConfigSet); + server.on("/program/load", httpProgramLoad); + server.on("/program/load/", httpProgramLoad); + server.on("/program/start", httpProgramStart); + server.on("/program/start/", httpProgramStart); + server.on("/program/stop", httpProgramStop); + server.on("/program/stop/", httpProgramStop); + server.on("/program/pause", httpProgramPause); + server.on("/program/pause/", httpProgramPause); + server.on("/program/resume", httpProgramResume); + server.on("/program/resume/", httpProgramResume); } #endif diff --git a/src/node/Fermenter/http.h b/src/node/Fermenter/http.h new file mode 100644 index 0000000..a0c1cc5 --- /dev/null +++ b/src/node/Fermenter/http.h @@ -0,0 +1,12 @@ +#ifndef HTTP_H +#define HTTP_H + +#include "patrix/http.h" + +void httpStatus(AsyncWebServerRequest* request); + +void httpTargetAdd(AsyncWebServerRequest* request); + +void httpSetup2(); + +#endif diff --git a/src/node/Fermenter/pid.cpp b/src/node/Fermenter/pid.cpp index cd61550..3a01c6f 100644 --- a/src/node/Fermenter/pid.cpp +++ b/src/node/Fermenter/pid.cpp @@ -1,6 +1,7 @@ #ifdef NODE_FERMENTER -#include "Fermenter.h" +#include "pid.h" +#include "config.h" DS18B20 ds18b20( "DS18B20", diff --git a/src/node/Fermenter/pid.h b/src/node/Fermenter/pid.h new file mode 100644 index 0000000..64e5f6a --- /dev/null +++ b/src/node/Fermenter/pid.h @@ -0,0 +1,20 @@ +#ifndef PID_H +#define PID_H + +#include "patrix/DS18B20Sensor.h" +#include "patrix/PIDController.h" +#include "patrix/PWMOutput.h" + +#define HEATER_POWER_W 30 + +extern DS18B20 ds18b20; + +extern DS18B20Sensor temperature; + +extern PWMOutput heater; + +extern PIDController pid; + +void addTarget(double delta); + +#endif diff --git a/src/node/Fermenter/rotary.cpp b/src/node/Fermenter/rotary.cpp index fb782cf..1fc2ec1 100644 --- a/src/node/Fermenter/rotary.cpp +++ b/src/node/Fermenter/rotary.cpp @@ -1,12 +1,14 @@ #ifdef NODE_FERMENTER -#include "Fermenter.h" - -Rotary rotary(D1, D6, rotaryCallback); +#include "rotary.h" +#include "display.h" +#include "pid.h" void rotaryCallback(const int delta) { - addTarget(delta); + pid.addTarget(delta); displayModifyTarget = millis(); } +Rotary rotary(D1, D6, rotaryCallback); + #endif diff --git a/src/node/Fermenter/rotary.h b/src/node/Fermenter/rotary.h new file mode 100644 index 0000000..f8cb5bb --- /dev/null +++ b/src/node/Fermenter/rotary.h @@ -0,0 +1,8 @@ +#ifndef ROTARY_H +#define ROTARY_H + +#include "patrix/Rotary.h" + +extern Rotary rotary; + +#endif diff --git a/src/patrix/Config.h b/src/patrix/Config.h index 21e5457..f72d67f 100644 --- a/src/patrix/Config.h +++ b/src/patrix/Config.h @@ -1,8 +1,9 @@ -#ifndef CONFIG_H -#define CONFIG_H +#ifndef PATRIX_CONFIG_H +#define PATRIX_CONFIG_H #include #include + #include "mqtt.h" class Config { @@ -28,8 +29,8 @@ public: unsigned long writeDelayMillis = 10 * 1000UL; explicit Config(String path, const handle_json_t collect, const handle_json_t apply): - path(std::move(path)), - collect(collect), apply(apply) { + path(std::move(path)), + collect(collect), apply(apply) { // } diff --git a/src/patrix/PIDController.h b/src/patrix/PIDController.h index 1c9371a..7ba6b5b 100644 --- a/src/patrix/PIDController.h +++ b/src/patrix/PIDController.h @@ -43,18 +43,18 @@ public: double d = 0; PIDController(String targetName, String inputName, String outputName, const IValueSensor& sensor, PWMOutput& pwmOutput, const char* unit, const double minValue, const double maxValue, const double p, const double i, const double d) : - targetName(std::move(targetName)), - inputName(std::move(inputName)), - outputName(std::move(outputName)), - input(sensor), - output(pwmOutput), - unit(unit), - controller(), - minValue(minValue), - maxValue(maxValue), - p(p), - i(i), - d(d) { + targetName(std::move(targetName)), + inputName(std::move(inputName)), + outputName(std::move(outputName)), + input(sensor), + output(pwmOutput), + unit(unit), + controller(), + minValue(minValue), + maxValue(maxValue), + p(p), + i(i), + d(d) { // } @@ -90,9 +90,15 @@ public: return targetValue; } - double setTarget(double target) { - targetValue = max(minValue, min(maxValue, target)); - Log.info("PID %s = %.1f", targetName.c_str(), targetValue); + double setTarget(const double newTargetValue) { + return setTarget(newTargetValue, true); + } + + double setTarget(const double newTargetValue, const bool doLog) { + targetValue = max(minValue, min(maxValue, newTargetValue)); + if (doLog) { + Log.info("PID %s = %.1f", targetName.c_str(), targetValue); + } return targetValue; } diff --git a/src/patrix/wifi.cpp b/src/patrix/wifi.cpp index e3b580f..8ca980a 100644 --- a/src/patrix/wifi.cpp +++ b/src/patrix/wifi.cpp @@ -7,7 +7,7 @@ #define WIFI_SSID "HappyNet" #define WIFI_PASSWORD "1Grausame!Sackratte7" #define NTP_SERVER "107.189.12.98" /* pool.ntp.org */ -#define BOOT_DELAY_SEC 5 +#define BOOT_DELAY_SEC 1 auto wifiConnected = false;