#include "SPIFFS.h" #include #include #include #include #include #include #include "AsyncJson.h" #include #include #include #include "global_data/defines.h" #include "networking/networking.h" #include "external_interfacing/leds.h" #include "sensor/sensor.h" #include "telemetry/telemetry.h" #include "tools/tools.h" #include #include "networking/json_builder.h" #include "networking/responses.h" #include #include #include "time.h" #include "tools/log.h" #define MYLOG 0 Preferences prefs; extern WaterData water_data; extern DeviceTelemetry telemetry; extern NetworkData wifi_data; extern NetworkData ethernet_data; extern SensorData shunt_data; extern "C" int rom_phy_get_vdd33(); Version current_spiffs_version; AsyncWebServer server(80); AsyncWebSocket webSocket("/webSocket"); #define FORMAT_LITTLEFS_IF_FAILED true void setup() { logger.registerSerial(MYLOG, DEBUG, "tst"); prefs.begin("waterlevel", false); Serial.begin(115200); LOG(DEBUG, "Init LEDs"); pinMode(LED_1, OUTPUT); pinMode(LED_2, OUTPUT); pinMode(LED_3, OUTPUT); pinMode(LED_4, OUTPUT); pinMode(LED_5, OUTPUT); pinMode(LED_RED, OUTPUT); display_error_code(31); delay(500); display_error_code(17); init_sensor(); LOG(DEBUG, "Beginning SPIFFS"); SPIFFS.begin(true); // Read the current SPIFFS version from the versions file on the spiffs File file = SPIFFS.open("/version", FILE_READ); if (!file) { Serial.println("Failed to open version file for reading"); } else { String version = file.readStringUntil('\n'); LOG(DEBUG, "Version: %s", version); current_spiffs_version = parseVersion(version.c_str()); LOG(DEBUG, "Current SPIFFS Version: %d.%d.%d", current_spiffs_version.major, current_spiffs_version.minor, current_spiffs_version.patch); } LOG(DEBUG, "SPIFFS initialized"); display_error_code(19); LOG(DEBUG, "Begin INA"); display_error_code(22); /////////////////////////////// ROUTES /////////////////////////////// LOG(DEBUG, "Route Setup"); // Normal HTML stuff server.on("/", HTTP_GET, [](AsyncWebServerRequest* request) { request->send(SPIFFS, "/status.html", "text/html", false, processor); }); server.on("/settings", HTTP_GET, [](AsyncWebServerRequest* request) { request->send(SPIFFS, "/settings.html", "text/html", false, processor); }); server.on("/export", HTTP_GET, [](AsyncWebServerRequest* request) { request->send(SPIFFS, "/data_export.html", "text/html", false); }); server.on("/logic.js", HTTP_GET, [](AsyncWebServerRequest* request) { request->send(SPIFFS, "/logic.js", "application/javascript", false, processor); }); server.on("/update", HTTP_GET, [](AsyncWebServerRequest* request) { request->send(SPIFFS, "/update_progress.html", "text/html", false, processor); }); // API stuff - internal server.on("/update_wifi_credentials", HTTP_POST, [](AsyncWebServerRequest* request) { int params = request->params(); // For settings SSID if (request->hasParam(ssid_key, true) && request->hasParam(wifi_password_key, true)) { LOG(DEBUG, "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 { request->send(400, "text/plain", "Missing parameters"); // TODO add proper error messages } request->send(SPIFFS, "/settings.html", "text/html", false, processor); // TODO add proper return templating }); server.on("/update_sensor_settings", HTTP_POST, [](AsyncWebServerRequest* request) { int params = request->params(); if (request->hasParam(level_sensor_range_key, true) && request->hasParam(water_level_min_key, true) && request->hasParam(water_level_max_key, true) && request->hasParam(water_volume_key, true)) { LOG(DEBUG, "Updating Sensor config"); AsyncWebParameter* range_param = request->getParam(level_sensor_range_key, true); AsyncWebParameter* level_min_param = request->getParam(water_level_min_key, true); AsyncWebParameter* level_max_param = request->getParam(water_level_max_key, true); AsyncWebParameter* liters_param = request->getParam(water_volume_key, true); String range_str = range_param->value(); String level_min_str = level_min_param->value(); String level_max_str = level_max_param->value(); String liters_str = liters_param->value(); float range_float = range_str.toFloat(); float level_min_float = level_min_str.toFloat(); float level_max_float = level_max_str.toFloat(); float liters_float = liters_str.toFloat(); const char* paramCStr = range_str.c_str(); char* endPtr; // Convert the C string to a float using strtod float value = strtod(paramCStr, &endPtr); LOG(DEBUG, "range_float:%D:", range_float); prefs.putFloat(level_sensor_range_key, range_float); prefs.putFloat(water_level_min_key, level_min_float); prefs.putFloat(water_level_max_key, level_max_float); prefs.putFloat(water_volume_key, liters_float); LOG(DEBUG, "range_float_after:%D:", prefs.getFloat(level_sensor_range_key, -1.0)); } else { LOG(DEBUG, "!!!! FAIL lo"); for (int i = 0; i < params; i++) { AsyncWebParameter* p = request->getParam(i); if (p->isFile()) { // p->isPost() is also true LOG(DEBUG, "POST[%s]: %s\n", p->name().c_str(), p->value().c_str()); } else if (p->isPost()) { LOG(DEBUG, "POST[%s]: %s\n", p->name().c_str(), p->value().c_str()); } else { LOG(DEBUG, "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 }); setup_api_endpoints(); display_error_code(23); webSocket.onEvent(onWsEvent); server.addHandler(&webSocket); server.on("/chota.css", HTTP_GET, [](AsyncWebServerRequest* request) { request->send(SPIFFS, "/chota.css", "text/css", false); }); server.on("/gauge.js", HTTP_GET, [](AsyncWebServerRequest* request) { request->send(SPIFFS, "/gauge.js", "application/javascript", false); }); display_error_code(24); LOG(DEBUG, "OTA Setup"); ArduinoOTA .onStart([]() { String type; if (ArduinoOTA.getCommand() == U_FLASH) type = "sketch"; else // U_SPIFFS type = "filesystem"; // NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end() LOG(DEBUG, "Start updating %s", type); }) .onEnd([]() { LOG(DEBUG, "\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(DEBUG, "Auth Failed"); else if (error == OTA_BEGIN_ERROR) LOG(DEBUG, "Begin Failed"); else if (error == OTA_CONNECT_ERROR) LOG(DEBUG, "Connect Failed"); else if (error == OTA_RECEIVE_ERROR) LOG(DEBUG, "Receive Failed"); else if (error == OTA_END_ERROR) LOG(DEBUG, "End Failed"); }); display_error_code(26); digitalWrite(LED_RED, 0); // Starting bootup sequence xTaskCreate(ethernet_task, "EthernetTask", 4096, NULL, 1, NULL); // Create Etnernet task and wait a second to see if there is connection LOG(DEBUG, "Started Ethernet, waiting"); delay(2000); if (ETH.linkUp()){ LOG(DEBUG, "Ethernet connected, starting update checker"); xTaskCreate(check_update_task, "CheckUpdateTask", 1024 * 8, NULL, 1, NULL); } else { LOG(DEBUG, "Ethernet not connected, starting update checker and WiFi Task"); xTaskCreate(wifi_task, "WiFiTask", 10000, NULL, 1, NULL); xTaskCreate(check_update_task, "CheckUpdateTask", 1024 * 8, NULL, 1, NULL); delay(2000); } LOG(DEBUG, "Getting time now"); // Configure time to UTC configTime(0, 0, "pool.ntp.org"); // Wait until a valid time is obtained (time > 8 hours in seconds) time_t now = time(NULL); struct tm timeinfo; int retry = 0; while (now < 8 * 3600 && retry < 10) { if(!getLocalTime(&timeinfo)){ Serial.println("Failed to obtain time"); } Serial.println(&timeinfo, "%A, %B %d %Y %H:%M:%S"); retry++; } getLocalTime(&timeinfo); // Sanity check: Ensure the year is at least 2020. int currentYear = timeinfo.tm_year + 1900; // tm_year is years since 1900 if (currentYear < 2020) { LOG(DEBUG, "Time not set properly: "); } else { LOG(DEBUG, "Time is valid: %s", asctime(&timeinfo)); } // Setup syslog logger.configureSyslog("192.168.6.11", 5514, "esp32"); logger.registerSyslog(MYLOG, DEBUG, FAC_USER, "waterlevel"); LOG(ERROR, "Here is an error message, error code: %d", 17); LOG(DEBUG, "Starting webserver"); server.begin(); ArduinoOTA.begin(); display_error_code(25); xTaskCreate(display_task, "DisplayTask", 10000, NULL, 1, NULL); xTaskCreate(read_sensor_task, "ReadSensorTask", 1024 * 4, NULL, 1, NULL); xTaskCreate(collect_internal_telemetry_task, "InternalTelemetryTask", 2048, NULL, 1, NULL); // Wait until there is network connection while (true) { if (WiFi.status() == WL_CONNECTED || ETH.localIP()) { int pingResult = Ping.ping("8.8.8.8"); // Use Google's public DNS server as a test IP if (pingResult >= 0) { LOG(DEBUG, "Network connection established"); break; } else { LOG(DEBUG, "Network not ready, retrying..."); } } else { LOG(DEBUG, "No WiFi or Ethernet connection, retrying..."); } delay(1000); // Delay to prevent rapid retry } } void loop() { ArduinoOTA.handle(); delay(1000); }