diff --git a/lib/fetchOTA/fetchOTA.cpp b/lib/fetchOTA/fetchOTA.cpp new file mode 100644 index 0000000..9c03fad --- /dev/null +++ b/lib/fetchOTA/fetchOTA.cpp @@ -0,0 +1,76 @@ +#ifndef UNIT_TEST +#include + + +#include "Arduino.h" +#include +#include + +#include + + +OTA::OTA(String server_url, String currentVersion, String currentDeviceConfiguration) { + _serverUrl = server_url; + _currentVersion = parseVersion(currentVersion.c_str()); + _current_device_configuration = currentDeviceConfiguration; +} + + +Firmware OTA::getLatestVersionOnServer() { + HTTPClient http; + http.begin(_serverUrl); + int httpCode = http.GET(); + + if (httpCode != 200) { + return createErrorResponse("HTTP GET request failed with code " + String(httpCode)); + } + + String payload = http.getString(); + DynamicJsonDocument doc(4096); + DeserializationError error = deserializeJson(doc, payload); + + if (error) { + return createErrorResponse("Failed to deserialize JSON"); + } + + if (!doc.containsKey("Configurations")) { + return createErrorResponse("JSON response does not contain a 'Configurations' key"); + } + + std::vector configs; + for (JsonObject config : doc["Configurations"].as()) { + if (config.containsKey("Version") && config.containsKey("URL")) { + configs.push_back(Configuration{ + parseVersion(config["Version"]), + config["URL"], + config["Board"], + config["Configuration"] + }); + } + } + + if (configs.empty()) { + return createErrorResponse("No valid configuration found in the JSON response"); + } + + Configuration latest = getLatestConfiguration(configs.data(), configs.size()); + return Firmware{ + latest.version, + latest.url, + true, + "" + }; +} + +Firmware OTA::createErrorResponse(const String& errorMsg) { + return Firmware{ + Version{0, 0, 0}, + "", + false, + errorMsg + }; +} + + + +#endif \ No newline at end of file diff --git a/lib/fetchOTA/fetchOTA.h b/lib/fetchOTA/fetchOTA.h new file mode 100644 index 0000000..db6f7c1 --- /dev/null +++ b/lib/fetchOTA/fetchOTA.h @@ -0,0 +1,27 @@ +#ifndef UNIT_TEST +#include + +#ifdef UNIT_TEST + #include +#else + #include +#endif + + + +class OTA { + public: + OTA(String server_url, String currentVersion, String currentDeviceConfiguration); + Firmware getLatestVersionOnServer(); + + bool checkForUpdate(); + + private: + bool _isHTTPS = false; + String _serverUrl; + Version _currentVersion; + String _current_device_configuration; + Firmware createErrorResponse(const String& errorMsg); +}; + +#endif \ No newline at end of file diff --git a/lib/fetchOTA/utils.cpp b/lib/fetchOTA/utils.cpp new file mode 100644 index 0000000..878b073 --- /dev/null +++ b/lib/fetchOTA/utils.cpp @@ -0,0 +1,33 @@ +#include +#include + +Version parseVersion(const char *versionStr) { + Version v = {0, 0, 0}; // Initialize with default values + + // Buffer to check for any extra content + char extraContent; + // Try to extract three integers and check for non-numeric trailing characters + int parsedCount = sscanf(versionStr, "%d.%d.%d%c", &v.major, &v.minor, &v.patch, &extraContent); + + // If parsed count is not 3, or there is an extra character, mark as invalid + if (parsedCount > 3) { + v.major = v.minor = v.patch = 0; + } + + return v; +} +Configuration getLatestConfiguration(Configuration *configs, int count) { + Configuration latest = configs[0]; + for (int i = 1; i < count; i++) { + const Version ¤tVersion = configs[i].version; + const Version &latestVersion = latest.version; + + // Compare the versions stored in the Configuration structs + if (currentVersion.major > latestVersion.major || + (currentVersion.major == latestVersion.major && currentVersion.minor > latestVersion.minor) || + (currentVersion.major == latestVersion.major && currentVersion.minor == latestVersion.minor && currentVersion.patch > latestVersion.patch)) { + latest = configs[i]; + } + } + return latest; +} \ No newline at end of file diff --git a/lib/fetchOTA/utils.h b/lib/fetchOTA/utils.h new file mode 100644 index 0000000..5b2416c --- /dev/null +++ b/lib/fetchOTA/utils.h @@ -0,0 +1,29 @@ +#ifdef UNIT_TEST + #include + typedef std::string String; +#else + #include +#endif + +struct Version { + int major; + int minor; + int patch; +}; + +struct Firmware { + Version version; + String url; + bool valid; + String error; +}; + +struct Configuration { + Version version; + String url; + String Board; + String Config; +}; + +Version parseVersion(const char *versionStr); +Configuration getLatestConfiguration(Configuration *configs, int count); \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index e5ff436..d5da0aa 100644 --- a/platformio.ini +++ b/platformio.ini @@ -8,7 +8,25 @@ ; Please visit documentation for the other options and examples ; https://docs.platformio.org/page/projectconf.html -[env:ESP32] +[platformio] +default_envs = ESP32_INA233, ESP32_INA226 + +[env:ESP32_INA233] +platform = espressif32 +board = esp32dev +framework = arduino +monitor_speed = 115200 +lib_deps = + ottowinter/ESPAsyncWebServer-esphome@^3.3.0 + bblanchon/ArduinoJson@^6.21.3 + ArduinoLog +board_build.partitions = default.csv +upload_protocol = espota +upload_port = 192.168.5.181 +lib_extra_dirs = libs +build_flags = -Wall -Wextra + +[env:ESP32_INA226] platform = espressif32 board = esp32dev framework = arduino @@ -20,6 +38,12 @@ lib_deps = ArduinoLog board_build.partitions = default.csv upload_protocol = espota -upload_port = 192.168.5.242 +upload_port = 192.168.5.181 lib_extra_dirs = libs -build_flags = -Wall -Wextra \ No newline at end of file +build_flags = -Wall -Wextra -DUSE_INA226 + +[env:native] +platform = native +build_flags = -DUNIT_TEST -Ilib/fetchOTA/ +lib_deps = + fetchOTA diff --git a/src/main.cpp b/src/main.cpp index edbc30c..6b09bd9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -12,9 +12,6 @@ #include -#include "INA226.h" -#include "INA233.h" - #include "global_data/defines.h" #include "networking/networking.h" @@ -26,6 +23,7 @@ #include #include "networking/json_builder.h" #include "networking/responses.h" +#include Preferences prefs; @@ -57,33 +55,38 @@ void update_started() { } void run_ota(void* parameter) { - Serial.println("RUNNING OTA"); - // WiFiClient client; - httpUpdate.onStart(update_started); - httpUpdate.onEnd(update_finished); - httpUpdate.onProgress(update_progress); - httpUpdate.onError(update_error); - Serial.println("RUNNING OTA"); + OTA ota("https://iot.tobiasmaier.me/firmware/waterlevel", "1.1.1", "INA233"); + Firmware fw = ota.getLatestVersionOnServer(); + Log.verbose("we are done"); + Serial.printf("Firmware Info: Valid: %d, Version: %d.%d.%d, URL: %s\n", fw.valid, fw.version.major, fw.version.minor, fw.version.patch, fw.url.c_str()); + // Serial.println("RUNNING OTA"); + // // WiFiClient client; + // httpUpdate.onStart(update_started); + // httpUpdate.onEnd(update_finished); + // httpUpdate.onProgress(update_progress); + // httpUpdate.onError(update_error); + // Serial.println("RUNNING OTA"); - WiFiClientSecure client; - client.setInsecure(); + // WiFiClientSecure client; + // client.setInsecure(); - t_httpUpdate_return ret = httpUpdate.update(client, "https://iot.tobiasmaier.me/firmware/waterlevel/INA233/1_1_1.bin"); - // t_httpUpdate_return ret = httpUpdate.update(client, "https://iot.tobiasmaier.me", 443, "/firmware/waterlevel/INA233/1_0_1.bin"); + // t_httpUpdate_return ret = httpUpdate.update(client, "https://iot.tobiasmaier.me/firmware/waterlevel/INA233/1_1_1.bin"); + // // t_httpUpdate_return ret = httpUpdate.update(client, "https://iot.tobiasmaier.me", 443, "/firmware/waterlevel/INA233/1_0_1.bin"); - switch (ret) { - case HTTP_UPDATE_FAILED: - Serial.printf("HTTP_UPDATE_FAILED Error (%d): %s\n", httpUpdate.getLastError(), httpUpdate.getLastErrorString().c_str()); - break; + // switch (ret) { + // case HTTP_UPDATE_FAILED: + // Serial.printf("HTTP_UPDATE_FAILED Error (%d): %s\n", httpUpdate.getLastError(), httpUpdate.getLastErrorString().c_str()); + // break; - case HTTP_UPDATE_NO_UPDATES: - Serial.println("HTTP_UPDATE_NO_UPDATES"); - break; + // case HTTP_UPDATE_NO_UPDATES: + // Serial.println("HTTP_UPDATE_NO_UPDATES"); + // break; - case HTTP_UPDATE_OK: - Serial.println("HTTP_UPDATE_OK"); - break; - } + // case HTTP_UPDATE_OK: + // Serial.println("HTTP_UPDATE_OK"); + // break; + // } + vTaskDelete(NULL); } void setup() @@ -199,8 +202,7 @@ void setup() server.on("/ota", HTTP_GET, [](AsyncWebServerRequest* request) { Serial.println("OTA Task start"); - xTaskCreate(run_ota, "RunOTATask", 1024 * 8, NULL, 1, NULL); - + xTaskCreate(run_ota, "OTATask", 1024 * 8, NULL, 1, NULL); request->send(200, "text/plain", "OTA done"); }); diff --git a/src/networking/networking.cpp b/src/networking/networking.cpp index b99da87..feeb5d0 100644 --- a/src/networking/networking.cpp +++ b/src/networking/networking.cpp @@ -64,8 +64,8 @@ void wifi_task(void* parameter) wifi_data.network_name = WiFi.SSID(); wifi_data.ip_address = WiFi.localIP().toString(); - Log.verbose("RSSI: %F, IP Address, %p, SSID: %s", float(WiFi.RSSI()), WiFi.localIP(), prefs.getString(ssid_key, "NOSSID")); - Serial.println(WiFi.channel()); + // Log.verbose("RSSI: %F, IP Address, %p, SSID: %s", float(WiFi.RSSI()), WiFi.localIP(), prefs.getString(ssid_key, "NOSSID")); + // Serial.println(WiFi.channel()); delay(5000); } else { Log.verbose("Connecting to %s using password %s", prefs.getString(ssid_key, ""), prefs.getString(wifi_password_key, "")); @@ -87,7 +87,7 @@ void ethernet_task(void* parameter) ethernet_data.link = ETH.linkUp(); ethernet_data.rssi = ETH.linkSpeed(); ethernet_data.ip_address = ETH.localIP().toString(); - Log.verbose("Ethernet RSSI: %F, IP Address, %s, LINK: %s", float(ethernet_data.rssi), ethernet_data.ip_address, String(ethernet_data.link)); + // Log.verbose("Ethernet RSSI: %F, IP Address, %s, LINK: %s", float(ethernet_data.rssi), ethernet_data.ip_address, String(ethernet_data.link)); delay(60 * 1000); } } \ No newline at end of file diff --git a/src/sensor/sensor.cpp b/src/sensor/sensor.cpp index c06f3ae..3b01496 100644 --- a/src/sensor/sensor.cpp +++ b/src/sensor/sensor.cpp @@ -1,14 +1,15 @@ #include "../global_data/defines.h" -#include #include #include #include "Wire.h" #include "../global_data/global_data.h" -#ifdef USE_INA226s +#ifdef USE_INA226 +#include "INA226.h" INA226 ina_sensor(0x40); #else +#include INA233 ina_sensor(0x40); #endif @@ -60,8 +61,8 @@ void read_sensor_task(void* parameter) float min_water_level_mA = 4 + min_water_level_mA_over_zero; float max_water_level_mA = 4 + max_water_level_mA_over_zero; - Log.verbose("max_water_level_mA: %F", max_water_level_mA); - Log.verbose("min_water_level_mA_over_zero: %F", min_water_level_mA_over_zero); + // Log.verbose("max_water_level_mA: %F", max_water_level_mA); + // Log.verbose("min_water_level_mA_over_zero: %F", min_water_level_mA_over_zero); // Current over the 0 level of the water float shunt_current_over_zero = shunt_current - min_water_level_mA; @@ -81,10 +82,10 @@ void read_sensor_task(void* parameter) active_errors.current_high = shunt_current > 20.2; active_errors.voltage_low = bus_voltage < 23; active_errors.voltage_high = bus_voltage > 25; - Log.verbose("Shunt current: %F", shunt_current); - Log.verbose("Shunt voltage: %F", shunt_voltage); - Log.verbose("Bus voltage: %F", bus_voltage); - Log.verbose("cm_over_zero: %F", cm_over_zero); + // Log.verbose("Shunt current: %F", shunt_current); + // Log.verbose("Shunt voltage: %F", shunt_voltage); + // Log.verbose("Bus voltage: %F", bus_voltage); + // Log.verbose("cm_over_zero: %F", cm_over_zero); shunt_data.bus_voltage = bus_voltage; shunt_data.shunt_voltage = shunt_voltage; diff --git a/test/otaTests.cpp b/test/otaTests.cpp new file mode 100644 index 0000000..ae73a07 --- /dev/null +++ b/test/otaTests.cpp @@ -0,0 +1,100 @@ +#include +#include +#include + +void test_latest_configuration_major() { + Configuration configs[] = { + {Version{1, 9, 9}, "url1", "Board1", "Config1"}, + {Version{2, 8, 8}, "url2", "Board2", "Config2"}, + {Version{3, 5, 1}, "url3", "Board3", "Config3"} + }; + + Configuration latest = getLatestConfiguration(configs, 3); + TEST_ASSERT_EQUAL(latest.version.major, 3); + TEST_ASSERT_EQUAL(latest.version.minor, 5); + TEST_ASSERT_EQUAL(latest.version.patch, 1); +} + +void test_latest_configuration_minor() { + Configuration configs[] = { + {Version{1, 7, 9}, "url1", "Board1", "Config1"}, + {Version{1, 8, 8}, "url2", "Board2", "Config2"}, + {Version{1, 5, 1}, "url3", "Board3", "Config3"} + }; + + Configuration latest = getLatestConfiguration(configs, 3); + TEST_ASSERT_EQUAL(latest.version.major, 1); + TEST_ASSERT_EQUAL(latest.version.minor, 8); + TEST_ASSERT_EQUAL(latest.version.patch, 8); +} + +void test_latest_configuration_patch() { + Configuration configs[] = { + {Version{1, 4, 4}, "url1", "Board1", "Config1"}, + {Version{1, 4, 7}, "url2", "Board2", "Config2"}, + {Version{1, 4, 2}, "url3", "Board3", "Config3"} + }; + + Configuration latest = getLatestConfiguration(configs, 3); + TEST_ASSERT_EQUAL(latest.version.major, 1); + TEST_ASSERT_EQUAL(latest.version.minor, 4); + TEST_ASSERT_EQUAL(latest.version.patch, 7); +} + + +void test_parse_version_valid_input() { + Version v = parseVersion("1.2.3"); + TEST_ASSERT_EQUAL(1, v.major); + TEST_ASSERT_EQUAL(2, v.minor); + TEST_ASSERT_EQUAL(3, v.patch); +} + +void test_parse_version_valid_input_2() { + Version v = parseVersion("61.5231.4212"); + TEST_ASSERT_EQUAL(61, v.major); + TEST_ASSERT_EQUAL(5231, v.minor); + TEST_ASSERT_EQUAL(4212, v.patch); +} + +void test_parse_version_missing_parts() { + Version v = parseVersion("1.2"); + TEST_ASSERT_EQUAL(1, v.major); + TEST_ASSERT_EQUAL(2, v.minor); + TEST_ASSERT_EQUAL(0, v.patch); +} + +void test_parse_version_more_missing_parts() { + Version v = parseVersion("1"); + TEST_ASSERT_EQUAL(1, v.major); + TEST_ASSERT_EQUAL(0, v.minor); + TEST_ASSERT_EQUAL(0, v.patch); +} + +void test_parse_version_invalid_format() { + Version v = parseVersion("invalid"); + TEST_ASSERT_EQUAL(0, v.major); + TEST_ASSERT_EQUAL(0, v.minor); + TEST_ASSERT_EQUAL(0, v.patch); +} + +void test_parse_version_extra_content() { + Version v = parseVersion("1.2.3-extra"); + TEST_ASSERT_EQUAL(0, v.major); + TEST_ASSERT_EQUAL(0, v.minor); + TEST_ASSERT_EQUAL(0, v.patch); +} + + +int main() { + UNITY_BEGIN(); + RUN_TEST(test_latest_configuration_major); + RUN_TEST(test_latest_configuration_minor); + RUN_TEST(test_latest_configuration_patch); + RUN_TEST(test_parse_version_valid_input); + RUN_TEST(test_parse_version_missing_parts); + RUN_TEST(test_parse_version_invalid_format); + RUN_TEST(test_parse_version_extra_content); + RUN_TEST(test_parse_version_valid_input_2); + RUN_TEST(test_parse_version_more_missing_parts); + UNITY_END(); +} \ No newline at end of file