ESP32_powerMC/firmware/firmware.ino
2024-02-07 21:34:37 +01:00

477 lines
14 KiB
C++

// 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()));
}