From 9cfd7f2d0327c8ad46ef7fa3fa28de494fa506cc Mon Sep 17 00:00:00 2001 From: mwood77 <43637076+mwood77@users.noreply.github.com> Date: Mon, 12 Feb 2024 14:26:29 +0100 Subject: [PATCH] Refactor JSON implementation & change local storage setup (#26) * refactor json implementation & change local storage to use json * Update LED control messages * Update configurable values in main.cpp * Add CI workflow for PlatformIO and remove unnecessary Firefox installation --- .github/workflows/ci.yml | 26 ++ .github/workflows/selenium-web.yml | 2 - data/settings.json | 1 + data/settings.txt | 1 - platformio.ini | 2 +- src/platformio/osww-server/src/main.cpp | 288 +++++++++--------- .../osww-server/src/utils/LedControl.cpp | 4 +- 7 files changed, 173 insertions(+), 151 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 data/settings.json delete mode 100644 data/settings.txt diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..bfba55c --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,26 @@ +name: PlatformIO CI + +on: + pull_request: + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - uses: actions/cache@v3 + with: + path: | + ~/.cache/pip + ~/.platformio/.cache + key: ${{ runner.os }}-pio + - uses: actions/setup-python@v4 + with: + python-version: '3.9' + - name: Install PlatformIO Core + run: pip install --upgrade platformio + + - name: Build PlatformIO Project + run: pio run \ No newline at end of file diff --git a/.github/workflows/selenium-web.yml b/.github/workflows/selenium-web.yml index e2a8103..f0ccbe1 100644 --- a/.github/workflows/selenium-web.yml +++ b/.github/workflows/selenium-web.yml @@ -17,8 +17,6 @@ jobs: with: node-version: 18 - # - run: sudo apt-get install firefox - - run: npm i selenium-webdriver - run: npx node .github/workflows/test-ali-links.mjs diff --git a/data/settings.json b/data/settings.json new file mode 100644 index 0000000..28a31ce --- /dev/null +++ b/data/settings.json @@ -0,0 +1 @@ +{"savedStatus":"Stopped","savedTPD":"220","savedHour":"00","savedMinutes":"00","savedTimerState":"0","savedDirection":"BOTH"} \ No newline at end of file diff --git a/data/settings.txt b/data/settings.txt deleted file mode 100644 index 81cee8b..0000000 --- a/data/settings.txt +++ /dev/null @@ -1 +0,0 @@ -Stopped,480,00,00,0,BOTH \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index 2b57f06..28d5fe0 100644 --- a/platformio.ini +++ b/platformio.ini @@ -15,7 +15,7 @@ description = IoT controlled smart watch winder platform = espressif32@^5.2.0 board = esp32doit-devkit-v1 framework = arduino -upload_speed = 921600 +upload_speed = 115200 monitor_speed = 115200 build_src_filter = +<*> -<./angular/> board_build.filesystem = littlefs diff --git a/src/platformio/osww-server/src/main.cpp b/src/platformio/osww-server/src/main.cpp index 6381052..72a61d9 100644 --- a/src/platformio/osww-server/src/main.cpp +++ b/src/platformio/osww-server/src/main.cpp @@ -17,12 +17,16 @@ * ********************************* CONFIGURABLES ************************************* * ************************************************************************************* * + * If you purchased the motor listed in the guide / Bill Of Materials, these default values are correct! + * * durationInSecondsToCompleteOneRevolution = how long it takes the watch to complete one rotation on the winder. - * If you purchased the motor listed in the guide / Bill Of Materials, then this default value is correct! * directionalPinA = this is the pin that's wired to IN1 on your L298N circuit board * directionalPinB = this is the pin that's wired to IN2 on your L298N circuit board * ledPin = by default this is set to the ESP32's onboard LED. If you've wired an external LED, change this value to the GPIO pin the LED is wired to. * externalButton = OPTIONAL - If you want to use an external ON/OFF button, connect it to this pin 13. If you need to use another pin, change the value here. + * + * If you're using a NeoPixel equipped board, you'll need to change directionalPinA, directionalPinB and ledPin (pin 18 on most, I think) to appropriate GPIOs. + * Faiulre to set these pins on NeoPixel boards will result in kernel panics. */ int durationInSecondsToCompleteOneRevolution = 8; int directionalPinA = 25; @@ -39,7 +43,7 @@ int externalButton = 13; * DO NOT CHANGE THESE VARIABLES! */ String timeURL = "http://worldtimeapi.org/api/ip"; -String settingsFile = "/settings.txt"; +String settingsFile = "/settings.json"; unsigned long rtc_offset; unsigned long rtc_epoch; unsigned long estimatedRoutineFinishEpoch; @@ -126,10 +130,10 @@ void getTime() if (httpCode > 0) { - DynamicJsonDocument doc(2048); - deserializeJson(doc, http.getStream()); - const unsigned long epoch = doc["unixtime"]; - const unsigned long offset = doc["raw_offset"]; + JsonDocument json; + deserializeJson(json, http.getStream()); + const unsigned long epoch = json["unixtime"]; + const unsigned long offset = json["raw_offset"]; rtc.offset = offset; rtc.setTime(epoch); @@ -144,24 +148,32 @@ void getTime() * @param file_name fully qualified name of file to load * @return contents of file as a single string */ -String loadConfigVarsFromFile(String file_name) +void loadConfigVarsFromFile(String file_name) { String result = ""; File this_file = LittleFS.open(file_name, "r"); - if (!this_file) + JsonDocument json; + DeserializationError error = deserializeJson(json, this_file); + + if (!this_file || error) { Serial.println("[STATUS] - Failed to open configuration file, returning empty result"); - return result; } while (this_file.available()) { result += (char)this_file.read(); } + + userDefinedSettings.status = json["savedStatus"].as(); // Winding || Stopped = 7char + userDefinedSettings.rotationsPerDay = json["savedTPD"].as(); // min = 100 || max = 960 + userDefinedSettings.hour = json["savedHour"].as(); // 00 + userDefinedSettings.minutes = json["savedMinutes"].as(); // 00 + userDefinedSettings.timerEnabled = json["savedTimerState"].as(); // 0 || 1 + userDefinedSettings.direction = json["savedDirection"].as(); // CW || CCW || BOTH this_file.close(); - return result; } /** @@ -171,19 +183,26 @@ String loadConfigVarsFromFile(String file_name) * @param contents entire contents to write to file * @return true if successfully wrote to file; else false */ -bool writeConfigVarsToFile(String file_name, String contents) +bool writeConfigVarsToFile(String file_name, const RUNTIME_VARS& userDefinedSettings) { File this_file = LittleFS.open(file_name, "w"); + JsonDocument json; + if (!this_file) { Serial.println("[STATUS] - Failed to open configuration file"); return false; } - int bytesWritten = this_file.print(contents); + json["savedStatus"] = userDefinedSettings.status; + json["savedTPD"] = userDefinedSettings.rotationsPerDay; + json["savedHour"] = userDefinedSettings.hour; + json["savedMinutes"] = userDefinedSettings.minutes; + json["savedTimerState"] = userDefinedSettings.timerEnabled; + json["savedDirection"] = userDefinedSettings.direction; - if (bytesWritten == 0) + if (serializeJson(json, this_file) == 0) { Serial.println("[STATUS] - Failed to write to configuration file"); return false; @@ -193,28 +212,6 @@ bool writeConfigVarsToFile(String file_name, String contents) return true; } -/** - * Parses substrings from user settings file & maps to runtime variables - * - * @param settings non-delimited string of settings - */ -void parseSettings(String settings) -{ - String savedStatus = settings.substring(0, 7); // Winding || Stopped = 7char - String savedTPD = settings.substring(8, 11); // min = 100 || max = 960 - String savedHour = settings.substring(12, 14); // 00 - String savedMinutes = settings.substring(15, 17); // 00 - String savedTimerState = settings.substring(18, 19); // 0 || 1 - String savedDirection = settings.substring(20); // CW || CCW || BOTH - - userDefinedSettings.status = savedStatus; - userDefinedSettings.rotationsPerDay = savedTPD; - userDefinedSettings.hour = savedHour; - userDefinedSettings.minutes = savedMinutes; - userDefinedSettings.timerEnabled = savedTimerState; - userDefinedSettings.direction = savedDirection; -} - /** * 404 handler for webserver */ @@ -239,129 +236,131 @@ void startWebserver() { server.on("/api/status", HTTP_GET, [](AsyncWebServerRequest *request) - { - AsyncResponseStream *response = request->beginResponseStream("application/json"); - DynamicJsonDocument json(1024); - json["status"] = userDefinedSettings.status; - json["rotationsPerDay"] = userDefinedSettings.rotationsPerDay; - json["direction"] = userDefinedSettings.direction; - json["hour"] = userDefinedSettings.hour; - json["minutes"] = userDefinedSettings.minutes; - json["durationInSecondsToCompleteOneRevolution"] = durationInSecondsToCompleteOneRevolution; - json["startTimeEpoch"] = startTimeEpoch; - json["currentTimeEpoch"] = rtc.getEpoch(); - json["estimatedRoutineFinishEpoch"] = estimatedRoutineFinishEpoch; - json["winderEnabled"] = userDefinedSettings.winderEnabled; - json["timerEnabled"] = userDefinedSettings.timerEnabled; - json["db"] = WiFi.RSSI(); - serializeJson(json, *response); - - request->send(response); - - // Update RTC time ref - getTime(); }); + { + AsyncResponseStream *response = request->beginResponseStream("application/json"); + JsonDocument json; + json["status"] = userDefinedSettings.status; + json["rotationsPerDay"] = userDefinedSettings.rotationsPerDay; + json["direction"] = userDefinedSettings.direction; + json["hour"] = userDefinedSettings.hour; + json["minutes"] = userDefinedSettings.minutes; + json["durationInSecondsToCompleteOneRevolution"] = durationInSecondsToCompleteOneRevolution; + json["startTimeEpoch"] = startTimeEpoch; + json["currentTimeEpoch"] = rtc.getEpoch(); + json["estimatedRoutineFinishEpoch"] = estimatedRoutineFinishEpoch; + json["winderEnabled"] = userDefinedSettings.winderEnabled; + json["timerEnabled"] = userDefinedSettings.timerEnabled; + json["db"] = WiFi.RSSI(); + serializeJson(json, *response); + + request->send(response); + + // Update RTC time ref + getTime(); + }); server.on("/api/power", HTTP_POST, [](AsyncWebServerRequest *request) - { - int params = request->params(); - - for ( int i = 0; i < params; i++ ) { - AsyncWebParameter* p = request->getParam(i); - - if( strcmp(p->name().c_str(), "winderEnabled") == 0 ) { - userDefinedSettings.winderEnabled = p->value().c_str(); - - if (userDefinedSettings.winderEnabled == "0") { - Serial.println("[STATUS] - Switched off!"); - userDefinedSettings.status = "Stopped"; - routineRunning = false; - motor.stop(); - } - } - } - - request->send(204); }); + { + int params = request->params(); - server.on("/api/update", HTTP_POST, [](AsyncWebServerRequest *request) - { - int params = request->params(); - - for ( int i = 0; i < params; i++ ) { - AsyncWebParameter* p = request->getParam(i); - - if( strcmp(p->name().c_str(), "rotationDirection") == 0 ) { - userDefinedSettings.direction = p->value().c_str(); - - motor.stop(); - delay(250); - - // Update motor direction - if (userDefinedSettings.direction == "CW" ) { - motor.setMotorDirection(1); - } else if (userDefinedSettings.direction == "CCW") { - motor.setMotorDirection(0); - } - - Serial.println("[STATUS] - direction set: " + userDefinedSettings.direction); - } - - if( strcmp(p->name().c_str(), "tpd") == 0 ) { - const char* newTpd = p->value().c_str(); - - if (strcmp(newTpd, userDefinedSettings.rotationsPerDay.c_str()) != 0) { - userDefinedSettings.rotationsPerDay = p->value().c_str(); - - unsigned long finishTime = calculateWindingTime(); - estimatedRoutineFinishEpoch = finishTime; - } - } - - if( strcmp(p->name().c_str(), "hour") == 0 ) { - userDefinedSettings.hour = p->value().c_str(); - } - - if( strcmp(p->name().c_str(), "timerEnabled") == 0 ) { - userDefinedSettings.timerEnabled = p->value().c_str(); + for ( int i = 0; i < params; i++ ) { + AsyncWebParameter* p = request->getParam(i); + + if( strcmp(p->name().c_str(), "winderEnabled") == 0 ) { + userDefinedSettings.winderEnabled = p->value().c_str(); + + if (userDefinedSettings.winderEnabled == "0") { + Serial.println("[STATUS] - Switched off!"); + userDefinedSettings.status = "Stopped"; + routineRunning = false; + motor.stop(); + } + } } - if( strcmp(p->name().c_str(), "minutes") == 0 ) { - userDefinedSettings.minutes = p->value().c_str(); - } + request->send(204); + }); + + server.on("/api/update", HTTP_POST, [](AsyncWebServerRequest *request) + { + int params = request->params(); + + for ( int i = 0; i < params; i++ ) { + AsyncWebParameter* p = request->getParam(i); - if( strcmp(p->name().c_str(), "action") == 0) { - if ( strcmp(p->value().c_str(), "START") == 0 ) { - if (!routineRunning) { - userDefinedSettings.status = "Winding"; - beginWindingRoutine(); - } - } else { - motor.stop(); - routineRunning = false; - userDefinedSettings.status = "Stopped"; - } - } - } + if( strcmp(p->name().c_str(), "rotationDirection") == 0 ) { + userDefinedSettings.direction = p->value().c_str(); - String configs = userDefinedSettings.status + "," + userDefinedSettings.rotationsPerDay + "," + userDefinedSettings.hour + "," + userDefinedSettings.minutes + "," + userDefinedSettings.timerEnabled + "," + userDefinedSettings.direction; + motor.stop(); + delay(250); + + // Update motor direction + if (userDefinedSettings.direction == "CW" ) { + motor.setMotorDirection(1); + } else if (userDefinedSettings.direction == "CCW") { + motor.setMotorDirection(0); + } + + Serial.println("[STATUS] - direction set: " + userDefinedSettings.direction); + } + + if( strcmp(p->name().c_str(), "tpd") == 0 ) { + const char* newTpd = p->value().c_str(); + + if (strcmp(newTpd, userDefinedSettings.rotationsPerDay.c_str()) != 0) { + userDefinedSettings.rotationsPerDay = p->value().c_str(); + + unsigned long finishTime = calculateWindingTime(); + estimatedRoutineFinishEpoch = finishTime; + } + } + + if( strcmp(p->name().c_str(), "hour") == 0 ) { + userDefinedSettings.hour = p->value().c_str(); + } + + if( strcmp(p->name().c_str(), "timerEnabled") == 0 ) { + userDefinedSettings.timerEnabled = p->value().c_str(); + } - bool writeSuccess = writeConfigVarsToFile(settingsFile, configs); + if( strcmp(p->name().c_str(), "minutes") == 0 ) { + userDefinedSettings.minutes = p->value().c_str(); + } + + if( strcmp(p->name().c_str(), "action") == 0) { + if ( strcmp(p->value().c_str(), "START") == 0 ) { + if (!routineRunning) { + userDefinedSettings.status = "Winding"; + beginWindingRoutine(); + } + } else { + motor.stop(); + routineRunning = false; + userDefinedSettings.status = "Stopped"; + } + } + } + + bool writeSuccess = writeConfigVarsToFile(settingsFile, userDefinedSettings); - if ( !writeSuccess ) { - request->send(500); - } + if ( !writeSuccess ) { + request->send(500); + } - request->send(204); }); + request->send(204); + }); server.on("/api/reset", HTTP_GET, [](AsyncWebServerRequest *request) - { + { Serial.println("[STATUS] - Received reset command"); AsyncResponseStream *response = request->beginResponseStream("application/json"); - DynamicJsonDocument json(1024); + JsonDocument json; json["status"] = "Resetting"; serializeJson(json, *response); request->send(response); - - reset = true; }); + + reset = true; + }); server.serveStatic("/css/", LittleFS, "/css/").setCacheControl("max-age=31536000"); server.serveStatic("/js/", LittleFS, "/js/").setCacheControl("max-age=31536000"); @@ -419,7 +418,7 @@ void triggerLEDCondition(int blinkState) /** * This is a non-block button listener function. * Credit to github OSWW ontribution from user @danagarcia - * + * * @param pauseInSeconds the amount of time to pause and listen */ void awaitWhileListening(int pauseInSeconds) @@ -487,8 +486,7 @@ void setup() Serial.println("[STATUS] - connected to saved network"); // retrieve & read saved settings - String savedSettings = loadConfigVarsFromFile(settingsFile); - parseSettings(savedSettings); + loadConfigVarsFromFile(settingsFile); if (!MDNS.begin("winderoo")) { @@ -597,6 +595,6 @@ void loop() { triggerLEDCondition(3); } - + wm.process(); } diff --git a/src/platformio/osww-server/src/utils/LedControl.cpp b/src/platformio/osww-server/src/utils/LedControl.cpp index 0b12715..75a135a 100644 --- a/src/platformio/osww-server/src/utils/LedControl.cpp +++ b/src/platformio/osww-server/src/utils/LedControl.cpp @@ -26,7 +26,7 @@ void LedControl::pwm() void LedControl::slowBlink() { // Slow blink to confirm success & restart - Serial.println("slow blink"); + Serial.println("[STATUS] - slow blink"); for (int dutyCycle = 0; dutyCycle <= 4; dutyCycle++) { @@ -48,7 +48,7 @@ void LedControl::slowBlink() void LedControl::fastBlink() { // Fast blink to confirm resetting - Serial.println("slow blink"); + Serial.println("[STATUS] - fast blink"); for (int i = 0; i < 12; i++) {