477 lines
14 KiB
Arduino
477 lines
14 KiB
Arduino
|
|
||
|
// 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()));
|
||
|
}
|