First commit to public repository

This commit is contained in:
Kai Lauterbach 2024-02-07 21:34:37 +01:00
parent bff2d844c3
commit 571a268436
10 changed files with 1513 additions and 1 deletions

BIN
INA226.zip Normal file

Binary file not shown.

117
README.md
View file

@ -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

44
firmware/config_user.h Normal file
View file

@ -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"

88
firmware/constants.h Normal file
View file

@ -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.";

120
firmware/data_storage.ino Normal file
View file

@ -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;
}

10
firmware/error.h Normal file
View file

@ -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

476
firmware/firmware.ino Normal file
View file

@ -0,0 +1,476 @@
// This project is written for aESP32 WROOM-32 module (AZ Delivery ESP32 Dev Kit C v4)
#include <WiFi.h>
#include <ESP32httpUpdate.h>
#include <WebServer.h>
#include <ArduinoJson.h>
#include <WiFiManager.h>
#include <esp_task_wdt.h>
#include <EEPROM.h>
#include <Wire.h>
#include <INA226.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
#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()));
}

View file

@ -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
}
}

5
firmware/start_webserver.sh Executable file
View file

@ -0,0 +1,5 @@
#!/bin/bash
cd build/esp32.esp32.esp32da && python3 -m http.server 8082
cd -

565
firmware/webserver.ino Normal file
View file

@ -0,0 +1,565 @@
void handleRoot() {
String html = "<html><head><script src='https://code.jquery.com/jquery-3.6.4.min.js'></script>";
html += "<link rel=\"stylesheet\" type=\"text/css\" href=\"/css\">";
html += "<link rel='stylesheet' href='https://unpkg.com/purecss@0.6.2/build/pure-min.css'>";
html += "<title>powerMC (ESP32) "+String(FIRMWARE_VERSION)+"</title></head>";
html += "<body><h1>powerMC (ESP32) "+String(FIRMWARE_VERSION)+"</h1>";
html += "<table><tr><td>";
html += "<p><a href='"+cs_jsonPath+"'>JSON Daten</a></p><br>";
html += "<p><a href='"+cs_demoMode1Path+"'>Start demo mode charge and discharge</a></p>";
html += "<p><a href='"+cs_demoMode2Path+"'>Start demo mode charge to max</a></p>";
html += "<p><a href='"+cs_demoMode3Path+"'>Start demo mode discharge to zero</a></p><br>";
html += "<p><a href='"+cs_resetESPPath+"'>Reset ESP32</a></p>";
html += "<p><a href='"+cs_resetWifiPath+"'>Reset Wifi settings</a></p><br>";
html += "<p><a href='"+cs_checkUpdatePath+"'>Firmware update</a></p><br>";
html += "<p><a href='"+cs_configPath+"'>Config</a></p>";
html += "</td><td>&nbsp;</td><td>";
html += "<p id='busVoltage'>Busvoltage: <span id='busVoltageValue'>0</span> V</p>";
html += "<p id='shuntVoltage'>Shuntvoltage: <span id='shuntVoltageValue'>0</span> V</p>";
html += "<p id='current'>Strom: <span id='currentValue'>0</span> A</p>";
html += "<p id='power'>Leistung: <span id='powerValue'>0</span> W</p>";
html += "<p id='energy'>Energie: <span id='energyValue'>0</span> Wh</p>";
html += "<p id='temp'>Temp: <span id='tempValue'>0</span> &deg;C</p>";
html += "<p id='humidity'>Humidity: <span id='humidityValue'>0</span> %</p>";
html += "<p>Shunt max voltage drop: <span>"+String(globalConfigData.shunt_voltage_drop,0)+"</span> mV</p>";
html += "<p>Shunt max current: <span>"+String(globalConfigData.shunt_max_current,0)+"</span> A</p>";
html += "<p>Current factor: <span>"+String(globalConfigData.current_fact/100,2)+" (div. by 100)</span></p>";
html += "<p>Max capacity: <span>"+String(globalConfigData.max_capacity,0)+"</span> Wh</p>";
html += "<p>Ina226 refresh period: <span>"+String(globalConfigData.time_ina226_refresh,0)+"</span> s</p>";
html += "<p id='errorCode'>Error code: <span id='error'>0</span></p>";
html += "</td></tr></table>";
html += "<script>";
html += "function showNotification(message, isSuccess) {";
html += " var notification = document.createElement('div');";
html += " notification.className = isSuccess ? 'success-notification' : 'error-notification';";
html += " notification.innerHTML = message;";
html += " document.body.appendChild(notification);";
html += " setTimeout(function() {";
html += " document.body.removeChild(notification);";
html += " }, 3000);";
html += "}";
html += "function updateValues() {";
html += "$.ajax({";
html += "url: '/json',";
html += "type: 'GET',";
html += "dataType: 'json',";
html += "success: function(data) {";
html += "$('#currentValue').text(data.current);";
html += "$('#powerValue').text(data.power);";
html += "$('#energyValue').text(data.energy);";
html += "$('#busVoltageValue').text(data.busVoltage);";
html += "$('#shuntVoltageValue').text(data.shuntVoltage);";
html += "$('#tempValue').text(data.temp.toFixed(2));";
html += "$('#humidityValue').text(data.humidity.toFixed(2));";
html += "$('#error').text('0b' + data.errorCode.toString(2).padStart(16, '0'));"; // Hier wird der errorCode als Binärzahl dargestellt
html += "},";
html += "error: function() {";
html += "console.log('Fehler beim Abrufen der Daten.');";
html += "}";
html += "});";
html += "}";
html += "updateValues();";
html += "setInterval(updateValues, " + String(LOOP_DISPLAY_DELAY_MS) + ");";
html += "</script></body></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><head>";
html += "<title>powerMC (ESP32) "+String(FIRMWARE_VERSION)+" - configuration</title>";
html += "<link rel=\"stylesheet\" type=\"text/css\" href=\"/css\">";
html += "<link rel='stylesheet' href='https://unpkg.com/purecss@0.6.2/build/pure-min.css'>";
html += "</head>";
html += "<body><h1>powerMC (ESP32) "+String(FIRMWARE_VERSION)+" - configuration</h1>";
html += "<p><a href='"+cs_rootPath+"'>Main</a></p><br>";
html += "<form id=\"configForm\" action=\"/saveConfig\" method=\"post\">";
html += "<table>";
html += "<tr><td><label for=\"temp_min\">Max capacity:</label></td><td>&nbsp;</td>";
html += "<td class=\"slider-container\"><input type=\"range\" id=\"max_capacity\" name=\"max_capacity\" value=\"" + String(globalConfigData.max_capacity) + "\" min=\"2000\" max=\"10000\" step=\"1\">";
html += "<span class=\"slider-value\" id=\"max_capacity_value\">" + String(globalConfigData.max_capacity, 0) + "</span>Wh</td></tr>";
html += "<tr><td><label for=\"temp_min\">Shunt voltage drop:</label></td><td>&nbsp;</td>";
html += "<td class=\"slider-container\"><input type=\"range\" id=\"shunt_voltage_drop\" name=\"shunt_voltage_drop\" value=\"" + String(globalConfigData.shunt_voltage_drop) + "\" min=\"10\" max=\"85\" step=\"1\">";
html += "<span class=\"slider-value\" id=\"shunt_voltage_drop_value\">" + String(globalConfigData.shunt_voltage_drop, 0) + "</span>mV</td></tr>";
html += "<tr><td><label for=\"shunt_max_current\">Shunt current Max:</label></td><td>&nbsp;</td>";
html += "<td class=\"slider-container\"><input type=\"range\" id=\"shunt_max_current\" name=\"shunt_max_current\" value=\"" + String(globalConfigData.shunt_max_current) + "\" min=\"10\" max=\"200\" step=\"1\">";
html += "<span class=\"slider-value\" id=\"shunt_max_current_value\">" + String(globalConfigData.shunt_max_current, 0) + "</span>A</td></tr>";
html += "<tr><td><label for=\"temp_min\">Temperature Min:</label></td><td>&nbsp;</td>";
html += "<td class=\"slider-container\"><input type=\"range\" id=\"temp_min\" name=\"temp_min\" value=\"" + String(globalConfigData.temp_min) + "\" min=\"0\" max=\"100\" step=\"1\">";
html += "<span class=\"slider-value\" id=\"temp_min_value\">" + String(globalConfigData.temp_min, 0) + "</span>&deg;C</td></tr>";
html += "<tr><td><label for=\"temp_max\">Temperature Max:</label></td><td>&nbsp;</td>";
html += "<td class=\"slider-container\"><input type=\"range\" id=\"temp_max\" name=\"temp_max\" value=\"" + String(globalConfigData.temp_max) + "\" min=\"0\" max=\"100\" step=\"1\">";
html += "<span class=\"slider-value\" id=\"temp_max_value\">" + String(globalConfigData.temp_max, 0) + "</span>&deg;C</td></tr>";
html += "<tr><td><label for=\"humidity_min\">Himidity Min:</label></td><td>&nbsp;</td>";
html += "<td class=\"slider-container\"><input type=\"range\" id=\"humi_min\" name=\"humi_min\" value=\"" + String(globalConfigData.humi_min) + "\" min=\"0\" max=\"100\" step=\"1\">";
html += "<span class=\"slider-value\" id=\"humi_min_value\">" + String(globalConfigData.humi_min, 0) + "</span>%</td></tr>";
html += "<tr><td><label for=\"humidity_max\">Humidity Max:</label></td><td>&nbsp;</td>";
html += "<td class=\"slider-container\"><input type=\"range\" id=\"humi_max\" name=\"humi_max\" value=\"" + String(globalConfigData.humi_max) + "\" min=\"0\" max=\"100\" step=\"1\">";
html += "<span class=\"slider-value\" id=\"humi_max_value\">" + String(globalConfigData.humi_max, 0) + "</span>%</td></tr>";
html += "<tr><td><label for=\"current_min\">Current Min:</label></td><td>&nbsp;</td>";
html += "<td class=\"slider-container\"><input type=\"range\" id=\"current_min\" name=\"current_min\" value=\"" + String(globalConfigData.current_min) + "\" min=\"-100\" max=\"100\" step=\"1\">";
html += "<span class=\"slider-value\" id=\"current_min_value\">" + String(globalConfigData.current_min, 0) + "</span>A</td></tr>";
html += "<tr><td><label for=\"current_max\">Current Max:</label></td><td>&nbsp;</td>";
html += "<td class=\"slider-container\"><input type=\"range\" id=\"current_max\" name=\"current_max\" value=\"" + String(globalConfigData.current_max) + "\" min=\"-100\" max=\"100\" step=\"1\">";
html += "<span class=\"slider-value\" id=\"current_max_value\">" + String(globalConfigData.current_max, 0) + "</span>A</td></tr>";
html += "<tr><td><label for=\"current_fact\">Current correction factor:</label></td><td>&nbsp;</td>";
html += "<td class=\"slider-container\"><input type=\"range\" id=\"current_fact\" name=\"current_fact\" value=\"" + String(globalConfigData.current_fact,0) + "\" min=\"1\" max=\"3000\" step=\"1\">";
html += "<span class=\"slider-value\" id=\"current_fact_value\">" + String(globalConfigData.current_fact/100,2) + "</span>(div. by 100)</td></tr>";
html += "<tr><td><label for=\"time_ina226_refresh\">Ina226 update interval (s):</label></td><td>&nbsp;</td>";
html += "<td class=\"slider-container\"><input type=\"range\" id=\"time_ina226_refresh\" name=\"time_ina226_refresh\" value=\"" + String(globalConfigData.time_ina226_refresh,0) + "\" min=\"10\" max=\"120\" step=\"1\">";
html += "<span class=\"slider-value\" id=\"time_ina226_refresh_value\">" + String(globalConfigData.time_ina226_refresh,0) + "</span>sek.</td></tr>";
html += "</table><br>";
html += "<button type=\"button\" onclick=\"saveConfig()\">Save Config</button></form>";
html += "<button onclick=\"location.reload()\">Reset</button>";
html += "<div id=\"responseMessage\" style=\"display: none;\"></div>";
html += "<script>";
html += "function showNotification(message, isSuccess) {";
html += " var notification = document.createElement('div');";
html += " notification.className = isSuccess ? 'success-notification' : 'error-notification';";
html += " notification.innerHTML = message;";
html += " document.body.appendChild(notification);";
html += " setTimeout(function() {";
html += " document.body.removeChild(notification);";
html += " }, 3000);";
html += "}";
html += "function saveConfig() {var form = document.getElementById('configForm');";
html += "var formData = new FormData(form);";
html += "var jsonData = {};";
html += "formData.forEach(function(value, key){jsonData[key] = value;});";
html += "fetch('/saveConfig', {method: 'PUT',headers: {'Content-Type': 'application/json'},body: JSON.stringify(jsonData)})";
html += ".then(response => response.json())";
html += ".then(data => { showNotification('Successful saved config', data.message.toLowerCase() === 'ok'); })";
html += ".catch(error => { console.error('Error:', error); });}";
html += "document.getElementById('temp_min').addEventListener('input', function() {";
html += "document.getElementById('temp_min_value').innerText = this.value;});";
html += "document.getElementById('temp_max').addEventListener('input', function() {";
html += "document.getElementById('temp_max_value').innerText = this.value;});";
html += "document.getElementById('humi_min').addEventListener('input', function() {";
html += "document.getElementById('humi_min_value').innerText = this.value;});";
html += "document.getElementById('humi_max').addEventListener('input', function() {";
html += "document.getElementById('humi_max_value').innerText = this.value;});";
html += "document.getElementById('current_min').addEventListener('input', function() {";
html += "document.getElementById('current_min_value').innerText = this.value;});";
html += "document.getElementById('current_max').addEventListener('input', function() {";
html += "document.getElementById('current_max_value').innerText = this.value;});";
html += "document.getElementById('current_fact').addEventListener('input', function() {";
html += "document.getElementById('current_fact_value').innerText = this.value / 100.0;});";
html += "document.getElementById('shunt_voltage_drop').addEventListener('input', function() {";
html += "document.getElementById('shunt_voltage_drop_value').innerText = this.value;});";
html += "document.getElementById('shunt_max_current').addEventListener('input', function() {";
html += "document.getElementById('shunt_max_current_value').innerText = this.value;});";
html += "document.getElementById('max_capacity').addEventListener('input', function() {";
html += "document.getElementById('max_capacity_value').innerText = this.value;});";
html += "document.getElementById('time_ina226_refresh').addEventListener('input', function() {";
html += "document.getElementById('time_ina226_refresh_value').innerText = this.value;});";
html += "</script></body></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 = "<html><head><title>powerMC (ESP32) "+String(FIRMWARE_VERSION)+" - reset</title>"
"<script>setTimeout(function() { window.location.href = '/'; }, 10000);</script>"
"<link rel=\"stylesheet\" type=\"text/css\" href=\"/css\">"
"<link rel='stylesheet' href='https://unpkg.com/purecss@0.6.2/build/pure-min.css'>"
"</head><body><h1>powerMC (ESP32) "+String(FIRMWARE_VERSION)+" - reset</h1>"
"Rebooting...<br>"
"</body></html>";
server.send(200, cs_textHtml, message);
delay(5000);
// manual reset after restart is required
ESP.restart();
}
void handleResetWifi() {
String message = "<html><head><title>powerMC (ESP32) "+String(FIRMWARE_VERSION)+" - reset WiFi configuration</title>"
"<script>setTimeout(function() { window.location.href = '/'; }, 10000);</script>"
"<link rel=\"stylesheet\" type=\"text/css\" href=\"/css\">"
"<link rel='stylesheet' href='https://unpkg.com/purecss@0.6.2/build/pure-min.css'>"
"</head><body><h1>owerMC (ESP32) "+String(FIRMWARE_VERSION)+" - reset WiFi configuration</h1>"
"Reset WifiManager config, rebooting...<br>"
"</body></html>";
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 = "<html><head><title>powerMC (ESP32) " + String(FIRMWARE_VERSION) + " - firmware update</title>"
"<link rel=\"stylesheet\" type=\"text/css\" href=\"/css\">"
"<link rel='stylesheet' href='https://unpkg.com/purecss@0.6.2/build/pure-min.css'>"
"</head><body><h1>powerMC (ESP32) " + String(FIRMWARE_VERSION) + " - firmware update</h1>"
"<p><a href='"+cs_rootPath+"'>Main</a></p><br>"
"<button onclick='window.location.href=\"/updateFirmwareAction\";'>Update Firmware</button>"
"</body></html>";
server.send(200, cs_textHtml, message);
}
void handleUpdateFirmwareAction() {
if (fwUpdate_isRunning)
{
Serial.println("Firmware-Update is running...");
if (updateStatus == "") {
updateStatus = "Firmware-Update is running...<br>";
}
server.send(200, cs_textHtml, updateStatus);
} else {
String message = "<html><head><title>powerMC (ESP32) " + String(FIRMWARE_VERSION) + " - firmware update</title>"
"<script>"
"function readUpdateFirmwareStatus() {"
" var xhr = new XMLHttpRequest();"
" xhr.onreadystatechange = function() {"
" if (xhr.readyState == 4) {"
" document.getElementById('updateStatus').innerHTML = xhr.responseText;"
" if (xhr.responseText.includes('Firmware update successful')) { "
" setTimeout(function() { "
" window.location.href = '" + cs_resetESPPath + "'; "
" }, 5000); }"
" }"
" };"
" xhr.timeout = 1000;"
" xhr.open('GET', '/readUpdateFirmwareStatus', true);"
" xhr.send();"
"}"
"setInterval(readUpdateFirmwareStatus, 2000);"
"</script>"
"<link rel=\"stylesheet\" type=\"text/css\" href=\"/css\">"
"<link rel='stylesheet' href='https://unpkg.com/purecss@0.6.2/build/pure-min.css'>"
"</head><body><h1>powerMC (ESP32) " + String(FIRMWARE_VERSION) + " - firmware update</h1><br>"
"<p>Updating...</p>"
"<div id='updateStatus'></div>"
"</body></html>";
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 = "<b>Firmware-Update failed.</b><br>";
updateStatus += "<b>Last error: "+String(ESPhttpUpdate.getLastError())+"</b><br>";
updateStatus += "<b>Last error message: "+ESPhttpUpdate.getLastErrorString()+"</b><br>";
fwUpdate_isRunning = false;
enableWatchdog();
break;
case HTTP_UPDATE_NO_UPDATES:
Serial.println(cs_noUpdatesAvailable);
updateStatus = "<b>No Updates available.</b><br>";
fwUpdate_isRunning = false;
enableWatchdog();
break;
case HTTP_UPDATE_OK:
Serial.println(cs_fwUpdSuccess);
updateStatus = "<b>Firmware update successful, resetting in a few seconds.</b><br>";
fwUpdate_isRunning = false;
break;
default: // other
Serial.println(cs_unknownStatus);
updateStatus = "<b>Unknown status.</b><br>";
fwUpdate_isRunning = false;
}
}
}
void handleReadUpdateFirmwareStatus() {
if (fwUpdate_isRunning)
{
Serial.println(cs_fwUpdRunning);
if (updateStatus == "") {
updateStatus = "Firmware-Update is running...<br>";
}
}
server.send(200, cs_textHtml, updateStatus);
}
void handleCSS() {
// "<link rel="stylesheet" type="text/css" href="/css">"
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);
}