This commit is contained in:
76
lib/fetchOTA/fetchOTA.cpp
Normal file
76
lib/fetchOTA/fetchOTA.cpp
Normal file
@@ -0,0 +1,76 @@
|
||||
#ifndef UNIT_TEST
|
||||
#include <fetchOTA.h>
|
||||
|
||||
|
||||
#include "Arduino.h"
|
||||
#include <ArduinoJson.h>
|
||||
#include <HTTPClient.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
|
||||
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<Configuration> configs;
|
||||
for (JsonObject config : doc["Configurations"].as<JsonArray>()) {
|
||||
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
|
||||
27
lib/fetchOTA/fetchOTA.h
Normal file
27
lib/fetchOTA/fetchOTA.h
Normal file
@@ -0,0 +1,27 @@
|
||||
#ifndef UNIT_TEST
|
||||
#include <utils.h>
|
||||
|
||||
#ifdef UNIT_TEST
|
||||
#include<ArduinoFake.h>
|
||||
#else
|
||||
#include <Arduino.h>
|
||||
#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
|
||||
33
lib/fetchOTA/utils.cpp
Normal file
33
lib/fetchOTA/utils.cpp
Normal file
@@ -0,0 +1,33 @@
|
||||
#include <utils.h>
|
||||
#include <stdio.h>
|
||||
|
||||
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;
|
||||
}
|
||||
29
lib/fetchOTA/utils.h
Normal file
29
lib/fetchOTA/utils.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#ifdef UNIT_TEST
|
||||
#include <string>
|
||||
typedef std::string String;
|
||||
#else
|
||||
#include <WString.h>
|
||||
#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);
|
||||
@@ -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
|
||||
build_flags = -Wall -Wextra -DUSE_INA226
|
||||
|
||||
[env:native]
|
||||
platform = native
|
||||
build_flags = -DUNIT_TEST -Ilib/fetchOTA/
|
||||
lib_deps =
|
||||
fetchOTA
|
||||
|
||||
56
src/main.cpp
56
src/main.cpp
@@ -12,9 +12,6 @@
|
||||
|
||||
#include <ArduinoOTA.h>
|
||||
|
||||
#include "INA226.h"
|
||||
#include "INA233.h"
|
||||
|
||||
#include "global_data/defines.h"
|
||||
|
||||
#include "networking/networking.h"
|
||||
@@ -26,6 +23,7 @@
|
||||
#include <Preferences.h>
|
||||
#include "networking/json_builder.h"
|
||||
#include "networking/responses.h"
|
||||
#include <fetchOTA.h>
|
||||
|
||||
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");
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,15 @@
|
||||
#include "../global_data/defines.h"
|
||||
#include <INA233.h>
|
||||
#include <Preferences.h>
|
||||
#include <ArduinoLog.h>
|
||||
#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.h>
|
||||
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;
|
||||
|
||||
100
test/otaTests.cpp
Normal file
100
test/otaTests.cpp
Normal file
@@ -0,0 +1,100 @@
|
||||
#include <unity.h>
|
||||
#include <string.h>
|
||||
#include <utils.cpp>
|
||||
|
||||
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();
|
||||
}
|
||||
Reference in New Issue
Block a user