#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 "time.h" #include "tools/log.h" #include #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 ActiveErrors active_errors; 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, ELOG_LEVEL_DEBUG, "Serial"); LOG(ELOG_LEVEL_DEBUG, "Init LEDs"); led_setup(); prefs.begin("waterlevel", false); Serial.begin(115200); delay(500); init_sensor(); LOG(ELOG_LEVEL_DEBUG, "Beginning SPIFFS"); //SPIFFS.begin(FORMAT_LITTLEFS_IF_FAILED); LittleFS.begin(FORMAT_LITTLEFS_IF_FAILED); // Read the current SPIFFS version from the versions file on the spiffs File file = LittleFS.open("/version", FILE_READ); if (!file) { Serial.println("Failed to open version file for reading"); } else { String version = file.readStringUntil('\n'); LOG(ELOG_LEVEL_DEBUG, "Version: %s", version); current_spiffs_version = parseVersion(version.c_str()); LOG(ELOG_LEVEL_DEBUG, "Current SPIFFS Version: %d.%d.%d", current_spiffs_version.major, current_spiffs_version.minor, current_spiffs_version.patch); } LOG(ELOG_LEVEL_DEBUG, "SPIFFS initialized"); LOG(ELOG_LEVEL_DEBUG, "Begin INA"); /////////////////////////////// ROUTES /////////////////////////////// LOG(ELOG_LEVEL_DEBUG, "Route Setup"); // Normal HTML stuff server.on("/", HTTP_GET, [](AsyncWebServerRequest* request) { request->send(LittleFS, "/status.html", "text/html", false, processor); }); server.on("/settings", HTTP_GET, [](AsyncWebServerRequest* request) { request->send(LittleFS, "/settings.html", "text/html", false, processor); }); server.on("/export", HTTP_GET, [](AsyncWebServerRequest* request) { request->send(LittleFS, "/data_export.html", "text/html", false); }); server.on("/logic.js", HTTP_GET, [](AsyncWebServerRequest* request) { request->send(LittleFS, "/logic.js", "application/javascript", false, processor); }); server.on("/update", HTTP_GET, [](AsyncWebServerRequest* request) { request->send(LittleFS, "/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(ELOG_LEVEL_DEBUG, "Updating SSID config"); const AsyncWebParameter* ssid_param = request->getParam(ssid_key, true); const 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(LittleFS, "/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(ELOG_LEVEL_DEBUG, "Updating Sensor config"); const AsyncWebParameter* range_param = request->getParam(level_sensor_range_key, true); const AsyncWebParameter* level_min_param = request->getParam(water_level_min_key, true); const AsyncWebParameter* level_max_param = request->getParam(water_level_max_key, true); const 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(ELOG_LEVEL_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(ELOG_LEVEL_DEBUG, "range_float_after:%D:", prefs.getFloat(level_sensor_range_key, -1.0)); } else { LOG(ELOG_LEVEL_DEBUG, "!!!! FAIL lo"); for (int i = 0; i < params; i++) { const AsyncWebParameter* p = request->getParam(i); if (p->isFile()) { // p->isPost() is also true LOG(ELOG_LEVEL_DEBUG, "POST[%s]: %s\n", p->name().c_str(), p->value().c_str()); } else if (p->isPost()) { LOG(ELOG_LEVEL_DEBUG, "POST[%s]: %s\n", p->name().c_str(), p->value().c_str()); } else { LOG(ELOG_LEVEL_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(LittleFS, "/settings.html", "text/html", false); // TODO add proper return templating }); setup_api_endpoints(); // webSocket.onEvent(onWsEvent); // server.addHandler(&webSocket); server.on("/chota.css", HTTP_GET, [](AsyncWebServerRequest* request) { request->send(LittleFS, "/chota.css", "text/css", false); }); server.on("/gauge.js", HTTP_GET, [](AsyncWebServerRequest* request) { request->send(LittleFS, "/gauge.js", "application/javascript", false); }); LOG(ELOG_LEVEL_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(ELOG_LEVEL_DEBUG, "Start updating %s", type); }) .onEnd([]() { LOG(ELOG_LEVEL_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(ELOG_LEVEL_DEBUG, "Auth Failed"); else if (error == OTA_BEGIN_ERROR) LOG(ELOG_LEVEL_DEBUG, "Begin Failed"); else if (error == OTA_CONNECT_ERROR) LOG(ELOG_LEVEL_DEBUG, "Connect Failed"); else if (error == OTA_RECEIVE_ERROR) LOG(ELOG_LEVEL_DEBUG, "Receive Failed"); else if (error == OTA_END_ERROR) LOG(ELOG_LEVEL_DEBUG, "End Failed"); }); digitalWrite(LED_RED, 0); // Starting bootup sequence xTaskCreate(ethernet_task, "EthernetTask", 4096, NULL, 1, NULL); xTaskCreate(wifi_task, "WiFiTask", 10000, NULL, 1, NULL); esp_netif_t* eth = esp_netif_get_handle_from_ifkey("ETH_DEF"); esp_netif_t* wifi = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF"); if (ETH.linkUp()){ LOG(ELOG_LEVEL_DEBUG, "Ethernet connected, starting update checker"); xTaskCreate(check_update_task, "CheckUpdateTask", 1024 * 8, NULL, 1, NULL); } else { LOG(ELOG_LEVEL_DEBUG, "Ethernet not connected, starting update checker and WiFi Task"); // esp_netif_set_default_netif(esp_netif_get_handle_from_ifkey("ETH_DEF")); xTaskCreate(check_update_task, "CheckUpdateTask", 1024 * 8, NULL, 1, NULL); delay(2000); } LOG(ELOG_LEVEL_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(ELOG_LEVEL_DEBUG, "Time not set properly: "); } else { LOG(ELOG_LEVEL_DEBUG, "Time is valid: %s", asctime(&timeinfo)); } // Setup syslog LOG(ELOG_LEVEL_ERROR, "Here is an error message, error code: %d", 17); LOG(ELOG_LEVEL_DEBUG, "Starting webserver"); server.begin(); ArduinoOTA.begin(); xTaskCreate(read_sensor_task, "ReadSensorTask", 1024 * 4, NULL, 1, NULL); xTaskCreate(collect_internal_telemetry_task, "InternalTelemetryTask", 2048, NULL, 1, NULL); xTaskCreate(display_task, "DisplayTask", 10000, NULL, 1, NULL); } void loop() { ArduinoOTA.handle(); delay(1000); }