diff --git a/data/logic.js b/data/logic.js index e69de29..e774409 100644 --- a/data/logic.js +++ b/data/logic.js @@ -0,0 +1,48 @@ +window.addEventListener('DOMContentLoaded', (event) => { + // URL of your API + const apiUrl = '/sensor_data'; + + function fetchData() { + // Fetching data from the API + fetch(apiUrl) + .then(response => response.json()) + .then(data => { + document.getElementById('voltage').textContent = roundToTwo(data.voltage)+ ' V' || 'N/A'; + document.getElementById('current').textContent = roundToTwo(data.current)+ ' mA' || 'N/A'; + }) + .catch(error => { + console.error("There was an error fetching data from the API", error); + }); + } + + fetchData(); // fetch immediately on page load + setInterval(fetchData, 5000); // fetch every 5 seconds +}); + +window.addEventListener('DOMContentLoaded', (event) => { + const apiUrl = '/network_info'; + + function fetchData() { + fetch(apiUrl) + .then(response => response.json()) + .then(data => { + document.getElementById('wifi_ip').textContent = data.wifi.ip || 'N/A'; + document.getElementById('wifi_rssi').textContent = data.wifi.rssi || 'N/A'; + document.getElementById('wifi_link').textContent = data.wifi.link || 'N/A'; + document.getElementById('wifi_ssid').textContent = data.wifi.ssid || 'N/A'; + document.getElementById('eth_link').textContent = data.ethernet.link || 'N/A'; + document.getElementById('eth_ip').textContent = data.ethernet.ip || 'N/A'; + document.getElementById('eth_speed').textContent = data.ethernet.rssi || 'N/A'; + }) + .catch(error => { + console.error("There was an error fetching data from the API", error); + }); + } + + fetchData(); // fetch immediately on page load + setInterval(fetchData, 5000); // fetch every 5 seconds +}); + +function roundToTwo(num) { + return Math.round(num*100)/100; +} \ No newline at end of file diff --git a/data/settings.html b/data/settings.html index a61ceb0..6bc8904 100644 --- a/data/settings.html +++ b/data/settings.html @@ -1,14 +1,7 @@ -” + - - @@ -55,6 +48,11 @@
WiFi Settings +

+ +
+

+

diff --git a/data/status.html b/data/status.html index 6f2750b..9a1ceb3 100644 --- a/data/status.html +++ b/data/status.html @@ -2,8 +2,7 @@ - - + @@ -34,15 +33,32 @@ Voltage: - 12.12V + WAITING Current: - 10 mA + WAITING + + + +

+ +
+
+
+
+
25%
+
2500 / 10000
+
+ +
+ + +
@@ -69,49 +85,57 @@
-

Connection Info

+

WiFi

- - + + - - + + - + + + + + + + +
Type: WiFiLink:
IP: 127.0.0.1SSID: XX
RSSI: goodXX
IP: 127.0.0.1
+
+ +
+
+

Ethernet

+
+ + + + + + + + + + + + + +
Link:
Speed: XX
IP: xxx
-
- - - -
-
- -
-
-
-
-
25%
-
2500 / 10000
-
- -
- - - - - \ No newline at end of file + \ No newline at end of file diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..56fa415 --- /dev/null +++ b/readme.md @@ -0,0 +1,11 @@ +# TODO + +- Water height calculation +- Water height config +- Return wather height in API +- Return Telementry in API + + +Config fields: +- ssid +- wifi_password \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 7732b14..df5e546 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -27,8 +27,10 @@ #define ssid_key "ssid" #define wifi_password_key "wifi_password" -//Calibration variables -float zero_value = 0.03; //Measured shunt voltage with nothing connected, used to fix measuring offset +extern "C" int rom_phy_get_vdd33(); + +// Calibration variables +float zero_value = 0.03; // Measured shunt voltage with nothing connected, used to fix measuring offset float max_water_level_cm = 200; float min_water_level_cm = 0; float sensor_range = 2.0; @@ -38,17 +40,27 @@ bool voltage_low = false; bool current_high = false; bool current_low = false; -float rssi; -uint32_t ip_address; uint8_t failed_connection_attempts = 0; -struct SensorData { +struct SensorData +{ int percentage; float voltage; float current; float water_height; }; +struct NetworkData +{ + String ip_address; + bool link; + float rssi; + String network_name; +}; + +NetworkData wifi_data; +NetworkData ethernet_data; + SensorData current_data = SensorData{-1, -1, -1, -1}; int64_t mac_address = ESP.getEfuseMac(); @@ -60,79 +72,109 @@ Preferences prefs; AsyncWebServer server(80); #define FORMAT_LITTLEFS_IF_FAILED true - -void display_percentage(int percentage){ +void display_percentage(int percentage) +{ digitalWrite(LED_RED, 0); - - if (percentage > 20){ + + if (percentage > 20) + { digitalWrite(LED_1, 1); - } else { + } + else + { digitalWrite(LED_1, 0); } - if (percentage > 40){ + if (percentage > 40) + { digitalWrite(LED_2, 1); - } else { + } + else + { digitalWrite(LED_2, 0); } - if (percentage > 60){ + if (percentage > 60) + { digitalWrite(LED_3, 1); - } else { + } + else + { digitalWrite(LED_3, 0); } - if (percentage > 80){ + if (percentage > 80) + { digitalWrite(LED_4, 1); - } else { + } + else + { digitalWrite(LED_4, 0); } - if (percentage > 95){ + if (percentage > 95) + { digitalWrite(LED_5, 1); - } else { + } + else + { digitalWrite(LED_5, 0); } } -void display_error_code(byte err_code) { +void display_error_code(byte err_code) +{ digitalWrite(LED_RED, 1); digitalWrite(LED_1, bitRead(err_code, 0)); digitalWrite(LED_2, bitRead(err_code, 1)); digitalWrite(LED_3, bitRead(err_code, 2)); digitalWrite(LED_4, bitRead(err_code, 3)); digitalWrite(LED_5, bitRead(err_code, 4)); - } -bool is_error(){ +bool is_error() +{ return voltage_high || voltage_low || current_high || current_low; } -void printSuffix(Print* _logOutput, int logLevel) { +void printSuffix(Print *_logOutput, int logLevel) +{ _logOutput->print(CR); } -void print_prefix(Print* _logOutput, int logLevel) { - _logOutput->print("WATERMETER - C"); - _logOutput->print(xPortGetCoreID()); - _logOutput->print(" - "); - _logOutput->print(pcTaskGetName(xTaskGetCurrentTaskHandle())); - _logOutput->print(" - "); +void print_prefix(Print *_logOutput, int logLevel) +{ + _logOutput->print("WATERMETER - C"); + _logOutput->print(xPortGetCoreID()); + _logOutput->print(" - "); + _logOutput->print(pcTaskGetName(xTaskGetCurrentTaskHandle())); + _logOutput->print(" - "); } -void display_task(void * parameter ){ - while (true) { - if (!is_error()){ +void display_task(void *parameter) +{ + while (true) + { + if (!is_error()) + { // We have no error, refresh status display and wait half a second display_percentage(current_data.percentage); delay(1000); - } else { + } + else + { Log.verbose("Error detected"); // We have an error, display error code for 3 seconds and then water level for 3 seconds - if (voltage_low) { + if (voltage_low) + { display_error_code(1); - } else if (voltage_high){ + } + else if (voltage_high) + { display_error_code(2); - } else if (current_low) { + } + else if (current_low) + { display_error_code(3); - } else if (current_high){ + } + else if (current_high) + { display_error_code(4); } delay(3000); @@ -140,19 +182,24 @@ void display_task(void * parameter ){ delay(3000); } } - } -void wifi_task(void * parameter ){ +void wifi_task(void *parameter) +{ Log.verbose("Starting WiFi Task"); - while (true) { - if (prefs.getString(ssid_key, "") == "" || failed_connection_attempts > 5 ) { - if (failed_connection_attempts > 5) { + while (true) + { + if (prefs.getString(ssid_key, "") == "" || failed_connection_attempts > 5) + { + wifi_data.link = false; + if (failed_connection_attempts > 5) + { Log.verbose("Failed to connecto to currently saved SSID, starting SoftAP"); - } else { + } + else + { Log.verbose("No SSID saved, starting SoftAP"); } - String ap_ssid = "Watermeter-" + String(mac_address); WiFi.softAP(ap_ssid, ""); @@ -160,23 +207,28 @@ void wifi_task(void * parameter ){ Log.verbose("[WIFI_TASK] Waiting for SSID now..."); String old_ssid = prefs.getString(ssid_key, "xxx"); - while(prefs.getString(ssid_key, "") == "" || prefs.getString(ssid_key, "") == old_ssid) { + while (prefs.getString(ssid_key, "") == "" || prefs.getString(ssid_key, "") == old_ssid) + { delay(1000); } - failed_connection_attempts = 0; - } else { - if (WiFi.isConnected() && WiFi.SSID() == prefs.getString(ssid_key, "")) { + } + else + { + if (WiFi.isConnected() && WiFi.SSID() == prefs.getString(ssid_key, "")) + { failed_connection_attempts = 0; - rssi = WiFi.RSSI(); - ip_address = WiFi.localIP(); + wifi_data.rssi = WiFi.RSSI(); + wifi_data.link = true; + wifi_data.network_name = WiFi.SSID(); + wifi_data.ip_address = WiFi.localIP().toString(); - - - Log.verbose("RSSI: %F, IP Address, %p, SSID: %s", rssi, WiFi.localIP(), prefs.getString(ssid_key, "NOSSID")); + Log.verbose("RSSI: %F, IP Address, %p, SSID: %s", float(WiFi.RSSI()), WiFi.localIP(), prefs.getString(ssid_key, "NOSSID")); delay(5000); - } else { + } + else + { Log.verbose("Connecting to %s using password %s", prefs.getString(ssid_key, ""), prefs.getString(wifi_password_key, "")); WiFi.mode(WIFI_STA); WiFi.begin(prefs.getString(ssid_key, ""), prefs.getString(wifi_password_key, "")); @@ -185,17 +237,75 @@ void wifi_task(void * parameter ){ } } } - } -void ethernet_task(void * parameter ){ - while (true) { - +void ethernet_task(void *parameter) +{ + Log.verbose("Connecting Ethernet"); + ETH.begin(0, 17, 23, 18); + ETH.setHostname("watermeter"); + while (true) + { + ethernet_data.link = ETH.linkUp(); + ethernet_data.rssi = ETH.linkSpeed(); + ethernet_data.ip_address = ETH.localIP().toString(); + delay(60 * 1000); } - } -void setup() { +void collect_internal_telemetry_task(void *parameter) +{ + + while (true) + { + float heap_usage = (float(ESP.getFreeHeap()) / float(ESP.getHeapSize())) * 100; + uint64_t uptime_seconds = millis() / 1000; + + Log.verbose("Current heap usage: %F", heap_usage); + Log.verbose("Current uptime: %d", uptime_seconds); + + delay(60000); + } +} + +void read_sensor_task(void *parameter) +{ + while (true) + { + float bus_voltage = ina_sensor.getBusVoltage(); + float shunt_voltage = ina_sensor.getShuntVoltage_mV() - zero_value; + float shunt_current = shunt_voltage / 4; + + float mA_per_cm = (20 - 4) / (sensor_range * 100); + + float min_water_level_mA_over_zero = (min_water_level_cm * mA_per_cm); + float max_water_level_mA_over_zero = (max_water_level_cm * mA_per_cm); + + float min_water_level_mA = 4 + min_water_level_mA_over_zero; + float max_water_level_mA = 4 + max_water_level_mA_over_zero; + + // Over Zero always revers to zero water level + float shunt_current_over_zero = shunt_current - min_water_level_mA; + + int percentage = round((shunt_current_over_zero / max_water_level_mA_over_zero) * 100); + + current_low = shunt_current < 3.8; + current_high = shunt_current > 20.2; + voltage_low = bus_voltage < 23; + voltage_high = bus_voltage > 25; + Log.verbose("Bus current: %F", bus_voltage); + Log.verbose("Shunt current: %F", shunt_current); + Log.verbose("Shunt voltage: %F", shunt_voltage); + Log.verbose("Value percentage: %F", percentage); + + current_data = SensorData{percentage, bus_voltage, shunt_current, shunt_current_over_zero}; + + delay(20000); + } +} + +void setup() +{ prefs.begin("waterlevel", false); Serial.begin(115200); @@ -203,9 +313,6 @@ void setup() { Log.setSuffix(printSuffix); Log.setPrefix(print_prefix); - Log.verbose("Starting WiFi Task now (external)"); - xTaskCreate(wifi_task, "WiFiTask", 10000, NULL, 1, NULL); - Log.verbose("Init LEDs"); pinMode(LED_1, OUTPUT); pinMode(LED_2, OUTPUT); @@ -229,54 +336,59 @@ void setup() { ina_sensor.setAverage(4); display_error_code(21); - Log.verbose("Connecting Ethernet"); - ETH.begin(0, 17, 23, 18); - Log.verbose(ETH.getHostname()); display_error_code(22); /////////////////////////////// ROUTES /////////////////////////////// Log.verbose("Route Setup"); - server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){ - request->send(SPIFFS, "/status.html", "text/html", false); - }); + server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) + { request->send(SPIFFS, "/status.html", "text/html", false); }); - server.on("/settings", HTTP_GET, [](AsyncWebServerRequest *request){ - request->send(SPIFFS, "/settings.html", "text/html", false); - }); + server.on("/settings", HTTP_GET, [](AsyncWebServerRequest *request) + { request->send(SPIFFS, "/settings.html", "text/html", false); }); - server.on("/export", HTTP_GET, [](AsyncWebServerRequest *request){ - request->send(SPIFFS, "/data_export.html", "text/html", false); - }); + server.on("/export", HTTP_GET, [](AsyncWebServerRequest *request) + { request->send(SPIFFS, "/data_export.html", "text/html", false); }); - server.on("/update_wifi_credentials", HTTP_POST, [](AsyncWebServerRequest *request){ - int params = request->params(); - - // Log.verbose(request->hasParam(ssid_key)); - // Log.verbose(request->hasParam(wifi_password_key)); + server.on("/logic.js", HTTP_GET, [](AsyncWebServerRequest *request) + { request->send(SPIFFS, "/logic.js", "application/javascript", false); }); - if (request->hasParam(ssid_key, true) && request->hasParam(wifi_password_key, true)){ - Log.verbose("Updating SSID config"); - AsyncWebParameter* ssid_param = request->getParam(ssid_key, true); - AsyncWebParameter* password_param = request->getParam(wifi_password_key, true); - prefs.putString(ssid_key, ssid_param->value().c_str()); - prefs.putString(wifi_password_key, password_param->value().c_str()); - } else { - for(int i=0;igetParam(i); - if(p->isFile()){ //p->isPost() is also true - Log.verbose("POST[%s]: %s\n", p->name().c_str(), p->value().c_str()); - } else if(p->isPost()){ - Log.verbose("POST[%s]: %s\n", p->name().c_str(), p->value().c_str()); - } else { - Log.verbose("GET[%s]: %s\n", p->name().c_str(), p->value().c_str()); - } - } - request->send(400, "text/plain", "Missing parameters"); //TODO add proper error messages - } - request->send(SPIFFS, "/settings.html", "text/html", false); //TODO add proper return templating - }); - - server.on("/sensor_data", HTTP_GET, [](AsyncWebServerRequest *request){ + server.on("/update_wifi_credentials", HTTP_POST, [](AsyncWebServerRequest *request) + { + int params = request->params(); + + if (request->hasParam(ssid_key, true) && request->hasParam(wifi_password_key, true)) + { + Log.verbose("Updating SSID config"); + AsyncWebParameter *ssid_param = request->getParam(ssid_key, true); + AsyncWebParameter *password_param = request->getParam(wifi_password_key, true); + prefs.putString(ssid_key, ssid_param->value().c_str()); + prefs.putString(wifi_password_key, password_param->value().c_str()); + } + else + { + for (int i = 0; i < params; i++) + { + AsyncWebParameter *p = request->getParam(i); + if (p->isFile()) + { // p->isPost() is also true + Log.verbose("POST[%s]: %s\n", p->name().c_str(), p->value().c_str()); + } + else if (p->isPost()) + { + Log.verbose("POST[%s]: %s\n", p->name().c_str(), p->value().c_str()); + } + else + { + Log.verbose("GET[%s]: %s\n", p->name().c_str(), p->value().c_str()); + } + } + request->send(400, "text/plain", "Missing parameters"); // TODO add proper error messages + } + request->send(SPIFFS, "/settings.html", "text/html", false); // TODO add proper return templating + }); + + server.on("/sensor_data", HTTP_GET, [](AsyncWebServerRequest *request) + { StaticJsonDocument<128> doc; doc["percentage"] = current_data.percentage; doc["voltage"] = current_data.voltage; @@ -285,22 +397,36 @@ void setup() { String output; serializeJson(doc, output); - request->send(200, "application/json", output); - }); + request->send(200, "application/json", output); }); + + server.on("/network_info", HTTP_GET, [](AsyncWebServerRequest *request) + { + StaticJsonDocument<256> doc; + doc["wifi"]["ip"] = wifi_data.ip_address; + doc["wifi"]["rssi"] = wifi_data.rssi; + doc["wifi"]["link"] = wifi_data.link; + doc["wifi"]["ssid"] = wifi_data.network_name; + + doc["ethernet"]["ip"] = ethernet_data.ip_address; + doc["ethernet"]["rssi"] = ethernet_data.rssi; + doc["ethernet"]["link"] = ethernet_data.link; + + + String output; + serializeJson(doc, output); + request->send(200, "application/json", output); }); + + server.on("/chota.css", HTTP_GET, [](AsyncWebServerRequest *request) + { request->send(SPIFFS, "/chota.css", "text/css", false); }); - server.on("/chota.css", HTTP_GET, [](AsyncWebServerRequest *request){ - request->send(SPIFFS, "/chota.css", "text/css", false); - }); - display_error_code(23); - Log.verbose("Starting webserver"); - server.begin(); display_error_code(24); Log.verbose("OTA Setup"); ArduinoOTA - .onStart([]() { + .onStart([]() + { String type; if (ArduinoOTA.getCommand() == U_FLASH) type = "sketch"; @@ -308,81 +434,37 @@ void setup() { type = "filesystem"; // NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end() - Log.verbose("Start updating %s", type); - }) - .onEnd([]() { - Log.verbose("\nEnd"); - }) - .onProgress([](unsigned int progress, unsigned int total) { - Serial.printf("Progress: %u%%\r", (progress / (total / 100))); - }) - .onError([](ota_error_t error) { + Log.verbose("Start updating %s", type); }) + .onEnd([]() + { Log.verbose("\nEnd"); }) + .onProgress([](unsigned int progress, unsigned int total) + { Serial.printf("Progress: %u%%\r", (progress / (total / 100))); }) + .onError([](ota_error_t error) + { Serial.printf("Error[%u]: ", error); if (error == OTA_AUTH_ERROR) Log.verbose("Auth Failed"); else if (error == OTA_BEGIN_ERROR) Log.verbose("Begin Failed"); else if (error == OTA_CONNECT_ERROR) Log.verbose("Connect Failed"); else if (error == OTA_RECEIVE_ERROR) Log.verbose("Receive Failed"); - else if (error == OTA_END_ERROR) Log.verbose("End Failed"); - }); - display_error_code(25); - ArduinoOTA.begin(); + else if (error == OTA_END_ERROR) Log.verbose("End Failed"); }); + display_error_code(26); digitalWrite(LED_RED, 0); - Log.verbose(ETH.localIP()); xTaskCreate(display_task, "DisplayTask", 10000, NULL, 1, NULL); + xTaskCreate(read_sensor_task, "ReadSensorTask", 2048, NULL, 1, NULL); + xTaskCreate(collect_internal_telemetry_task, "InternalTelemetryTask", 2048, NULL, 1, NULL); + xTaskCreate(ethernet_task, "EthernetTask", 4096, NULL, 1, NULL); + xTaskCreate(wifi_task, "WiFiTask", 10000, NULL, 1, NULL); + delay(1000); + Log.verbose("Starting webserver"); + server.begin(); + display_error_code(25); + ArduinoOTA.begin(); } - -/** - * Updates the global variables from the current ina sensor data - * All other parts just read these variables to prevent incositencies -*/ -void update_sensor_data(){ - float bus_voltage = ina_sensor.getBusVoltage(); - float shunt_voltage = ina_sensor.getShuntVoltage_mV() - zero_value; - float shunt_current = shunt_voltage / 4 ; - - - float mA_per_cm = (20 - 4)/ (sensor_range*100); - - - float min_water_level_mA_over_zero = (min_water_level_cm * mA_per_cm); - float max_water_level_mA_over_zero = (max_water_level_cm * mA_per_cm); - - float min_water_level_mA = 4 + min_water_level_mA_over_zero; - float max_water_level_mA = 4 + max_water_level_mA_over_zero; - - // Over Zero always revers to zero water level - float shunt_current_over_zero = shunt_current - min_water_level_mA; - - int percentage = round(( shunt_current_over_zero / max_water_level_mA_over_zero) * 100); - - - current_low = shunt_current < 3.8; - current_high = shunt_current > 20.2; - voltage_low = bus_voltage < 23; - voltage_high = bus_voltage > 25; - Log.verbose("=========="); - Log.verbose("Shunt current: %F", shunt_current); - Log.verbose("Shunt voltage: %F", shunt_voltage); - Log.verbose("Value percentage: %F", percentage); - - - current_data = SensorData{percentage, bus_voltage, shunt_current, shunt_current_over_zero}; - - - -} - -void loop() { +void loop() +{ ArduinoOTA.handle(); - - - update_sensor_data(); - // Log.verbose(WiFi.softAPIP()); - // Log.verbose(ETH.localIP()); - delay(5000); + delay(1000); } - -