#include "SPIFFS.h" #include #include #include #include #include #include "AsyncJson.h" #include #include #include #include "global_data/defines.h" #include "global_data/global_data.h" #include "networking/networking.h" #include "networking/webserver.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 #include "time.h" #include "tools/log.h" #include "tools/ota.h" #include #include "esp_heap_caps.h" #define MYLOG 0 Preferences prefs; Version current_spiffs_version; // Variable to store the current state of the system CurrentState current_state = { {-999.0f, -999.0f, -999.0f}, {-999.0f, -999.0f, -999.0f}, {"0.0.0.0", false, -999.0f, "UNKNOWN"}, // wifiData {"0.0.0.0", false, -999.0f, "UNKNOWN"}, // ethernetData {-999.0f, -999, -999.0f}, {false, {0, 0, 0}, {0, 0, 0}, -999, "UNKNOWN"}, {false, false, false, false, false, false} }; #define FORMAT_LITTLEFS_IF_FAILED true // Queue for sending data from producers to the processor in main QueueHandle_t dataQueue = xQueueCreate(5, sizeof(DataMessage)); // Queue for sending data from processor to different tasks QueueHandle_t stateForWebserverQueue = xQueueCreate(2, sizeof(CurrentState)); // Queue for sending preferences from main to webserver QueueHandle_t prefsForWebserverQueue = xQueueCreate(2, sizeof(DataMessage)); // Queue for sending preferences from main to sensor task QueueHandle_t prefsForSensorQueue = xQueueCreate(2, sizeof(DataMessage)); void setup() { Logger.registerSerial(MYLOG, ELOG_LEVEL_DEBUG, "Serial"); LOG(ELOG_LEVEL_DEBUG, "Init Starting prefs and Serial output"); prefs.begin("waterlevel", false); Serial.begin(115200); // Read initial preferences and send to webserver and sensor if (prefsForWebserverQueue != NULL || prefsForSensorQueue != NULL) { DataMessage prefsMessage; prefsMessage.type = DATA_TYPE_PREFERENCES; prefsMessage.data.preferencesData.level_sensor_range = prefs.getFloat(level_sensor_range_key, 200); prefsMessage.data.preferencesData.water_level_min = prefs.getFloat(water_level_min_key, 0); prefsMessage.data.preferencesData.water_level_max = prefs.getFloat(water_level_max_key, 200); prefsMessage.data.preferencesData.water_volume = prefs.getFloat(water_volume_key, 10000.0f); strncpy(prefsMessage.data.preferencesData.ssid, prefs.getString(ssid_key, "").c_str(), sizeof(prefsMessage.data.preferencesData.ssid) - 1); prefsMessage.data.preferencesData.ssid[sizeof(prefsMessage.data.preferencesData.ssid) - 1] = '\0'; strncpy(prefsMessage.data.preferencesData.wifi_password, prefs.getString(wifi_password_key, "").c_str(), sizeof(prefsMessage.data.preferencesData.wifi_password) - 1); prefsMessage.data.preferencesData.wifi_password[sizeof(prefsMessage.data.preferencesData.wifi_password) - 1] = '\0'; // Send to webserver if (prefsForWebserverQueue != NULL) { if (xQueueSend(prefsForWebserverQueue, &prefsMessage, 100 / portTICK_PERIOD_MS) != pdTRUE) { LOG(ELOG_LEVEL_ERROR, "Failed to send initial preferences to webserver queue"); } else { LOG(ELOG_LEVEL_DEBUG, "Sent initial preferences to webserver"); } } // Send to sensor task if (prefsForSensorQueue != NULL) { if (xQueueSend(prefsForSensorQueue, &prefsMessage, 100 / portTICK_PERIOD_MS) != pdTRUE) { LOG(ELOG_LEVEL_ERROR, "Failed to send initial preferences to sensor queue"); } else { LOG(ELOG_LEVEL_DEBUG, "Sent initial preferences to sensor"); } } } LOG(ELOG_LEVEL_DEBUG, "Beginning LittleFS"); LittleFS.begin(FORMAT_LITTLEFS_IF_FAILED); // Read the current SPIFFS version from the versions file on the spiffs LOG(ELOG_LEVEL_DEBUG, "Reading LittleFS version"); File file = LittleFS.open("/version", FILE_READ); if (!file) { LOG( ELOG_LEVEL_ERROR, "Failed opening LittleFS version file"); } else { String version = file.readStringUntil('\n'); current_spiffs_version = parseVersion(version.c_str()); LOG(ELOG_LEVEL_DEBUG, "Current LittleFS Version: %d.%d.%d", current_spiffs_version.major, current_spiffs_version.minor, current_spiffs_version.patch); } LOG(ELOG_LEVEL_DEBUG, "LittleFS initialized"); LOG(ELOG_LEVEL_DEBUG, "Starting main tasks"); if (dataQueue == NULL) { LOG(ELOG_LEVEL_ERROR, "Failed to create data queue"); } else { xTaskCreate(ethernet_task, "EthernetTask", 1024 * 4, dataQueue, 1, NULL); xTaskCreate(wifi_task, "WiFiTask", 1024 * 4, dataQueue, 1, NULL); // Create sensor queues struct SensorQueues sensorQueues = { .dataQueue = dataQueue, .prefsQueue = prefsForSensorQueue }; xTaskCreate(read_sensor_task, "ReadSensorTask", 1024 * 4, &sensorQueues, 1, NULL); xTaskCreate(collect_internal_telemetry_task, "InternalTelemetryTask", 1024 * 2, dataQueue, 1, NULL); xTaskCreate(display_task, "DisplayTask", 1024 * 2, NULL, 1, NULL); xTaskCreate(get_time_task, "GetTimeTask", 1024 * 2, NULL, 1, NULL); xTaskCreate(check_update_task, "CheckUpdateTask", 1024 * 6, dataQueue, 1, NULL); // Create webserver queues struct WebserverQueues webserverQueues = { .stateQueue = stateForWebserverQueue, .dataQueue = dataQueue, .prefsQueue = prefsForWebserverQueue }; xTaskCreate(webserver_task, "WebServerTask", 1024 * 4, &webserverQueues, 1, NULL); xTaskCreate(ota_handler_task, "OTAHandlerTask", 1024 * 4, NULL, 3, NULL); } } void loop() { size_t free_heap = heap_caps_get_free_size(MALLOC_CAP_8BIT); Serial.println(free_heap); // Check if there is new data in the queue DataMessage dataMessage; if (xQueueReceive(dataQueue, &dataMessage, portMAX_DELAY) == pdTRUE) { // Decode the data based on its type and update the current state switch (dataMessage.type) { case DATA_TYPE_WATER: current_state.waterData = dataMessage.data.waterData; LOG(ELOG_LEVEL_DEBUG, "Received water data: level=%F, liters=%F, percentage=%F", current_state.waterData.level, current_state.waterData.liters, current_state.waterData.percentage); break; case DATA_TYPE_SENSOR: current_state.sensorData = dataMessage.data.sensorData; LOG(ELOG_LEVEL_DEBUG, "Received sensor data: bus_voltage=%F, shunt_voltage=%F, shunt_current=%F", current_state.sensorData.bus_voltage, current_state.sensorData.shunt_voltage, current_state.sensorData.shunt_current); break; case DATA_TYPE_WIFI: current_state.wifiData = dataMessage.data.networkData; LOG(ELOG_LEVEL_DEBUG, "Received WiFi data: ip=%s, link=%d, rssi=%F, name=%s", current_state.wifiData.ip_address, current_state.wifiData.link, current_state.wifiData.rssi, current_state.wifiData.network_name); break; case DATA_TYPE_ETHERNET: current_state.ethernetData = dataMessage.data.networkData; LOG(ELOG_LEVEL_DEBUG, "Received Ethernet data: ip=%s, link=%d, rssi=%F, name=%s", current_state.ethernetData.ip_address, current_state.ethernetData.link, current_state.ethernetData.rssi, current_state.ethernetData.network_name); break; case DATA_TYPE_TELEMETRY: current_state.telemetryData = dataMessage.data.telemetryData; LOG(ELOG_LEVEL_DEBUG, "Received telemetry data: heap=%F, uptime=%d, temperature=%F", current_state.telemetryData.heap_used_percent, current_state.telemetryData.uptime_seconds, current_state.telemetryData.temperature); break; case DATA_TYPE_OTA: current_state.otaStatus = dataMessage.data.otaStatus; LOG(ELOG_LEVEL_DEBUG, "Received OTA status: update_available=%d, current=%d.%d.%d, latest=%d.%d.%d", current_state.otaStatus.update_available, current_state.otaStatus.current_version.major, current_state.otaStatus.current_version.minor, current_state.otaStatus.current_version.patch, current_state.otaStatus.latest_version.major, current_state.otaStatus.latest_version.minor, current_state.otaStatus.latest_version.patch); break; case DATA_TYPE_PREFERENCES: // Store preferences in flash and forward to webserver LOG(ELOG_LEVEL_DEBUG, "Received preference update request"); // Write to preferences storage prefs.putFloat(level_sensor_range_key, dataMessage.data.preferencesData.level_sensor_range); prefs.putFloat(water_level_min_key, dataMessage.data.preferencesData.water_level_min); prefs.putFloat(water_level_max_key, dataMessage.data.preferencesData.water_level_max); prefs.putFloat(water_volume_key, dataMessage.data.preferencesData.water_volume); prefs.putString(ssid_key, dataMessage.data.preferencesData.ssid); prefs.putString(wifi_password_key, dataMessage.data.preferencesData.wifi_password); // Forward to webserver so it can update its local copy if (prefsForWebserverQueue != NULL) { if (xQueueSend(prefsForWebserverQueue, &dataMessage, 100 / portTICK_PERIOD_MS) != pdTRUE) { LOG(ELOG_LEVEL_ERROR, "Failed to forward preferences to webserver queue"); } } // Forward to sensor task so it can update its local copy if (prefsForSensorQueue != NULL) { if (xQueueSend(prefsForSensorQueue, &dataMessage, 100 / portTICK_PERIOD_MS) != pdTRUE) { LOG(ELOG_LEVEL_ERROR, "Failed to forward preferences to sensor queue"); } } LOG(ELOG_LEVEL_DEBUG, "Stored preferences and forwarded to tasks"); break; default: LOG(ELOG_LEVEL_ERROR, "Unknown data type received"); break; } // Send the updated current_state to the webserver queue (for non-preference messages) if (dataMessage.type != DATA_TYPE_PREFERENCES) { if (xQueueSendToBack(stateForWebserverQueue, ¤t_state, 250 / portTICK_PERIOD_MS) != pdTRUE) { LOG(ELOG_LEVEL_ERROR, "Failed to send current_state to webserver queue"); } } } else { LOG(ELOG_LEVEL_WARNING, "No message received within max wait"); } delay(1000); }