From 571a268436f5b13ceec277df76f0c6dfe40bd89d Mon Sep 17 00:00:00 2001 From: Kai Lauterbach Date: Wed, 7 Feb 2024 21:34:37 +0100 Subject: [PATCH] First commit to public repository --- INA226.zip | Bin 0 -> 28705 bytes README.md | 117 +++++++- firmware/config_user.h | 44 +++ firmware/constants.h | 88 ++++++ firmware/data_storage.ino | 120 ++++++++ firmware/error.h | 10 + firmware/firmware.ino | 476 +++++++++++++++++++++++++++++ firmware/simulation_demo.ino | 89 ++++++ firmware/start_webserver.sh | 5 + firmware/webserver.ino | 565 +++++++++++++++++++++++++++++++++++ 10 files changed, 1513 insertions(+), 1 deletion(-) create mode 100644 INA226.zip create mode 100644 firmware/config_user.h create mode 100644 firmware/constants.h create mode 100644 firmware/data_storage.ino create mode 100644 firmware/error.h create mode 100644 firmware/firmware.ino create mode 100644 firmware/simulation_demo.ino create mode 100755 firmware/start_webserver.sh create mode 100644 firmware/webserver.ino diff --git a/INA226.zip b/INA226.zip new file mode 100644 index 0000000000000000000000000000000000000000..3f26b76a750ebcfc2c04a4ea14d2b289337a5896 GIT binary patch literal 28705 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 literal 0 HcmV?d00001 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); +}