diff --git a/INA226.zip b/INA226.zip new file mode 100644 index 0000000..3f26b76 Binary files /dev/null and b/INA226.zip differ diff --git a/README.md b/README.md index 89bb553..712d9da 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,117 @@ -# ESP32_powerMC +# Webserver und URLs + +Der Webserver ist auf dem ESP32 implementiert und bietet verschiedene Endpunkte für die Bedienung und Anzeige von Informationen. Hier sind die definierten URLs und ihre Funktionen: + +- **Root Path ("/")** + - HTTP-Methode: GET + - Zeigt die Hauptseite mit allgemeinen Informationen an, einschließlich Links zu anderen Endpunkten. + +- **JSON Path ("/json")** + - HTTP-Methode: GET + - Liefert JSON-formatierte Daten mit Informationen über Busspannung, Strom, Leistung, Energie, Temperatur, Luftfeuchtigkeit und Fehlercode. + +Beispiel: +``` +{ + "busVoltage": 25.6, + "shuntVoltage": 0.0, + "current": 10.5, + "power": 268.8, + "energy": 134.4, + "temp": 28.5, + "humidity": 45.8, + "errorCode +} +``` + +- **Config Path ("/config")** + - HTTP-Methode: GET + - Zeigt eine Konfigurationsseite an, auf der Benutzer Einstellungen für Temperatur, Luftfeuchtigkeit und Strombereiche ändern können. + +- **Save Config Path ("/saveConfig")** + - HTTP-Methode: PUT + - Behandelt die Anforderung zum Speichern der neuen Konfigurationsdaten, die durch die Konfigurationsseite festgelegt wurden. + +Beispiel: +``` +{ + "temp_min": 20.0, + "temp_max": 30.0, + "humi_min": 40.0, + "humi_max": 60.0, + "current_min": -5.0, + "current_max": 5.0 +} +``` + +- **Demo Mode 1 Path ("/demo1")** + - HTTP-Methode: GET + - Startet den Demo-Modus 1: Demonstriert einen lade-/entladezyklus ab der Hälfte der maximalen Kapazität mit variablem Strom bei 25,6A. + +- **Demo Mode 2 Path ("/demo2")** + - HTTP-Methode: GET + - Startet den Demo-Modus 2: Demonstriert einen ladezyklus ab 97% der maximalen Kapazität auf 100% mit 200A bei 25.6V. + +- **Demo Mode 3 Path ("/demo3")** + - HTTP-Methode: GET + - Startet den Demo-Modus 3: Demonstriert einen entladezyklus ab 3% der maximalen Kapazität auf 0% mit -200A bei 25.6V. + +Die HTML-Seiten werden dynamisch generiert und enthalten JavaScript für die Aktualisierung von Daten über AJAX-Anfragen. Die Konfigurationsseite ermöglicht es Benutzern, bestimmte Parameter über Schieberegler einzustellen und Änderungen zu speichern. + +# Fehlermeldungen für den wert `errorCode` + +Die Variable `globalError` wird als Bitmask verwendet, wobei jedes Bit eine bestimmte Fehlerbedingung repräsentiert. Hier sind die möglichen Fehlermeldungen und ihre Bedeutungen: + +## `ERROR_NONE` (0b0000000000000000) + +Kein Fehler. Das Gerät funktioniert ordnungsgemäß, es liegen keine Fehler vor. + +## `ERROR_MAX_CURRENT_EXCEEDED` (0b0000000000000001) + +Der maximale Stromverbrauch wurde überschritten. Möglicherweise liegt eine Überlastung des Geräts vor. + +## `ERROR_CURRENT_BELOW_MIN` (0b0000000000000010) + +Der aktuelle Stromverbrauch liegt unter dem zulässigen Minimum. Möglicherweise gibt es ein Problem mit der Stromzufuhr. + +## `ERROR_MAX_TEMP_EXCEEDED` (0b0000000000000100) + +Die maximale Temperatur wurde überschritten. Das Gerät könnte überhitzen. + +## `ERROR_TEMP_BELOW_MIN` (0b0000000000001000) + +Die Temperatur liegt unterhalb des zulässigen Minimums. Es besteht die Gefahr von Unterkühlung oder anderen temperaturbedingten Problemen. + +## `ERROR_MAX_HUMI_EXCEEDED` (0b0000000000010000) + +Die maximale Luftfeuchtigkeit wurde überschritten. Dies könnte zu feuchtigkeitsbedingten Problemen führen. + +## `ERROR_HUMI_BELOW_MIN` (0b0000000000100000) + +Die Luftfeuchtigkeit liegt unter dem zulässigen Minimum. Es besteht die Gefahr von zu geringer Luftfeuchtigkeit. + +Bitte beachten Sie, dass mehrere Fehler gleichzeitig auftreten können, da die Bitmasken kombiniert werden können. Zum Beispiel könnte `ERROR_MAX_TEMP_EXCEEDED | ERROR_MAX_HUMI_EXCEEDED` darauf hinweisen, dass sowohl die maximale Temperatur als auch die maximale Luftfeuchtigkeit überschritten wurden. + +# Used libraries (Arduino) + +WiFi at version 2.0.0 +ESP32httpUpdate at version 2.1.145 +HTTPClient at version 2.0.0 +WiFiClientSecure at version 2.0.0 +Update at version 2.0.0 +FS at version 2.0.0 +SPIFFS at version 2.0.0 +WebServer at version 2.0.0 +ArduinoJson at version 7.0.2 +WiFiManager at version 2.0.16-rc.2 +DNSServer at version 2.0.0 +EEPROM at version 2.0.0 +Wire at version 2.0.0 +INA226 at version 0.5.2 +Adafruit GFX Library at version 1.11.9 +Adafruit BusIO at version 1.15.0 +SPI at version 2.0.0 +Adafruit SSD1306 at version 2.5.9 +Adafruit Unified Sensor at version 1.1.14 +Adafruit BME280 Library at version 2.2.4 diff --git a/firmware/config_user.h b/firmware/config_user.h new file mode 100644 index 0000000..d546e79 --- /dev/null +++ b/firmware/config_user.h @@ -0,0 +1,44 @@ + +#define FIRMWARE_VERSION "v0.3.0" + +//#define DEBUG_NO_I2C +#define DEBUG_NO_SERIAL_MSG + +#define WATCHDOG_TIMEOUT_S 5 + +#define INA226_I2C_ADDRESS 0x41 +#define BME280_I2C_ADDRESS 0x76 // oder 0x77, je nach Verbindung des ADDR-Pins +#define OLED_I2C_ADDRESS 0x3C // -> the addresses like 0x78 which is selected on the chip is not correct + +#define LOOP_INA226READ_DEMO_DELAY_MS 1000 +#define LOOP_DISPLAY_DELAY_MS 5000 // 5 Sekunden +#define LOOP_DISPLAY_DEMO_DELAY_MS 1000 +#define LOOP_HANDLE_CLIENT_DELAY_MS 250 +#define LOOP_INA226CHECK_DELAY_MS 600000 +#define LOOP_WLAN_CHECK_DELAY_MS 60000 + +#define OLED_SCREEN_WIDTH 128 // OLED display width, in pixels +#define OLED_SCREEN_HEIGHT 64 // OLED display height, in pixels +#define OLED_RESET_PIN -1 // Reset pin # (or -1 if sharing Arduino reset pin) +#define OLED_TEST_SIZE 2 + +#define DISPLAY_SWITCH_SHOWN_VALUE_COUNT 2 + +// default config values +#define DEFAULT_SHUNT_VOLTAGE_DROP 85.0 // mV +#define DEFAULT_SHUNT_CURRENT_MAX 100.0 // A +#define DEFAULT_TEMP_MIN 20.0 // °C environment +#define DEFAULT_TEMP_MAX 30.0 // °C environment +#define DEFAULT_HUMI_MIN 30.0 // % humidity +#define DEFAULT_HUMI_MAX 70.0 // % humidity +#define DEFAULT_CURRENT_MIN -100.0 // maximum continuous discharge current +#define DEFAULT_CURRENT_MAX 100.0 // maximum continuous discharge current +#define DEFAULT_MAX_CAPACITY 2500.0f +#define DEFAULT_INA226READ_DELAY_S 30 // 30 Sekunden default, overwritten by eeprom config + +#define EEPROM_SIZE 100 // in byte +// EEPROM-Adresse, an der die globale Energiemenge gespeichert wird +#define EEPROM_ADDR_ENERGY 0 +#define EEPROM_ADDR_CFG_START 8 // sizeof(struct EnergyData { float energy; uint16_t checksum; }; + +#define FIRMWARE_UPDATE_URL "http://192.168.0.142:8082/firmware.ino.bin" diff --git a/firmware/constants.h b/firmware/constants.h new file mode 100644 index 0000000..9d0472b --- /dev/null +++ b/firmware/constants.h @@ -0,0 +1,88 @@ + +const String cs_configFile = "config_user.h"; +const String cs_configPortalSSID = "powerMC_Config"; +const String cs_connectionError = "Failed to establish connection, and timeout for the configuration portal expired. Restart."; +const String cs_connectedToWiFi = "Connected to WiFi"; +const String cs_ssd1306Init = "SSD1306 display init"; +const String cs_ssd1306NotFound = "SSD1306 display not found. Restart."; +const String cs_ina226Init = "INA226 sensor init"; +const String cs_ina226ChipNotFound = "INA226 chip not found. Restart."; +const String cs_bme280Init = "BME280 sensor init"; +const String cs_bme280NotFound = "Could not find BME280. Restart."; +const String cs_webserverInit = "Webserver init"; +const String cs_rootPath = "/"; +const String cs_jsonPath = "/json"; +const String cs_configPath = "/config"; +const String cs_saveConfigPath = "/saveConfig"; +const String cs_demoMode1Path = "/demo1"; +const String cs_demoMode2Path = "/demo2"; +const String cs_demoMode3Path = "/demo3"; +const String cs_resetESPPath = "/resetESP"; +const String cs_resetWifiPath = "/resetWifi"; +const String cs_checkUpdatePath = "/checkUpdate"; +const String cs_updateFirmwareActionPath = "/updateFirmwareAction"; +const String cs_readUpdateFirmwareStatusPath = "/readUpdateFirmwareStatus"; +const String cs_handleCSSPath = "/css"; +const String cs_enableWatchdog = "Enable the WatchDog"; +const String cs_readCapacityFromEEPROM = "Read available capacity from EEPROM"; +const String cs_readConfigFromEEPROM = "Read config from EEPROM"; +const String cs_initializationComplete = "Initialization completed"; +const String cs_separatorLine = "----------"; +const String cs_busVoltageLabel = "Bus Voltage: "; +const String cs_shuntVoltageLabel = "Shunt Voltage: "; +const String cs_currentLabel = "Current: "; +const String cs_powerLabel = "Power: "; +const String cs_energyLabel = "Energy: "; +const String cs_temperatureLabel = "Temperature: "; +const String cs_humidityLabel = "Humidity: "; +const String cs_configDataLabel = "Config Data:"; +const String cs_tempMinLabel = "Temp Min: "; +const String cs_tempMaxLabel = "Temp Max: "; +const String cs_humiMinLabel = "Humi Min: "; +const String cs_humiMaxLabel = "Humi Max: "; +const String cs_currentMinLabel = "Current Min: "; +const String cs_currentMaxLabel = "Current Max: "; +const String cs_shuntVoltageDropLabel = "Shunt voltage drop: "; +const String cs_shuntMaxCurrentLabel = "Shunt max current: "; +const String cs_timeIna226RefreshLabel = "Ina226 refresh time: "; +const String cs_checksumLabel = "Checksum: "; +const String cs_separator2 = "------------------------"; +const String cs_startingFirmwareUpdate = "Starting firmware update "; +const String cs_finish = "finish"; +const String cs_noUpdatesAvailable = "No Updates available."; +const String cs_fwUpdSuccess = "Firmware update successful, resetting in a few seconds."; +const String cs_unknownStatus = "Unknown status."; +const String cs_fwUpdRunning = "Firmware-Update is running..."; +const String cs_disablingWatchdog = "Disabling watchdog."; +const String cs_ina226ChipNotFoundRestart = "INA226 chip not found. Restart."; +const String cs_wifiDisconnectedRestart = "WiFi connection disconnected. Restart."; +const String cs_textHtml = "text/html"; +const String cs_textCSS = "text/css"; +const String cs_busVoltageID = "busVoltage"; +const String cs_shuntVoltageID = "shuntVoltage"; +const String cs_currentID = "current"; +const String cs_powerID = "power"; +const String cs_energyID = "energy"; +const String cs_tempID = "temp"; +const String cs_humidityID = "humidity"; +const String cs_timeIna226RefreshID = "time_ina226_refresh"; +const String cs_errorCodeID = "errorCode"; +const String cs_applicationJson = "application/json"; +const String cs_ampereUnit = "A"; +const String cs_wattUnit = "W"; +const String cs_wattHourUnit = "Wh"; +const String cs_voltageUnit = "V"; +const String cs_stateOfChargeLabel = "SOC:"; +const String cs_percentageUnit = "%"; +const String cs_savingGlobalEnergyToEEPROM = "Saving global energy to EEPROM"; +const String cs_dataEnergy = "data.energy: "; +const String cs_dataChecksum = "data.checksum: "; +const String cs_eepromWriteComplete = "EEPROM write energy complete "; +const String cs_readChecksum = "Read Checksum: "; +const String cs_calculatedChecksum = "Calculated Checksum: "; +const String cs_eepromReadComplete = "EEPROM read energy complete "; +const String cs_eepromValueDamaged = "EEPROM value for capacity is damaged, returning 0.0"; +const String cs_errorCode = "Error code: 0b"; +const String cs_newChecksum = "New calculated checksum = "; +const String cs_eepromConfigAfterRead = "After EEPROM read - "; +const String cs_eepromSetConfigDefault = "EEPROM-Daten beschädigt! Setze auf Standardwerte."; diff --git a/firmware/data_storage.ino b/firmware/data_storage.ino new file mode 100644 index 0000000..cad333d --- /dev/null +++ b/firmware/data_storage.ino @@ -0,0 +1,120 @@ + +void saveGlobalEnergyToEEPROM() { + +#ifndef DEBUG_NO_SERIAL_MSG + Serial.println(cs_savingGlobalEnergyToEEPROM); +#endif + + EnergyData data; + data.energy = globalEnergy; + // Berechnung der Prüfsumme nur über data.energy + data.checksum = calculateChecksum(data.energy); + + EEPROM.put(EEPROM_ADDR_ENERGY, data); + EEPROM.commit(); // Dauerhaftes Speichern der Änderungen + delay(100); +#ifndef DEBUG_NO_SERIAL_MSG + Serial.print(cs_eepromWriteComplete); + Serial.println(data.energy); +#endif + + //readGlobalEnergyFromEEPROM(); +} + +// Funktion zum Lesen der Energiemenge aus dem EEPROM +float readGlobalEnergyFromEEPROM() { + EnergyData data; + EEPROM.get(EEPROM_ADDR_ENERGY, data); + + /* + Serial.print(cs_readChecksum); + Serial.println(data.checksum); + Serial.print(cs_calculatedChecksum); + Serial.println(calculateChecksum(data.energy)); + */ + + // Überprüfen der Prüfsumme nur über data.energy + if (data.checksum == calculateChecksum(data.energy)) { + // Prüfsumme ist korrekt, geben Sie die Energiemenge zurück + Serial.print(cs_eepromReadComplete); + Serial.print(data.energy); + Serial.println(); + + return data.energy; + + } else { + // Prüfsumme stimmt nicht überein, möglicherweise ungültige Daten + // Hier können Sie eine geeignete Behandlung implementieren, z. B. Standardwert zurückgeben + Serial.println(cs_eepromValueDamaged); + return 0.0; + } +} + +// Funktion zum Berechnen der Prüfsumme über data.energy +uint16_t calculateChecksum(float energy) { + uint16_t checksum = 0; + uint8_t *dataPtr = (uint8_t *)&energy; + + for (int i = 0; i < sizeof(float); i++) { + checksum ^= dataPtr[i]; + } + + return checksum; +} + +void writeConfigToEEPROM() { + globalConfigData.checksum = calculateChecksumConfig(); + + printConfigData(globalConfigData); + + EEPROM.put(EEPROM_ADDR_CFG_START, globalConfigData); + EEPROM.commit(); + delay(100); +} + +void readConfigFromEEPROM() { + EEPROM.get(EEPROM_ADDR_CFG_START, globalConfigData); + + Serial.print(cs_eepromConfigAfterRead); + printConfigData(globalConfigData); + uint16_t checksumEEPROM = globalConfigData.checksum; + + // Überprüfe die Checksumme + uint16_t checksum = calculateChecksumConfig(); + + if (checksum != checksumEEPROM) { + Serial.println(cs_eepromSetConfigDefault); + // Setze auf Standardwerte + globalConfigData.temp_min = DEFAULT_TEMP_MIN; + globalConfigData.temp_max = DEFAULT_TEMP_MAX; + globalConfigData.humi_min = DEFAULT_HUMI_MIN; + globalConfigData.humi_max = DEFAULT_HUMI_MAX; + globalConfigData.current_min = DEFAULT_CURRENT_MIN; + globalConfigData.current_max = DEFAULT_CURRENT_MAX; + globalConfigData.shunt_voltage_drop = DEFAULT_SHUNT_VOLTAGE_DROP; + globalConfigData.shunt_max_current = DEFAULT_SHUNT_CURRENT_MAX; + globalConfigData.max_capacity = DEFAULT_MAX_CAPACITY; + globalConfigData.time_ina226_refresh = DEFAULT_INA226READ_DELAY_S; + globalConfigData.current_fact = 100.0; // offset 100 + globalConfigData.checksum = 0; + + writeConfigToEEPROM(); + } +} + +uint16_t calculateChecksumConfig() +{ + // Berechne die Checksumme + uint16_t checksum = 0; + byte* dataPointer = (byte*)&globalConfigData; + globalConfigData.checksum = 0; + + for (size_t i = 0; i < sizeof(globalConfigData); ++i) { + checksum += dataPointer[i]; + } + + Serial.print(cs_newChecksum); + Serial.println(checksum); + + return checksum; +} diff --git a/firmware/error.h b/firmware/error.h new file mode 100644 index 0000000..fc7f377 --- /dev/null +++ b/firmware/error.h @@ -0,0 +1,10 @@ + +#define ERROR_NONE 0b0000000000000000 +#define ERROR_MAX_CURRENT_EXCEEDED 0b0000000000000001 +#define ERROR_CURRENT_BELOW_MIN 0b0000000000000010 +#define ERROR_MAX_TEMP_EXCEEDED 0b0000000000000100 +#define ERROR_TEMP_BELOW_MIN 0b0000000000001000 +#define ERROR_MAX_HUMI_EXCEEDED 0b0000000000010000 +#define ERROR_HUMI_BELOW_MIN 0b0000000000100000 +#define ERROR_INA226_UNCONFIGURED 0b1000000000000000 + diff --git a/firmware/firmware.ino b/firmware/firmware.ino new file mode 100644 index 0000000..32fc834 --- /dev/null +++ b/firmware/firmware.ino @@ -0,0 +1,476 @@ + +// This project is written for aESP32 WROOM-32 module (AZ Delivery ESP32 Dev Kit C v4) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "error.h" +#include "config_user.h" // includes preprocessor defines +#include "constants.h" + +#ifndef DEBUG_NO_I2C +INA226 ina226(INA226_I2C_ADDRESS); +Adafruit_SSD1306 display(OLED_SCREEN_WIDTH, OLED_SCREEN_HEIGHT, &Wire, OLED_RESET_PIN); +Adafruit_BME280 bme; +#endif + +// WiFiManager initialisieren +WiFiManager wifiManager; +WebServer server(80); + +// Struktur für die gespeicherte Energie mit Prüfsumme +struct EnergyData { + float energy; + uint16_t checksum; +}; + +struct ConfigData { + float temp_min; + float temp_max; + float humi_min; + float humi_max; + float current_min; + float current_max; + float shunt_voltage_drop; + float shunt_max_current; + float max_capacity; + float time_ina226_refresh; + float current_fact; + uint16_t checksum; +}; + +// demo modes +bool demoMode1 = false; +bool demo1Increasing = false; +bool demoMode2 = false; +bool demoMode3 = false; + +// Globale Variable für den Anzeigemodus +bool displayEnergyVoltageMode = false; +uint8_t displayEnergyVoltageModeChangeCnt = 0; + +// Globale Variablen für INA226-Werte +float globalBusVoltage = 0.0; +float globalShuntVoltage = 0.0; +float globalCurrent = 0.0; +float globalPower = 0.0; +float globalShuntValue = 0.0; +// Globale Variable für die Energiemenge in Wattstunden +float globalEnergy = 0.0; +float globalTemp = 0.0; +float globalHumidity = 0.0; + +// global error code +uint16_t globalErrorCode = ERROR_NONE; + +uint16_t ina226Status = 0x0000; + +unsigned long lastINA226CheckTime = 0; +unsigned long lastWiFiCheckTime = 0; +unsigned long lastHandleClientTime = 0; +unsigned long lastLoopDisplayTime = 0; + +String updateStatus; +bool fwUpdate_isRunning = false; + +ConfigData globalConfigData; + +void setup() { + Serial.begin(115200); + + Serial.println(cs_readCapacityFromEEPROM); + EEPROM.begin(EEPROM_SIZE); + // Versuche, die gespeicherte Energiemenge aus dem EEPROM zu laden + globalEnergy = readGlobalEnergyFromEEPROM(); + + Serial.println(cs_readConfigFromEEPROM); + readConfigFromEEPROM(); + printConfigData(globalConfigData); + + // Timeout für die Konfiguration auf 120s setzen + wifiManager.setConfigPortalTimeout(120); + + // Versuche, eine Verbindung herzustellen, und wenn nicht erfolgreich, starte den Konfigurationsportal + if (!wifiManager.autoConnect(cs_configPortalSSID.c_str())) { + Serial.println(cs_connectionError); + delay(10000); + // Resete und versuche es erneut + ESP.restart(); + } + + Serial.println(cs_connectedToWiFi); + + // Initialisiere die I2C-Kommunikation + Wire.begin(); + +#ifndef DEBUG_NO_I2C + Serial.println(cs_ssd1306Init); + // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally + if(!display.begin(SSD1306_SWITCHCAPVCC, OLED_I2C_ADDRESS)) { + Serial.println(cs_ssd1306NotFound); + delay(10000); // Warte 10 Sekunden + ESP.restart(); // Neu starten + } + // Clear the buffer + display.clearDisplay(); + + Serial.println(cs_ina226Init); + if (!ina226.begin()) { + Serial.println(cs_ina226ChipNotFound); + delay(10000); // Warte 10 Sekunden + ESP.restart(); // Neu starten + } + + configureIna226(); + + Serial.println(cs_bme280Init); + if (!bme.begin(BME280_I2C_ADDRESS)) { + Serial.println(cs_bme280NotFound); + delay(10000); // Warte 10 Sekunden + ESP.restart(); // Neu starten + } +#endif // no sensor debug + + Serial.println(cs_webserverInit); + server.on(cs_rootPath, HTTP_GET, handleRoot); + server.on(cs_jsonPath, HTTP_GET, handleJson); + server.on(cs_configPath, HTTP_GET, handleConfig); + server.on(cs_saveConfigPath, HTTP_PUT, handleSaveConfig); + server.on(cs_demoMode1Path, HTTP_GET, handleDemoMode1); + server.on(cs_demoMode2Path, HTTP_GET, handleDemoMode2); + server.on(cs_demoMode3Path, HTTP_GET, handleDemoMode3); + server.on(cs_resetESPPath, HTTP_GET, handleResetESP); + server.on(cs_resetWifiPath, HTTP_GET, handleResetWifi); + server.on(cs_checkUpdatePath, HTTP_GET, handleUpdateFirmware); + server.on(cs_updateFirmwareActionPath, HTTP_GET, handleUpdateFirmwareAction); + server.on(cs_readUpdateFirmwareStatusPath, HTTP_GET, handleReadUpdateFirmwareStatus); + server.on(cs_handleCSSPath, HTTP_GET, handleCSS); + server.begin(); + + enableWatchdog(); + + Serial.println(cs_initializationComplete); + + checkError(); + +#ifndef DEBUG_NO_I2C + //delay(1000); + displayData(); +#endif +} + +void loop() { + static unsigned long lastLoopTime = 0; + + bool demoMode = (demoMode1 || demoMode2 || demoMode3); + + // Führe den Code nur alle xx Sekunden aus + if (millis() - lastLoopTime >= ((demoMode == false) ? (globalConfigData.time_ina226_refresh * 1000) : LOOP_INA226READ_DEMO_DELAY_MS)) { + lastLoopTime = millis(); + + if (demoMode1 == true) + { + simulateUpDownSensorValues(); + + } else if (demoMode2 == true) { + simulateChrgFull(); + + } else if (demoMode3 == true) { + simulateDischrgEmpty(); + + } else { +#ifndef DEBUG_NO_I2C + // INA226-Werte aktualisieren + globalBusVoltage = ina226.getBusVoltage(); + globalShuntVoltage = ina226.getShuntVoltage(); + globalCurrent = ina226.getCurrent() * (globalConfigData.current_fact / 100); + globalPower = ina226.getPower(); +#endif + } + +#ifndef DEBUG_NO_SERIAL_MSG + Serial.println(cs_separatorLine); +#endif + + // Berechne den Energieverbrauch und aktualisiere die globale Energiemenge + updateEnergy(globalCurrent, globalPower); + +#ifndef DEBUG_NO_I2C + globalTemp = bme.readTemperature(); + globalHumidity = bme.readHumidity(); +#endif + + checkError(); + +#ifndef DEBUG_NO_SERIAL_MSG + // Debug-Ausgabe der aktualisierten Werte + Serial.print(cs_busVoltageLabel); + Serial.println(globalBusVoltage); + Serial.print(cs_shuntVoltageLabel); + Serial.println(globalShuntVoltage); + Serial.print(cs_currentLabel); + Serial.println(globalCurrent); + Serial.print(cs_powerLabel); + Serial.println(globalPower); + Serial.print(cs_energyLabel); + Serial.println(globalEnergy); + Serial.print(cs_temperatureLabel); + Serial.println(globalTemp); + Serial.print(cs_humidityLabel); + Serial.println(globalHumidity); +#endif + + } + + if (millis() - lastLoopDisplayTime >= ((demoMode == false) ? LOOP_DISPLAY_DELAY_MS : LOOP_DISPLAY_DEMO_DELAY_MS)) { + lastLoopDisplayTime = millis(); + // Display aktualisieren +#ifndef DEBUG_NO_I2C + displayData(); +#endif + } + + if (millis() - lastHandleClientTime >= LOOP_HANDLE_CLIENT_DELAY_MS) { + lastHandleClientTime = millis(); + server.handleClient(); + } + + // Überprüfe, ob es Zeit ist, die Anwesenheit des INA226-Chip erneut zu überprüfen + if (millis() - lastINA226CheckTime >= LOOP_INA226CHECK_DELAY_MS) { // 10 Minuten = 600000 Millisekunden + lastINA226CheckTime = millis(); + +#ifndef DEBUG_NO_I2C + if (!ina226.begin()) { + Serial.println(cs_ina226ChipNotFoundRestart); + delay(5000); + ESP.restart(); + } +#endif + } + + // Überprüfe, ob WLAN-Verbindung getrennt wurde + if (WiFi.status() == WL_DISCONNECTED && millis() - lastWiFiCheckTime >= LOOP_WLAN_CHECK_DELAY_MS) { // 1 Minute = 60000 Millisekunden + lastWiFiCheckTime = millis(); + Serial.println(cs_wifiDisconnectedRestart); + delay(5000); + ESP.restart(); + } + + // Füttere den Watchdog-Timer, um einen Reset zu vermeiden + esp_task_wdt_reset(); + + // Blockiere die loop() Funktion maximal 100ms + delay(100); + +} + +#ifndef DEBUG_NO_I2C +void displayData() { + display.clearDisplay(); + + // Zeige den Stromwert an + display.setTextSize(OLED_TEST_SIZE); + display.setTextColor(SSD1306_WHITE); + display.setCursor(0, 0); + display.print(globalCurrent, 2); // Zwei Dezimalstellen anzeigen + display.setCursor(100, 0); + display.print(cs_ampereUnit); + + // Zeige die Leistung an + display.setTextColor(SSD1306_WHITE); + display.setCursor(0, 17); + display.print(globalPower, 2); // Zwei Dezimalstellen anzeigen + display.setCursor(100, 17); + display.print(cs_wattUnit); + + //display.setTextSize(1); + + // Zeige den Stromwert oder die Bus-Spannung basierend auf dem Anzeigemodus an + if (displayEnergyVoltageMode) { + // Zeige die verfügbare Energiemenge an + display.setTextSize(OLED_TEST_SIZE); + display.setTextColor(SSD1306_WHITE); + display.setCursor(0, 34); + display.print(globalEnergy, 2); // Zwei Dezimalstellen anzeigen + display.setCursor(100, 34); + display.print(cs_wattHourUnit); + } else { + // Zeige die Bus-Spannung an + display.setTextSize(OLED_TEST_SIZE); + display.setTextColor(SSD1306_WHITE); + display.setCursor(0, 34); + display.print(globalBusVoltage, 2); // Zwei Dezimalstellen anzeigen + display.setCursor(100, 34); + display.print(cs_voltageUnit); + } + + // Wechsle den Anzeigemodus für das nächste Mal + if (displayEnergyVoltageModeChangeCnt >= DISPLAY_SWITCH_SHOWN_VALUE_COUNT) + { + displayEnergyVoltageMode = !displayEnergyVoltageMode; + displayEnergyVoltageModeChangeCnt = 0; + } else { + displayEnergyVoltageModeChangeCnt++; + } + + // Zeige die maximale Kapazität an + display.setCursor(0, 51); + display.print(cs_stateOfChargeLabel); + display.setCursor(50, 51); + float batteryCapacityPercentage = (globalEnergy / globalConfigData.max_capacity) * 100.0; + batteryCapacityPercentage = max(0.0f, min(batteryCapacityPercentage, 100.0f)); + display.print(batteryCapacityPercentage, 1); // Zwei Dezimalstellen anzeigen + display.setCursor(115, 51); + display.print(cs_percentageUnit); + + // Zeige den Pfeil an + if (globalCurrent > 0) { + display.drawLine(120, 10, 116, 5, SSD1306_WHITE); + display.drawLine(120, 10, 124, 5, SSD1306_WHITE); + } else if (globalCurrent < 0) { + display.drawLine(120, 5, 116, 10, SSD1306_WHITE); + display.drawLine(120, 5, 124, 10, SSD1306_WHITE); + } + + display.display(); +} +#endif + +void updateEnergy(float current, float power) { + static unsigned long lastUpdateTime = 0; + static unsigned long lastDailyUpdateCheck = 0; + + // Berechne die vergangene Zeit seit dem letzten Update + unsigned long currentTime = millis(); + float timeInterval = (currentTime - lastUpdateTime) / 1000.0; // Zeitintervall in Sekunden + + // Aktualisiere die globale Energiemenge basierend auf der Leistung und vergangener Zeit + float energyChange = (power * timeInterval) / 3600.0; // Energieänderung in Wattstunden + if (current > 0) + { + globalEnergy += energyChange; + } else { + globalEnergy -= energyChange; + } + + + // Begrenze den Wert auf das Minimum (0) und den maximalen Wert + globalEnergy = max(0.0f, min(globalEnergy, globalConfigData.max_capacity)); + + if (demoMode1 == false && demoMode2 == false && demoMode3 == false) + { + // Speichere die globale Energiemenge im EEPROM + saveGlobalEnergyToEEPROM(); + } + + // Aktualisiere die Zeit des letzten Updates + lastUpdateTime = currentTime; +} + +void checkError() +{ + globalErrorCode = ERROR_NONE; + + if (globalTemp >= globalConfigData.temp_max) { + globalErrorCode |= ERROR_MAX_TEMP_EXCEEDED; + } else if (globalTemp <= globalConfigData.temp_min) { + globalErrorCode |= ERROR_TEMP_BELOW_MIN; + } + if (globalCurrent > globalConfigData.current_max) { + globalErrorCode |= ERROR_MAX_CURRENT_EXCEEDED; + } else if (globalCurrent < globalConfigData.current_min) { + globalErrorCode |= ERROR_CURRENT_BELOW_MIN; + } + if (globalHumidity >= globalConfigData.humi_max) { + globalErrorCode |= ERROR_MAX_HUMI_EXCEEDED; + } else if (globalHumidity <= globalConfigData.humi_min) { + globalErrorCode |= ERROR_HUMI_BELOW_MIN; + } + if (ina226.isCalibrated() == false) + { + globalErrorCode |= ERROR_INA226_UNCONFIGURED; + } + +#ifndef DEBUG_NO_SERIAL_MSG + Serial.print(cs_errorCode); + for (int i = 15; i >= 0; i--) { + Serial.print((globalErrorCode >> i) & 1); + } + Serial.println(); +#endif +} + + +void printConfigData(ConfigData c) { +#ifndef DEBUG_NO_SERIAL_MSG + Serial.println(cs_configDataLabel); + Serial.print(cs_tempMinLabel); + Serial.println(c.temp_min); + Serial.print(cs_tempMaxLabel; + Serial.println(c.temp_max); + Serial.print(cs_humiMinLabel); + Serial.println(c.humi_min); + Serial.print(cs_humiMaxLabel); + Serial.println(c.humi_max); + Serial.print(cs_currentMinLabel); + Serial.println(c.current_min); + Serial.print(cs_currentMaxLabel); + Serial.println(c.current_max); + Serial.print(cs_shuntVoltageDropLabel); + Serial.println(c.shunt_voltage_drop); + Serial.print(cs_shuntMaxCurrentLabel); + Serial.println(c.shunt_max_current); + Serial.print(cs_timeIna226RefreshLabel); + Serial.println(c.time_ina226_refresh); + Serial.print(cs_checksumLabel); + Serial.println(c.checksum); + Serial.println(cs_separator2); +#endif +} + +void enableWatchdog() +{ + Serial.println(cs_enableWatchdog); + // Initialisiere den Watchdog-Timer mit einer Timeout-Zeit von 5 Sekunden + esp_task_wdt_init(WATCHDOG_TIMEOUT_S, true); + // Aktiviere den Watchdog-Timer für den Hauptprozess + esp_task_wdt_add(NULL); +} + +void disableWatchdog() +{ + Serial.println(cs_disablingWatchdog); + esp_task_wdt_deinit(); + esp_task_wdt_delete(NULL); +} + +void configureIna226() +{ + ina226.setMinimalShunt(0.0001); // overwrite INA226_ERR_SHUNT_LOW + + globalShuntValue = (globalConfigData.shunt_voltage_drop / 1000.0) / globalConfigData.shunt_max_current; + Serial.println("Ina226 target shunt size (Ohm): " + String(globalShuntValue)); + + // normalization to 3 floatingpoint accuracy + ina226Status = ina226.setMaxCurrentShunt(globalConfigData.shunt_max_current / 1000.0, + globalShuntValue, true); + if (0x0000 != ina226Status) + { + Serial.print("Ina226 error, invalid configuration: "); + Serial.println(ina226Status, HEX); + } + ina226.setAverage(2); // 2=16 samples will be used to build an average value + Serial.println("Ina226 mode: "+String(ina226.getMode())); + Serial.println("Ina226 shunt ohm: "+String(ina226.getShunt())); + Serial.println("Ina226 is calibrated: "+String(ina226.isCalibrated())); +} diff --git a/firmware/simulation_demo.ino b/firmware/simulation_demo.ino new file mode 100644 index 0000000..d4cb685 --- /dev/null +++ b/firmware/simulation_demo.ino @@ -0,0 +1,89 @@ + +void simulateUpDownSensorValues() { + + if (demo1Increasing == false) { + globalCurrent -= 5.0; + globalPower = 25.6 * globalCurrent; + + // Überprüfe, ob die maximalen Werte erreicht sind + if (globalCurrent < -100.0) { + demo1Increasing = true; + globalCurrent = 0; + } + } else if (demo1Increasing == true) { + globalCurrent += 5.0; + globalPower = 25.6 * globalCurrent; + + } + + // Überprüfe, ob die maximalen Werte erreicht sind + if (globalCurrent > 100.0) { + // Setze Werte auf 0 zurück + globalCurrent = 0.0; + globalPower = 0.0; + globalBusVoltage = 0.0; + globalEnergy = 0.0; + + demoMode1 = false; + demo1Increasing = false; + } + +} + +void simulateChrgFull() { + static unsigned long startTime = 0; + + // Startzeit initialisieren, wenn es der erste Durchlauf ist + if (startTime == 0) { + startTime = millis(); + } + + globalCurrent = 200.0; + globalPower = 25.6 * globalCurrent; + + // Zeitdauer seit dem Start berechnen + unsigned long currentTime = millis(); + unsigned long elapsedTime = currentTime - startTime; + + // Überprüfe, ob die Zeit von 60 Sekunden abgelaufen ist + if (elapsedTime >= 60000) { + // Setze Werte auf 0 zurück + globalCurrent = 0.0; + globalPower = 0.0; + globalBusVoltage = 0.0; + globalEnergy = 0.0; + + // Setze Flags zurück + demoMode2 = false; + startTime = 0; // Zurücksetzen der Startzeit für den nächsten Durchlauf + } +} + +void simulateDischrgEmpty() { + static unsigned long startTime = 0; + + // Startzeit initialisieren, wenn es der erste Durchlauf ist + if (startTime == 0) { + startTime = millis(); + } + + globalCurrent = -200.0; + globalPower = 25.6 * globalCurrent; + + // Zeitdauer seit dem Start berechnen + unsigned long currentTime = millis(); + unsigned long elapsedTime = currentTime - startTime; + + // Überprüfe, ob die Zeit von 60 Sekunden abgelaufen ist + if (elapsedTime >= 60000) { + // Setze Werte auf 0 zurück + globalCurrent = 0.0; + globalPower = 0.0; + globalBusVoltage = 0.0; + globalEnergy = 0.0; + + // Setze Flags zurück + demoMode3 = false; + startTime = 0; // Zurücksetzen der Startzeit für den nächsten Durchlauf + } +} diff --git a/firmware/start_webserver.sh b/firmware/start_webserver.sh new file mode 100755 index 0000000..00851d6 --- /dev/null +++ b/firmware/start_webserver.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +cd build/esp32.esp32.esp32da && python3 -m http.server 8082 +cd - + diff --git a/firmware/webserver.ino b/firmware/webserver.ino new file mode 100644 index 0000000..c136224 --- /dev/null +++ b/firmware/webserver.ino @@ -0,0 +1,565 @@ + +void handleRoot() { + String html = ""; + html += ""; + html += ""; + html += "powerMC (ESP32) "+String(FIRMWARE_VERSION)+""; + html += "

powerMC (ESP32) "+String(FIRMWARE_VERSION)+"

"; + + html += "
"; + + html += "

JSON Daten


"; + html += "

Start demo mode charge and discharge

"; + html += "

Start demo mode charge to max

"; + html += "

Start demo mode discharge to zero


"; + html += "

Reset ESP32

"; + html += "

Reset Wifi settings


"; + html += "

Firmware update


"; + html += "

Config

"; + + html += "
 "; + + html += "

Busvoltage: 0 V

"; + html += "

Shuntvoltage: 0 V

"; + html += "

Strom: 0 A

"; + html += "

Leistung: 0 W

"; + html += "

Energie: 0 Wh

"; + html += "

Temp: 0 °C

"; + html += "

Humidity: 0 %

"; + html += "

Shunt max voltage drop: "+String(globalConfigData.shunt_voltage_drop,0)+" mV

"; + html += "

Shunt max current: "+String(globalConfigData.shunt_max_current,0)+" A

"; + html += "

Current factor: "+String(globalConfigData.current_fact/100,2)+" (div. by 100)

"; + html += "

Max capacity: "+String(globalConfigData.max_capacity,0)+" Wh

"; + html += "

Ina226 refresh period: "+String(globalConfigData.time_ina226_refresh,0)+" s

"; + html += "

Error code: 0

"; + + html += "
"; + + html += ""; + + server.send(200, cs_textHtml, html); +} + +void handleJson() { + // JSON-Daten aus globalen Variablen erstellen + DynamicJsonDocument doc(200); + doc[cs_busVoltageID] = globalBusVoltage; + doc[cs_shuntVoltageID] = globalShuntVoltage; + doc[cs_currentID] = globalCurrent; + doc[cs_powerID] = globalPower; + doc[cs_energyID] = globalEnergy; + doc[cs_tempID] = globalTemp; + doc[cs_humidityID] = globalHumidity; + doc[cs_errorCodeID] = globalErrorCode; + doc["shuntValue"] = globalShuntValue; + doc["ina226Status"] = ina226Status; + doc["ina226ShuntValue"] = ina226.getShunt(); + doc["ina226AlertFlag"] = ina226.getAlertFlag(); + doc["ina226AlertLimit"] = ina226.getAlertLimit(); + doc["ina226CurrentLSB"] = ina226.getCurrentLSB(); + + String jsonData; + serializeJson(doc, jsonData); + + server.send(200, cs_applicationJson, jsonData); +} + +void handleConfig() { + String html = ""; + html += "powerMC (ESP32) "+String(FIRMWARE_VERSION)+" - configuration"; + html += ""; + html += ""; + html += ""; + + html += "

powerMC (ESP32) "+String(FIRMWARE_VERSION)+" - configuration

"; + + html += "

Main


"; + + html += "
"; + + html += ""; + + html += ""; + html += ""; + + html += ""; + html += ""; + + html += ""; + html += ""; + + html += ""; + html += ""; + + html += ""; + html += ""; + + html += ""; + html += ""; + + html += ""; + html += ""; + + html += ""; + html += ""; + + html += ""; + html += ""; + + html += ""; + html += ""; + + html += ""; + html += ""; + + html += "
 "; + html += "" + String(globalConfigData.max_capacity, 0) + "Wh
 "; + html += "" + String(globalConfigData.shunt_voltage_drop, 0) + "mV
 "; + html += "" + String(globalConfigData.shunt_max_current, 0) + "A
 "; + html += "" + String(globalConfigData.temp_min, 0) + "°C
 "; + html += "" + String(globalConfigData.temp_max, 0) + "°C
 "; + html += "" + String(globalConfigData.humi_min, 0) + "%
 "; + html += "" + String(globalConfigData.humi_max, 0) + "%
 "; + html += "" + String(globalConfigData.current_min, 0) + "A
 "; + html += "" + String(globalConfigData.current_max, 0) + "A
 "; + html += "" + String(globalConfigData.current_fact/100,2) + "(div. by 100)
 "; + html += "" + String(globalConfigData.time_ina226_refresh,0) + "sek.

"; + + html += "
"; + html += ""; + + html += "
"; + + html += ""; + + server.send(200, cs_textHtml, html); +} + + +void handleSaveConfig() { + if (server.hasArg("plain")) { + + Serial.println("Received new config data:"); + Serial.println(server.arg("plain")); + + // JSON-Payload analysieren + DynamicJsonDocument jsonDoc(1024); + deserializeJson(jsonDoc, server.arg("plain")); + + // Parameter überprüfen und in die Konfiguration übertragen + if (jsonDoc.containsKey("temp_min")) { + float temp_min = jsonDoc["temp_min"]; + // Hier prüfen und in die globale Konfiguration übertragen + globalConfigData.temp_min = temp_min; + } + + if (jsonDoc.containsKey("temp_max")) { + float temp_max = jsonDoc["temp_max"]; + // Hier prüfen und in die globale Konfiguration übertragen + globalConfigData.temp_max = temp_max; + } + + if (jsonDoc.containsKey("humi_min")) { + float humi_min = jsonDoc["humi_min"]; + // Hier prüfen und in die globale Konfiguration übertragen + globalConfigData.humi_min = humi_min; + } + + if (jsonDoc.containsKey("humi_max")) { + float humi_max = jsonDoc["humi_max"]; + // Hier prüfen und in die globale Konfiguration übertragen + globalConfigData.humi_max = humi_max; + } + + if (jsonDoc.containsKey("current_min")) { + float current_min = jsonDoc["current_min"]; + // Hier prüfen und in die globale Konfiguration übertragen + globalConfigData.current_min = current_min; + } + + if (jsonDoc.containsKey("current_max")) { + float current_max = jsonDoc["current_max"]; + // Hier prüfen und in die globale Konfiguration übertragen + globalConfigData.current_max = current_max; + } + + if (jsonDoc.containsKey("shunt_voltage_drop")) { + float shunt_voltage_drop = jsonDoc["shunt_voltage_drop"]; + // Hier prüfen und in die globale Konfiguration übertragen + globalConfigData.shunt_voltage_drop = shunt_voltage_drop; + } + + if (jsonDoc.containsKey("shunt_max_current")) { + float shunt_max_current = jsonDoc["shunt_max_current"]; + // Hier prüfen und in die globale Konfiguration übertragen + globalConfigData.shunt_max_current = shunt_max_current; + } + + if (jsonDoc.containsKey("max_capacity")) { + float max_capacity = jsonDoc["max_capacity"]; + // Hier prüfen und in die globale Konfiguration übertragen + globalConfigData.max_capacity = max_capacity; + } + + if (jsonDoc.containsKey("time_ina226_refresh")) { + float time_ina226_refresh = jsonDoc["time_ina226_refresh"]; + // Hier prüfen und in die globale Konfiguration übertragen + globalConfigData.time_ina226_refresh = time_ina226_refresh; + } + + if (jsonDoc.containsKey("current_fact")) { + float current_fact = jsonDoc["current_fact"]; + // Hier prüfen und in die globale Konfiguration übertragen + globalConfigData.current_fact = current_fact; + } + + writeConfigToEEPROM(); + + if (ina226.reset()) + { + Serial.println("Ina226 reset"); + configureIna226(); + checkError(); + } else { + Serial.println("Ina226 error: can't reset and reconfigure."); + } + + // Schicke eine Bestätigung zurück + server.send(200, "application/json", "{\"message\": \"ok\"}"); + } else { + // Ungültige Anfrage + server.send(400, "text/plain", "Invalid request"); + } +} + +void handleDemoMode1() { + demoMode1 = true; + demoMode2 = false; + demoMode3 = false; + globalBusVoltage = 25.6; + globalEnergy = globalConfigData.max_capacity / 2; + handleRoot(); +} + +void handleDemoMode2() { + demoMode1 = false; + demoMode2 = true; + demoMode3 = false; + globalBusVoltage = 25.6; + globalEnergy = globalConfigData.max_capacity * 0.97; + handleRoot(); +} + +void handleDemoMode3() { + demoMode1 = false; + demoMode2 = false; + demoMode3 = true; + globalBusVoltage = 25.6; + globalEnergy = globalConfigData.max_capacity * 0.03; + handleRoot(); +} + +void handleResetESP() { + + String message = "powerMC (ESP32) "+String(FIRMWARE_VERSION)+" - reset" + "" + "" + "" + "

powerMC (ESP32) "+String(FIRMWARE_VERSION)+" - reset

" + "Rebooting...
" + ""; + + server.send(200, cs_textHtml, message); + + delay(5000); + + // manual reset after restart is required + ESP.restart(); +} + +void handleResetWifi() { + + String message = "powerMC (ESP32) "+String(FIRMWARE_VERSION)+" - reset WiFi configuration" + "" + "" + "" + "

owerMC (ESP32) "+String(FIRMWARE_VERSION)+" - reset WiFi configuration

" + "Reset WifiManager config, rebooting...
" + ""; + + server.send(200, cs_textHtml, message); + + // Erase WiFi Credentials, enable, compile, flash, disable and reflash. + wifiManager.resetSettings(); + + delay(5000); + + // manual reset after restart is required + ESP.restart(); +} + +void handleUpdateFirmware() { + String message = "powerMC (ESP32) " + String(FIRMWARE_VERSION) + " - firmware update" + "" + "" + "

powerMC (ESP32) " + String(FIRMWARE_VERSION) + " - firmware update

" + "

Main


" + "" + ""; + + server.send(200, cs_textHtml, message); +} + +void handleUpdateFirmwareAction() { + + if (fwUpdate_isRunning) + { + Serial.println("Firmware-Update is running..."); + if (updateStatus == "") { + updateStatus = "Firmware-Update is running...
"; + } + server.send(200, cs_textHtml, updateStatus); + } else { + + String message = "powerMC (ESP32) " + String(FIRMWARE_VERSION) + " - firmware update" +"" +"" +"" +"

powerMC (ESP32) " + String(FIRMWARE_VERSION) + " - firmware update


" +"

Updating...

" +"
" +""; + + server.send(200, cs_textHtml, message); + + disableWatchdog(); + + updateStatus = ""; + + Serial.print(cs_startingFirmwareUpdate); + ESPhttpUpdate.rebootOnUpdate(false); + t_httpUpdate_return ret = ESPhttpUpdate.update(String(FIRMWARE_UPDATE_URL)); + fwUpdate_isRunning = true; + + Serial.println(cs_finish); + + switch (ret) { + case HTTP_UPDATE_FAILED: + Serial.printf("HTTP_UPDATE_FAILED Error (%d): %s\n", ESPhttpUpdate.getLastError(), ESPhttpUpdate.getLastErrorString().c_str()); + updateStatus = "Firmware-Update failed.
"; + updateStatus += "Last error: "+String(ESPhttpUpdate.getLastError())+"
"; + updateStatus += "Last error message: "+ESPhttpUpdate.getLastErrorString()+"
"; + fwUpdate_isRunning = false; + enableWatchdog(); + break; + + case HTTP_UPDATE_NO_UPDATES: + Serial.println(cs_noUpdatesAvailable); + updateStatus = "No Updates available.
"; + fwUpdate_isRunning = false; + enableWatchdog(); + break; + + case HTTP_UPDATE_OK: + Serial.println(cs_fwUpdSuccess); + updateStatus = "Firmware update successful, resetting in a few seconds.
"; + fwUpdate_isRunning = false; + break; + + default: // other + Serial.println(cs_unknownStatus); + updateStatus = "Unknown status.
"; + fwUpdate_isRunning = false; + } + } + +} + +void handleReadUpdateFirmwareStatus() { + if (fwUpdate_isRunning) + { + Serial.println(cs_fwUpdRunning); + if (updateStatus == "") { + updateStatus = "Firmware-Update is running...
"; + } + } + server.send(200, cs_textHtml, updateStatus); +} + +void handleCSS() { + // "" + String customCSS = R"( +body { + font-family: Arial, sans-serif; + margin: 20px; +} + +h1 { + color: #333; +} + +p { + margin: 5px 0; +} + +a { + color: #0066cc; + text-decoration: none; +} + +#busVoltage, +#current, +#power, +#energy, +#temp, +#humidity, +#errorCode { + margin-bottom: 10px; +} + +span { + font-weight: bold; +} + +td { + vertical-align: top; + padding: 10px; +} + +.success-notification { + position: fixed; + bottom: 0; + left: 0; + width: 100%; + background-color: #4CAF50; + color: #fff; + text-align: center; + padding: 10px; +} + +.error-notification { + position: fixed; + bottom: 0; + left: 0; + width: 100%; + background-color: #f44336; + color: #fff; + text-align: center; + padding: 10px; +} + +button { + font-family: Arial, sans-serif; + background-color: #d3d3d3; + color: #000; + padding: 10px 20px; + text-align: center; + text-decoration: none; + display: inline-block; + font-size: 16px; + margin: 4px 2px; + cursor: pointer; +} + +button:active { + background-color: #a9a9a9; /* Dunkleres Grau beim Drücken */ +} +)"; + + server.send(200, cs_textCSS, customCSS); +}