diff --git a/.gitignore b/.gitignore index 65ad4290..448c8864 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,4 @@ *.vscode/ # Ignore any files in the build folder -Software/build/ +Software/build/ \ No newline at end of file diff --git a/Software/Software.ino b/Software/Software.ino index 2ec566d6..df20927a 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -8,6 +8,7 @@ #include "src/battery/BATTERIES.h" #include "src/charger/CHARGERS.h" #include "src/devboard/config.h" +#include "src/devboard/utils/events.h" #include "src/inverter/INVERTERS.h" #include "src/lib/adafruit-Adafruit_NeoPixel/Adafruit_NeoPixel.h" #include "src/lib/eModbus-eModbus/Logging.h" @@ -20,8 +21,8 @@ #include "src/devboard/webserver/webserver.h" #endif -Preferences settings; // Store user settings - +Preferences settings; // Store user settings +const char* version_number = "5.2.0"; // The current software version, shown on webserver // Interval settings int intervalUpdateValues = 4800; // Interval at which to update inverter values / Modbus registers const int interval10 = 10; // Interval for 10ms tasks @@ -129,6 +130,10 @@ void setup() { init_webserver(); #endif +#ifdef EVENTLOGGING + init_events(); +#endif + init_CAN(); init_LED(); @@ -153,8 +158,8 @@ void loop() { #ifdef WEBSERVER // Over-the-air updates by ElegantOTA + wifi_monitor(); ElegantOTA.loop(); - WiFi_monitor_loop(); #ifdef MQTT mqtt_loop(); #endif @@ -183,6 +188,9 @@ void loop() { { previousMillisUpdateVal = millis(); update_values(); // Update values heading towards inverter. Prepare for sending on CAN, or write directly to Modbus. + if (DUMMY_EVENT_ENABLED) { + set_event(EVENT_DUMMY, (uint8_t)millis()); + } } // Output @@ -190,6 +198,7 @@ void loop() { #ifdef DUAL_CAN send_can2(); #endif + update_event_timestamps(); } // Initialization functions diff --git a/Software/USER_SETTINGS.cpp b/Software/USER_SETTINGS.cpp index c248b11a..9ef1f9d5 100644 --- a/Software/USER_SETTINGS.cpp +++ b/Software/USER_SETTINGS.cpp @@ -36,5 +36,5 @@ const char* ssid = "REPLACE_WITH_YOUR_SSID"; // Maximum of 63 character const char* password = "REPLACE_WITH_YOUR_PASSWORD"; // Minimum of 8 characters; const char* ssidAP = "Battery Emulator"; // Maximum of 63 characters; const char* passwordAP = "123456789"; // Minimum of 8 characters; set to NULL if you want the access point to be open -const char* versionNumber = "5.0.1"; // The current software version, shown on webserver +const uint8_t wifi_channel = 0; // set to 0 for automatic channel selection #endif diff --git a/Software/USER_SETTINGS.h b/Software/USER_SETTINGS.h index 0b4fe1c1..4b69ef06 100644 --- a/Software/USER_SETTINGS.h +++ b/Software/USER_SETTINGS.h @@ -46,6 +46,10 @@ #define MQTT_SERVER "192.168.xxx.yyy" #define MQTT_PORT 1883 +/* Event options*/ +#define EVENTLOGGING //Enable this line to log events to the event log +#define DUMMY_EVENT_ENABLED false //Enable this line to have a dummy event that gets logged to test the interface + /* Select charger used (Optional) */ //#define CHEVYVOLT_CHARGER //Enable this line to control a Chevrolet Volt charger connected to battery - for example, when generator charging or using an inverter without a charging function. //#define NISSANLEAF_CHARGER //Enable this line to control a Nissan LEAF PDM connected to battery - for example, when generator charging @@ -71,4 +75,6 @@ extern volatile float CHARGER_END_A; extern bool charger_HV_enabled; extern bool charger_aux12V_enabled; +extern const uint8_t wifi_channel; + #endif diff --git a/Software/src/battery/BMW-I3-BATTERY.cpp b/Software/src/battery/BMW-I3-BATTERY.cpp index 935da03e..40acb0e2 100644 --- a/Software/src/battery/BMW-I3-BATTERY.cpp +++ b/Software/src/battery/BMW-I3-BATTERY.cpp @@ -1,4 +1,5 @@ #include "BMW-I3-BATTERY.h" +#include "../devboard/utils/events.h" #include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h" #include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h" @@ -103,6 +104,7 @@ void update_values_i3_battery() { //This function maps all the values fetched v if (!CANstillAlive) { bms_status = FAULT; Serial.println("No CAN communication detected for 60s. Shutting down battery control."); + set_event(EVENT_CAN_FAILURE, 0); } else { CANstillAlive--; } diff --git a/Software/src/battery/CHADEMO-BATTERY.cpp b/Software/src/battery/CHADEMO-BATTERY.cpp index 929c0a15..84a3634d 100644 --- a/Software/src/battery/CHADEMO-BATTERY.cpp +++ b/Software/src/battery/CHADEMO-BATTERY.cpp @@ -1,4 +1,5 @@ #include "CHADEMO-BATTERY.h" +#include "../devboard/utils/events.h" #include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h" #include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h" @@ -107,6 +108,7 @@ void update_values_chademo_battery() { //This function maps all the values fetc bms_status = FAULT; errorCode = 7; Serial.println("No CAN communication detected for 60s. Shutting down battery control."); + set_event(EVENT_CAN_FAILURE, 0); } else { CANstillAlive--; } diff --git a/Software/src/battery/IMIEV-CZERO-ION-BATTERY.cpp b/Software/src/battery/IMIEV-CZERO-ION-BATTERY.cpp index f9a4d97d..51ebdba9 100644 --- a/Software/src/battery/IMIEV-CZERO-ION-BATTERY.cpp +++ b/Software/src/battery/IMIEV-CZERO-ION-BATTERY.cpp @@ -1,4 +1,5 @@ #include "IMIEV-CZERO-ION-BATTERY.h" +#include "../devboard/utils/events.h" #include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h" #include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h" @@ -109,6 +110,7 @@ void update_values_imiev_battery() { //This function maps all the values fetche if (!CANstillAlive) { bms_status = FAULT; Serial.println("No CAN communication detected for 60s. Shutting down battery control."); + set_event(EVENT_CAN_FAILURE, 0); } else { CANstillAlive--; } diff --git a/Software/src/battery/KIA-HYUNDAI-64-BATTERY.cpp b/Software/src/battery/KIA-HYUNDAI-64-BATTERY.cpp index 40c948cd..e1b27df7 100644 --- a/Software/src/battery/KIA-HYUNDAI-64-BATTERY.cpp +++ b/Software/src/battery/KIA-HYUNDAI-64-BATTERY.cpp @@ -1,4 +1,5 @@ #include "KIA-HYUNDAI-64-BATTERY.h" +#include "../devboard/utils/events.h" #include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h" #include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h" @@ -200,6 +201,7 @@ void update_values_kiaHyundai_64_battery() { //This function maps all the value if (!CANstillAlive) { bms_status = FAULT; Serial.println("No CAN communication detected for 60s. Shutting down battery control."); + set_event(EVENT_CAN_FAILURE, 0); } else { CANstillAlive--; } @@ -207,11 +209,13 @@ void update_values_kiaHyundai_64_battery() { //This function maps all the value if (waterleakageSensor == 0) { Serial.println("Water leakage inside battery detected. Operation halted. Inspect battery!"); bms_status = FAULT; + set_event(EVENT_WATER_INGRESS, 0); } if (leadAcidBatteryVoltage < 110) { Serial.println("12V battery source below required voltage to safely close contactors. Inspect the supply/battery!"); LEDcolor = YELLOW; + set_event(EVENT_12V_LOW, leadAcidBatteryVoltage); } // Check if cell voltages are within allowed range @@ -220,14 +224,17 @@ void update_values_kiaHyundai_64_battery() { //This function maps all the value if (cell_max_voltage >= MAX_CELL_VOLTAGE) { bms_status = FAULT; Serial.println("ERROR: CELL OVERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!"); + set_event(EVENT_CELL_OVER_VOLTAGE, 0); } if (cell_min_voltage <= MIN_CELL_VOLTAGE) { bms_status = FAULT; Serial.println("ERROR: CELL UNDERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!"); + set_event(EVENT_CELL_UNDER_VOLTAGE, 0); } if (cell_deviation_mV > MAX_CELL_DEVIATION) { LEDcolor = YELLOW; Serial.println("ERROR: HIGH CELL DEVIATION!!! Inspect battery!"); + set_event(EVENT_CELL_DEVIATION_HIGH, 0); } if (bms_status == FAULT) { //Incase we enter a critical fault state, zero out the allowed limits diff --git a/Software/src/battery/NISSAN-LEAF-BATTERY.cpp b/Software/src/battery/NISSAN-LEAF-BATTERY.cpp index 89a65cc2..f8d4e29b 100644 --- a/Software/src/battery/NISSAN-LEAF-BATTERY.cpp +++ b/Software/src/battery/NISSAN-LEAF-BATTERY.cpp @@ -2,6 +2,7 @@ #ifdef MQTT #include "../devboard/mqtt/mqtt.h" #endif +#include "../devboard/utils/events.h" #include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h" #include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h" @@ -262,6 +263,7 @@ void update_values_leaf_battery() { /* This function maps all the values fetched #ifdef DEBUG_VIA_USB Serial.println("ERROR: SOC% reported by battery not plausible. Restart battery!"); #endif + set_event(EVENT_SOC_PLAUSIBILITY_ERROR, LB_SOC / 10); } } @@ -311,6 +313,7 @@ void update_values_leaf_battery() { /* This function maps all the values fetched #ifdef DEBUG_VIA_USB Serial.println("ERROR: Battery raised caution indicator AND requested discharge stop. Inspect battery status!"); #endif + set_event(EVENT_BATTERY_DISCHG_STOP_REQ, 0); break; case (6): //Caution Lamp Request & Charging Mode Stop Request @@ -319,6 +322,7 @@ void update_values_leaf_battery() { /* This function maps all the values fetched #ifdef DEBUG_VIA_USB Serial.println("ERROR: Battery raised caution indicator AND requested charge stop. Inspect battery status!"); #endif + set_event(EVENT_BATTERY_CHG_STOP_REQ, 0); break; case (7): //Caution Lamp Request & Charging Mode Stop Request & Normal Stop Request @@ -328,6 +332,7 @@ void update_values_leaf_battery() { /* This function maps all the values fetched Serial.println( "ERROR: Battery raised caution indicator AND requested charge/discharge stop. Inspect battery status!"); #endif + set_event(EVENT_BATTERY_CHG_DISCHG_STOP_REQ, 0); break; default: break; @@ -342,6 +347,7 @@ void update_values_leaf_battery() { /* This function maps all the values fetched #endif bms_status = FAULT; errorCode = 5; + set_event(EVENT_LOW_SOH, LB_StateOfHealth); max_target_discharge_power = 0; max_target_charge_power = 0; } @@ -355,6 +361,7 @@ void update_values_leaf_battery() { /* This function maps all the values fetched "disabled!"); #endif bms_status = FAULT; + set_event(EVENT_HVIL_FAILURE, 0); errorCode = 6; SOC = 0; max_target_discharge_power = 0; @@ -369,6 +376,7 @@ void update_values_leaf_battery() { /* This function maps all the values fetched #ifdef DEBUG_VIA_USB Serial.println("ERROR: No CAN communication detected for 60s. Shutting down battery control."); #endif + set_event(EVENT_CAN_FAILURE, 0); } else { CANstillAlive--; } @@ -380,6 +388,7 @@ void update_values_leaf_battery() { /* This function maps all the values fetched #ifdef DEBUG_VIA_USB Serial.println("ERROR: High amount of corrupted CAN messages detected. Check CAN wire shielding!"); #endif + set_event(EVENT_CAN_WARNING, 0); } /*Finally print out values to serial if configured to do so*/ @@ -615,6 +624,7 @@ void receive_can_leaf_battery(CAN_frame_t rx_frame) { #ifdef DEBUG_VIA_USB Serial.println("HIGH CELL DEVIATION!!! Inspect battery!"); #endif + set_event(EVENT_CELL_DEVIATION_HIGH, 0); } if (min_max_voltage[1] >= MAX_CELL_VOLTAGE) { @@ -623,6 +633,7 @@ void receive_can_leaf_battery(CAN_frame_t rx_frame) { #ifdef DEBUG_VIA_USB Serial.println("CELL OVERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!"); #endif + set_event(EVENT_CELL_OVER_VOLTAGE, 0); } if (min_max_voltage[0] <= MIN_CELL_VOLTAGE) { bms_status = FAULT; @@ -630,6 +641,7 @@ void receive_can_leaf_battery(CAN_frame_t rx_frame) { #ifdef DEBUG_VIA_USB Serial.println("CELL UNDERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!"); #endif + set_event(EVENT_CELL_UNDER_VOLTAGE, 0); } break; } diff --git a/Software/src/battery/RENAULT-KANGOO-BATTERY.cpp b/Software/src/battery/RENAULT-KANGOO-BATTERY.cpp index 4f818878..47ab1f64 100644 --- a/Software/src/battery/RENAULT-KANGOO-BATTERY.cpp +++ b/Software/src/battery/RENAULT-KANGOO-BATTERY.cpp @@ -1,4 +1,5 @@ #include "RENAULT-KANGOO-BATTERY.h" +#include "../devboard/utils/events.h" #include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h" #include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h" @@ -126,6 +127,7 @@ void update_values_kangoo_battery() { //This function maps all the values fetch if (!CANstillAlive) { bms_status = FAULT; Serial.println("No CAN communication detected for 60s. Shutting down battery control."); + set_event(EVENT_CAN_FAILURE, 0); } else { CANstillAlive--; } @@ -133,14 +135,17 @@ void update_values_kangoo_battery() { //This function maps all the values fetch if (LB_Cell_Max_Voltage >= ABSOLUTE_CELL_MAX_VOLTAGE) { bms_status = FAULT; Serial.println("ERROR: CELL OVERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!"); + set_event(EVENT_CELL_OVER_VOLTAGE, 0); } if (LB_Cell_Min_Voltage <= ABSOLUTE_CELL_MIN_VOLTAGE) { bms_status = FAULT; Serial.println("ERROR: CELL UNDERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!"); + set_event(EVENT_CELL_UNDER_VOLTAGE, 0); } if (cell_deviation_mV > MAX_CELL_DEVIATION_MV) { LEDcolor = YELLOW; Serial.println("ERROR: HIGH CELL mV DEVIATION!!! Inspect battery!"); + set_event(EVENT_CELL_DEVIATION_HIGH, 0); } #ifdef DEBUG_VIA_USB diff --git a/Software/src/battery/RENAULT-ZOE-BATTERY.cpp b/Software/src/battery/RENAULT-ZOE-BATTERY.cpp index b65b71ec..d40f09cf 100644 --- a/Software/src/battery/RENAULT-ZOE-BATTERY.cpp +++ b/Software/src/battery/RENAULT-ZOE-BATTERY.cpp @@ -1,4 +1,5 @@ #include "RENAULT-ZOE-BATTERY.h" +#include "../devboard/utils/events.h" #include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h" #include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h" @@ -87,6 +88,7 @@ void update_values_zoe_battery() { //This function maps all the values fetched if (!CANstillAlive) { bms_status = FAULT; Serial.println("No CAN communication detected for 60s. Shutting down battery control."); + set_event(EVENT_CAN_FAILURE, 0); } else { CANstillAlive--; } @@ -94,14 +96,17 @@ void update_values_zoe_battery() { //This function maps all the values fetched if (LB_Cell_Max_Voltage >= ABSOLUTE_CELL_MAX_VOLTAGE) { bms_status = FAULT; Serial.println("ERROR: CELL OVERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!"); + set_event(EVENT_CELL_OVER_VOLTAGE, 0); } if (LB_Cell_Min_Voltage <= ABSOLUTE_CELL_MIN_VOLTAGE) { bms_status = FAULT; Serial.println("ERROR: CELL UNDERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!"); + set_event(EVENT_CELL_UNDER_VOLTAGE, 0); } if (cell_deviation_mV > MAX_CELL_DEVIATION_MV) { LEDcolor = YELLOW; Serial.println("ERROR: HIGH CELL mV DEVIATION!!! Inspect battery!"); + set_event(EVENT_CELL_DEVIATION_HIGH, 0); } #ifdef DEBUG_VIA_USB diff --git a/Software/src/battery/SANTA-FE-PHEV-BATTERY.cpp b/Software/src/battery/SANTA-FE-PHEV-BATTERY.cpp index 2959a684..e2aaa66f 100644 --- a/Software/src/battery/SANTA-FE-PHEV-BATTERY.cpp +++ b/Software/src/battery/SANTA-FE-PHEV-BATTERY.cpp @@ -1,4 +1,5 @@ #include "SANTA-FE-PHEV-BATTERY.h" +#include "../devboard/utils/events.h" #include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h" #include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h" @@ -84,6 +85,7 @@ void update_values_santafe_phev_battery() { //This function maps all the values if (!CANstillAlive) { bms_status = FAULT; Serial.println("No CAN communication detected for 60s. Shutting down battery control."); + set_event(EVENT_CAN_FAILURE, 0); } else { CANstillAlive--; } diff --git a/Software/src/battery/TESLA-MODEL-3-BATTERY.cpp b/Software/src/battery/TESLA-MODEL-3-BATTERY.cpp index 12697ad0..8fbdf301 100644 --- a/Software/src/battery/TESLA-MODEL-3-BATTERY.cpp +++ b/Software/src/battery/TESLA-MODEL-3-BATTERY.cpp @@ -1,4 +1,5 @@ #include "TESLA-MODEL-3-BATTERY.h" +#include "../devboard/utils/events.h" #include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h" #include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h" @@ -231,6 +232,7 @@ void update_values_tesla_model_3_battery() { //This function maps all the value if (!stillAliveCAN) { bms_status = FAULT; Serial.println("ERROR: No CAN communication detected for 60s. Shutting down battery control."); + set_event(EVENT_CAN_FAILURE, 0); } else { stillAliveCAN--; } @@ -238,6 +240,7 @@ void update_values_tesla_model_3_battery() { //This function maps all the value if (hvil_status == 3) { //INTERNAL_OPEN_FAULT - Someone disconnected a high voltage cable while battery was in use bms_status = FAULT; Serial.println("ERROR: High voltage cable removed while battery running. Opening contactors!"); + set_event(EVENT_INTERNAL_OPEN_FAULT, 0); } cell_deviation_mV = (cell_max_v - cell_min_v); @@ -258,40 +261,52 @@ void update_values_tesla_model_3_battery() { //This function maps all the value if (SOC < 6500) { //When SOC is less than 65.00% when approaching max voltage bms_status = FAULT; Serial.println("ERROR: SOC% reported by battery not plausible. Restart battery!"); + set_event(EVENT_SOC_PLAUSIBILITY_ERROR, SOC / 100); } } //Check if BMS is in need of recalibration - if (nominal_full_pack_energy < REASONABLE_ENERGYAMOUNT) { - Serial.println("Warning: kWh remaining reported by battery not plausible. Battery needs cycling."); + if (nominal_full_pack_energy > 1 && nominal_full_pack_energy < REASONABLE_ENERGYAMOUNT) { + Serial.println("Warning: kWh remaining " + String(nominal_full_pack_energy) + + " reported by battery not plausible. Battery needs cycling."); + set_event(EVENT_KWH_PLAUSIBILITY_ERROR, nominal_full_pack_energy); LEDcolor = YELLOW; + } else if (nominal_full_pack_energy <= 1) { + Serial.println("Info: kWh remaining battery is not reporting kWh remaining."); + set_event(EVENT_KWH_PLAUSIBILITY_ERROR, nominal_full_pack_energy); } if (LFP_Chemistry) { //LFP limits used for voltage safeties if (cell_max_v >= MAX_CELL_VOLTAGE_LFP) { bms_status = FAULT; Serial.println("ERROR: CELL OVERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!"); + set_event(EVENT_CELL_OVER_VOLTAGE, 0); } if (cell_min_v <= MIN_CELL_VOLTAGE_LFP) { bms_status = FAULT; Serial.println("ERROR: CELL UNDERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!"); + set_event(EVENT_CELL_UNDER_VOLTAGE, 0); } if (cell_deviation_mV > MAX_CELL_DEVIATION_LFP) { LEDcolor = YELLOW; Serial.println("ERROR: HIGH CELL DEVIATION!!! Inspect battery!"); + set_event(EVENT_CELL_DEVIATION_HIGH, 0); } } else { //NCA/NCM limits used if (cell_max_v >= MAX_CELL_VOLTAGE_NCA_NCM) { bms_status = FAULT; Serial.println("ERROR: CELL OVERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!"); + set_event(EVENT_CELL_OVER_VOLTAGE, 0); } if (cell_min_v <= MIN_CELL_VOLTAGE_NCA_NCM) { bms_status = FAULT; Serial.println("ERROR: CELL UNDERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!"); + set_event(EVENT_CELL_UNDER_VOLTAGE, 0); } if (cell_deviation_mV > MAX_CELL_DEVIATION_NCA_NCM) { LEDcolor = YELLOW; Serial.println("ERROR: HIGH CELL DEVIATION!!! Inspect battery!"); + set_event(EVENT_CELL_DEVIATION_HIGH, 0); } } diff --git a/Software/src/devboard/mqtt/mqtt.cpp b/Software/src/devboard/mqtt/mqtt.cpp index 7b7de371..2e7cb9c3 100644 --- a/Software/src/devboard/mqtt/mqtt.cpp +++ b/Software/src/devboard/mqtt/mqtt.cpp @@ -26,22 +26,6 @@ static void publish_values(void) { publish_cell_voltages(); } -static void publish_common_info(void) { - snprintf(mqtt_msg, sizeof(mqtt_msg), - "{\n" - " \"SOC\": %.3f,\n" - " \"StateOfHealth\": %.3f,\n" - " \"temperature_min\": %.3f,\n" - " \"temperature_max\": %.3f,\n" - " \"cell_max_voltage\": %d,\n" - " \"cell_min_voltage\": %d\n" - "}\n", - ((float)SOC) / 100.0, ((float)StateOfHealth) / 100.0, ((float)((int16_t)temperature_min)) / 10.0, - ((float)((int16_t)temperature_max)) / 10.0, cell_max_voltage, cell_min_voltage); - bool result = client.publish("battery/info", mqtt_msg, true); - //Serial.println(mqtt_msg); // Uncomment to print the payload on serial -} - static void publish_cell_voltages(void) { static bool mqtt_first_transmission = true; @@ -76,17 +60,17 @@ static void publish_cell_voltages(void) { "\"object_id\": \"sensor_battery_voltage_cell%d\"," "\"origin\": {" "\"name\": \"BatteryEmulator\"," - "\"sw\": \"4.4.0-mqtt\"," + "\"sw\": \"%s-mqtt\"," "\"url\": \"https://github.com/dalathegreat/Battery-Emulator\"" "}," "\"state_class\": \"measurement\"," "\"name\": \"Battery Cell Voltage %d\"," - "\"state_topic\": \"battery/spec_data\"," + "\"state_topic\": \"battery-emulator/spec_data\"," "\"unique_id\": \"battery-emulator_battery_voltage_cell%d\"," "\"unit_of_measurement\": \"V\"," "\"value_template\": \"{{ value_json.cell_voltages[%d] }}\"" "}", - i + 1, i + 1, i + 1, i); + i + 1, version_number, i + 1, i + 1, i); // End each discovery topic with cell number and '/config' String cell_topic = topic + String(i + 1) + "/config"; mqtt_publish_retain(cell_topic.c_str()); @@ -99,7 +83,7 @@ static void publish_cell_voltages(void) { // is the string content // If cell voltages haven't been populated... - if (cellvoltages[0] == 0u) { + if (cellvoltages[0] == 0u / 1000) { //cell voltage is in mV and homeassistant expects V return; } @@ -111,12 +95,101 @@ static void publish_cell_voltages(void) { snprintf(mqtt_msg + msg_length, sizeof(mqtt_msg) - msg_length, "]\n}\n"); // Publish and print error if not OK - if (mqtt_publish_retain("battery/spec_data") == false) { + if (mqtt_publish_retain("battery-emulator/spec_data") == false) { Serial.println("Cell voltage MQTT msg could not be sent"); } } } +struct SensorConfig { + const char* object_id; + const char* topic; + const char* name; + const char* value_template; + const char* unit; + const char* device_class; +}; + +SensorConfig sensorConfigs[] = { + {"SOC", "homeassistant/sensor/battery-emulator/SOC/config", "Battery Emulator SOC", "{{ value_json.SOC }}", "%", + "battery"}, + {"state_of_health", "homeassistant/sensor/battery-emulator/state_of_health/config", + "Battery Emulator State Of Health", "{{ value_json.state_of_health }}", "%", "battery"}, + {"temperature_min", "homeassistant/sensor/battery-emulator/temperature_min/config", + "Battery Emulator Temperature Min", "{{ value_json.temperature_min }}", "°C", "temperature"}, + {"temperature_max", "homeassistant/sensor/battery-emulator/temperature_max/config", + "Battery Emulator Temperature Max", "{{ value_json.temperature_max }}", "°C", "temperature"}, + {"stat_batt_power", "homeassistant/sensor/battery-emulator/stat_batt_power/config", + "Battery Emulator Stat Batt Power", "{{ value_json.stat_batt_power }}", "W", "power"}, + {"battery_current", "homeassistant/sensor/battery-emulator/battery_current/config", + "Battery Emulator Battery Current", "{{ value_json.battery_current }}", "A", "current"}, + {"cell_max_voltage", "homeassistant/sensor/battery-emulator/cell_max_voltage/config", + "Battery Emulator Cell Max Voltage", "{{ value_json.cell_max_voltage }}", "V", "voltage"}, + {"cell_min_voltage", "homeassistant/sensor/battery-emulator/cell_min_voltage/config", + "Battery Emulator Cell Min Voltage", "{{ value_json.cell_min_voltage }}", "V", "voltage"}, + {"battery_voltage", "homeassistant/sensor/battery-emulator/battery_voltage/config", + "Battery Emulator Battery Voltage", "{{ value_json.battery_voltage }}", "V", "voltage"}, +}; + +static void publish_common_info(void) { + static bool mqtt_first_transmission = true; + static char* state_topic = "battery-emulator/info"; + if (mqtt_first_transmission == true) { + mqtt_first_transmission = false; + for (int i = 0; i < sizeof(sensorConfigs) / sizeof(sensorConfigs[0]); i++) { + SensorConfig& config = sensorConfigs[i]; + snprintf(mqtt_msg, sizeof(mqtt_msg), + "{" + "\"name\": \"%s\"," + "\"state_topic\": \"%s\"," + "\"unique_id\": \"battery-emulator_%s\"," + "\"object_id\": \"sensor_battery_%s\"," + "\"device\": {" + "\"identifiers\": [" + "\"battery-emulator\"" + "]," + "\"manufacturer\": \"DalaTech\"," + "\"model\": \"BatteryEmulator\"," + "\"name\": \"BatteryEmulator\"" + "}," + "\"origin\": {" + "\"name\": \"BatteryEmulator\"," + "\"sw\": \"%s-mqtt\"," + "\"url\": \"https://github.com/dalathegreat/Battery-Emulator\"" + "}," + "\"value_template\": \"%s\"," + "\"unit_of_measurement\": \"%s\"," + "\"device_class\": \"%s\"," + "\"enabled_by_default\": true," + "\"state_class\": \"measurement\"" + "}", + config.name, state_topic, config.object_id, config.object_id, version_number, config.value_template, + config.unit, config.device_class); + mqtt_publish_retain(config.topic); + } + } else { + snprintf(mqtt_msg, sizeof(mqtt_msg), + "{\n" + " \"SOC\": %.3f,\n" + " \"state_of_health\": %.3f,\n" + " \"temperature_min\": %.3f,\n" + " \"temperature_max\": %.3f,\n" + " \"stat_batt_power\": %.3f,\n" + " \"battery_current\": %.3f,\n" + " \"cell_max_voltage\": %d,\n" + " \"cell_min_voltage\": %d,\n" + " \"battery_voltage\": %d\n" + "}\n", + ((float)SOC) / 100.0, ((float)StateOfHealth) / 100.0, ((float)((int16_t)temperature_min)) / 10.0, + ((float)((int16_t)temperature_max)) / 10.0, ((float)((int16_t)stat_batt_power)), + ((float)((int16_t)battery_current)) / 10.0, cell_max_voltage / 1000, cell_min_voltage / 1000, + battery_voltage / 10.0); + bool result = client.publish(state_topic, mqtt_msg, true); + } + + //Serial.println(mqtt_msg); // Uncomment to print the payload on serial +} + /* This is called whenever a subscribed topic changes (hopefully) */ static void callback(char* topic, byte* payload, unsigned int length) { Serial.print("Message arrived ["); @@ -132,9 +205,8 @@ static void callback(char* topic, byte* payload, unsigned int length) { static void reconnect() { // attempt one reconnection Serial.print("Attempting MQTT connection... "); - // Create a random client ID - String clientId = "LilyGoClient-"; - clientId += String(random(0xffff), HEX); + const char* hostname = WiFi.getHostname(); + String clientId = "LilyGoClient-" + String(hostname); // Attempt to connect if (client.connect(clientId.c_str(), mqtt_user, mqtt_password)) { Serial.println("connected"); diff --git a/Software/src/devboard/mqtt/mqtt.h b/Software/src/devboard/mqtt/mqtt.h index b23b5ca6..b7b58c8f 100644 --- a/Software/src/devboard/mqtt/mqtt.h +++ b/Software/src/devboard/mqtt/mqtt.h @@ -39,6 +39,8 @@ #define MQTT_MSG_BUFFER_SIZE (1024) +extern const char* version_number; // The current software version, used for mqtt + extern uint16_t SOC; extern uint16_t StateOfHealth; extern uint16_t temperature_min; //C+1, Goes thru convert2unsignedint16 function (15.0C = 150, -15.0C = 65385) @@ -47,6 +49,8 @@ extern uint16_t cell_max_voltage; //mV, 0-4350 extern uint16_t cell_min_voltage; //mV, 0-4350 extern uint16_t cellvoltages[120]; //mV 0-4350 per cell extern uint8_t nof_cellvoltages; // Total number of cell voltages, set by each battery. +extern uint16_t battery_voltage; //V+1, 0-500.0 (0-5000) +extern uint16_t battery_current; //A+1, Goes thru convert2unsignedint16 function (5.0A = 50, -5.0A = 65485) extern const char* mqtt_user; extern const char* mqtt_password; diff --git a/Software/src/devboard/utils/events.cpp b/Software/src/devboard/utils/events.cpp new file mode 100644 index 00000000..ee2d79f5 --- /dev/null +++ b/Software/src/devboard/utils/events.cpp @@ -0,0 +1,130 @@ +#include "events.h" + +#include "../../../USER_SETTINGS.h" +#include "../config.h" + +unsigned long previous_millis = 0; +uint32_t time_seconds = 0; +static uint8_t total_led_color = GREEN; +static char event_message[256]; +EVENTS_STRUCT_TYPE entries[EVENT_NOF_EVENTS]; + +/* Local function prototypes */ +static void set_event_message(EVENTS_ENUM_TYPE event); +static void update_led_color(EVENTS_ENUM_TYPE event); + +/* Exported functions */ +void init_events(void) { + for (uint8_t i = 0; i < EVENT_NOF_EVENTS; i++) { + entries[i].timestamp = 0; + entries[i].data = 0; + entries[i].occurences = 0; + entries[i].led_color = RED; // Most events are RED + } + + // YELLOW events below + entries[EVENT_12V_LOW].led_color = YELLOW; + entries[EVENT_CAN_WARNING].led_color = YELLOW; + entries[EVENT_CELL_DEVIATION_HIGH].led_color = YELLOW; + entries[EVENT_KWH_PLAUSIBILITY_ERROR].led_color = YELLOW; +} + +void set_event(EVENTS_ENUM_TYPE event, uint8_t data) { +#ifdef EVENTLOGGING + if (event >= EVENT_NOF_EVENTS) { + event = EVENT_UNKNOWN_EVENT_SET; + } + entries[event].timestamp = time_seconds; + entries[event].data = data; + ++entries[event].occurences; + set_event_message(event); +#ifdef DEBUG_VIA_USB + Serial.println("Set event: " + String(get_event_enum_string(event)) + ". Has occured " + + String(entries[event].occurences) + " times"); +#endif +#endif +} + +void update_event_timestamps(void) { + unsigned long new_millis = millis(); + if (new_millis - previous_millis >= 1000) { + time_seconds++; + previous_millis = new_millis; + } +} + +/* Local functions */ +static void update_led_color(EVENTS_ENUM_TYPE event) { + total_led_color = (total_led_color == RED) ? RED : entries[event].led_color; +} + +const char* get_led_color_display_text(u_int8_t led_color) { + switch (led_color) { + case RED: + return "RED"; + case YELLOW: + return "YELLOW"; + case GREEN: + return "GREEN"; + case BLUE: + return "BLUE"; + default: + return "UNKNOWN"; + } +} + +const char* get_event_message(EVENTS_ENUM_TYPE event) { + switch (event) { + case EVENT_CAN_FAILURE: + return "No CAN communication detected for 60s. Shutting down battery control."; + case EVENT_CAN_WARNING: + return "ERROR: High amount of corrupted CAN messages detected. Check CAN wire shielding!"; + case EVENT_WATER_INGRESS: + return "Water leakage inside battery detected. Operation halted. Inspect battery!"; + case EVENT_12V_LOW: + return "12V battery source below required voltage to safely close contactors. Inspect the supply/battery!"; + case EVENT_SOC_PLAUSIBILITY_ERROR: + return "ERROR: SOC% reported by battery not plausible. Restart battery!"; + case EVENT_KWH_PLAUSIBILITY_ERROR: + return "Warning: kWh remaining reported by battery not plausible. Battery needs cycling."; + case EVENT_BATTERY_CHG_STOP_REQ: + return "ERROR: Battery raised caution indicator AND requested charge stop. Inspect battery status!"; + case EVENT_BATTERY_DISCHG_STOP_REQ: + return "ERROR: Battery raised caution indicator AND requested discharge stop. Inspect battery status!"; + case EVENT_BATTERY_CHG_DISCHG_STOP_REQ: + return "ERROR: Battery raised caution indicator AND requested charge/discharge stop. Inspect battery status!"; + case EVENT_LOW_SOH: + return "ERROR: State of health critically low. Battery internal resistance too high to continue. Recycle " + "battery."; + case EVENT_HVIL_FAILURE: + return "ERROR: Battery interlock loop broken. Check that high voltage connectors are seated. Battery will be " + "disabled!"; + case EVENT_INTERNAL_OPEN_FAULT: + return "ERROR: High voltage cable removed while battery running. Opening contactors!"; + case EVENT_CELL_UNDER_VOLTAGE: + return "ERROR: CELL UNDERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!"; + case EVENT_CELL_OVER_VOLTAGE: + return "ERROR: CELL OVERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!"; + case EVENT_CELL_DEVIATION_HIGH: + return "ERROR: HIGH CELL DEVIATION!!! Inspect battery!"; + case EVENT_UNKNOWN_EVENT_SET: + return "An unknown event was set! Review your code!"; + case EVENT_DUMMY: + return "The dummy event was set!"; // Don't change this event message! + default: + return ""; + } +} + +const char* get_event_enum_string(EVENTS_ENUM_TYPE event) { + const char* fullString = EVENTS_ENUM_TYPE_STRING[event]; + if (strncmp(fullString, "EVENT_", 6) == 0) { + return fullString + 6; // Skip the first 6 characters + } + return fullString; +} + +static void set_event_message(EVENTS_ENUM_TYPE event) { + const char* message = get_event_message(event); + snprintf(event_message, sizeof(event_message), "%s", message); +} diff --git a/Software/src/devboard/utils/events.h b/Software/src/devboard/utils/events.h new file mode 100644 index 00000000..e67824df --- /dev/null +++ b/Software/src/devboard/utils/events.h @@ -0,0 +1,55 @@ +#ifndef __EVENTS_H__ +#define __EVENTS_H__ +#include + +#ifndef UNIT_TEST +#include +extern unsigned long previous_millis; +extern uint32_t time_seconds; +#endif + +#define EVENTS_ENUM_TYPE(XX) \ + XX(EVENT_CAN_FAILURE) \ + XX(EVENT_CAN_WARNING) \ + XX(EVENT_WATER_INGRESS) \ + XX(EVENT_12V_LOW) \ + XX(EVENT_SOC_PLAUSIBILITY_ERROR) \ + XX(EVENT_KWH_PLAUSIBILITY_ERROR) \ + XX(EVENT_BATTERY_CHG_STOP_REQ) \ + XX(EVENT_BATTERY_DISCHG_STOP_REQ) \ + XX(EVENT_BATTERY_CHG_DISCHG_STOP_REQ) \ + XX(EVENT_LOW_SOH) \ + XX(EVENT_HVIL_FAILURE) \ + XX(EVENT_INTERNAL_OPEN_FAULT) \ + XX(EVENT_CELL_UNDER_VOLTAGE) \ + XX(EVENT_CELL_OVER_VOLTAGE) \ + XX(EVENT_CELL_DEVIATION_HIGH) \ + XX(EVENT_UNKNOWN_EVENT_SET) \ + XX(EVENT_DUMMY) \ + XX(EVENT_NOF_EVENTS) + +#define GENERATE_ENUM(ENUM) ENUM, +#define GENERATE_STRING(STRING) #STRING, + +typedef enum { EVENTS_ENUM_TYPE(GENERATE_ENUM) } EVENTS_ENUM_TYPE; + +static const char* EVENTS_ENUM_TYPE_STRING[] = {EVENTS_ENUM_TYPE(GENERATE_STRING)}; + +const char* get_event_enum_string(EVENTS_ENUM_TYPE event); + +const char* get_event_message(EVENTS_ENUM_TYPE event); + +const char* get_led_color_display_text(u_int8_t led_color); + +void init_events(void); +void set_event(EVENTS_ENUM_TYPE event, uint8_t data); +void update_event_timestamps(void); +typedef struct { + uint32_t timestamp; // Time in seconds since startup when the event occurred + uint8_t data; // Custom data passed when setting the event, for example cell number for under voltage + uint8_t occurences; // Number of occurrences since startup + uint8_t led_color; // Weirdly indented comment +} EVENTS_STRUCT_TYPE; +extern EVENTS_STRUCT_TYPE entries[EVENT_NOF_EVENTS]; + +#endif // __MYTIMER_H__ diff --git a/Software/src/devboard/utils/timer.cpp b/Software/src/devboard/utils/timer.cpp index 5b4d59bf..ff051a03 100644 --- a/Software/src/devboard/utils/timer.cpp +++ b/Software/src/devboard/utils/timer.cpp @@ -1,11 +1,13 @@ #include "timer.h" -MyTimer::MyTimer(unsigned long interval) : interval(interval), previousMillis(0) {} +MyTimer::MyTimer(unsigned long interval) : interval(interval) { + previous_millis = millis(); +} bool MyTimer::elapsed() { - unsigned long currentMillis = millis(); - if (currentMillis - previousMillis >= interval) { - previousMillis = currentMillis; + unsigned long current_millis = millis(); + if (current_millis - previous_millis >= interval) { + previous_millis = current_millis; return true; } return false; diff --git a/Software/src/devboard/utils/timer.h b/Software/src/devboard/utils/timer.h index 829ea1e5..e330eb2f 100644 --- a/Software/src/devboard/utils/timer.h +++ b/Software/src/devboard/utils/timer.h @@ -1,7 +1,9 @@ #ifndef __MYTIMER_H__ #define __MYTIMER_H__ +#ifndef UNIT_TEST #include +#endif class MyTimer { public: @@ -12,7 +14,7 @@ class MyTimer { private: unsigned long interval; - unsigned long previousMillis; + unsigned long previous_millis; }; #endif // __MYTIMER_H__ diff --git a/Software/src/devboard/webserver/webserver.cpp b/Software/src/devboard/webserver/webserver.cpp index bfb1318c..8d7aa1ab 100644 --- a/Software/src/devboard/webserver/webserver.cpp +++ b/Software/src/devboard/webserver/webserver.cpp @@ -36,32 +36,31 @@ const char index_html[] PROGMEM = R"rawliteral( )rawliteral"; -// Wifi connect time declarations and definition -const unsigned long MAX_WIFI_RECONNECT_BACKOFF_TIME = 60000; // Maximum backoff time of 1 minute -const unsigned long DEFAULT_WIFI_RECONNECT_BACKOFF_TIME = - 1000; // Default wifi reconnect backoff time. Start with 1 second -const unsigned long WIFI_CONNECT_TIMEOUT = 10000; // Timeout for WiFi connect in milliseconds -const unsigned long WIFI_MONITOR_LOOP_TIME = - 1000; // Will check if WiFi is connected and try reconnect every x milliseconds -unsigned long last_wifi_monitor_run = 0; -unsigned long wifi_connect_start_time; -unsigned long wifi_reconnect_backoff_time = DEFAULT_WIFI_RECONNECT_BACKOFF_TIME; - -enum WiFiState { DISCONNECTED, CONNECTING, CONNECTED }; - -WiFiState wifi_state = - DISCONNECTED; //the esp library has no specific state to indicate if its connecting (only WL_IDLE_STATUS) so we keep track of it here +enum WifiState { + INIT, //before connecting first time + RECONNECTING, //we've connected before, but lost connection + CONNECTED //we are connected +}; + +WifiState wifi_state = INIT; + +unsigned const long WIFI_MONITOR_INTERVAL_TIME = 15000; +unsigned const long INIT_WIFI_CONNECT_TIMEOUT = 8000; // Timeout for initial WiFi connect in milliseconds +unsigned const long DEFAULT_WIFI_RECONNECT_INTERVAL = 1000; // Default WiFi reconnect interval in ms +unsigned const long MAX_WIFI_RETRY_INTERVAL = 30000; // Maximum wifi retry interval in ms +unsigned long last_wifi_monitor_time = millis(); //init millis so wifi monitor doesn't run immediately +unsigned long wifi_reconnect_interval = DEFAULT_WIFI_RECONNECT_INTERVAL; +unsigned long last_wifi_attempt_time = millis(); //init millis so wifi monitor doesn't run immediately void init_webserver() { // Configure WiFi if (AccessPointEnabled) { WiFi.mode(WIFI_AP_STA); // Simultaneous WiFi AP and Router connection init_WiFi_AP(); - init_WiFi_STA(ssid, password); } else { WiFi.mode(WIFI_STA); // Only Router connection - init_WiFi_STA(ssid, password); } + init_WiFi_STA(ssid, password, wifi_channel); // Route for root / web page server.on("/", HTTP_GET, @@ -76,6 +75,11 @@ void init_webserver() { request->send_P(200, "text/html", index_html, cellmonitor_processor); }); +#ifdef EVENTLOGGING + server.on("/events", HTTP_GET, + [](AsyncWebServerRequest* request) { request->send_P(200, "text/html", index_html, events_processor); }); +#endif + // Route for editing Wh server.on("/updateBatterySize", HTTP_GET, [](AsyncWebServerRequest* request) { if (request->hasParam("value")) { @@ -255,120 +259,77 @@ void init_webserver() { #endif } -void print_wifi_status_message(wl_status_t status) { +void init_WiFi_AP() { + Serial.println("Creating Access Point: " + String(ssidAP)); + Serial.println("With password: " + String(passwordAP)); + WiFi.softAP(ssidAP, passwordAP); + IPAddress IP = WiFi.softAPIP(); + Serial.println("Access Point created."); + Serial.print("IP address: "); + Serial.println(IP); +} + +String getConnectResultString(wl_status_t status) { switch (status) { case WL_CONNECTED: - Serial.println("Connected to WiFi network: " + String(ssid)); - Serial.println("IP address: " + WiFi.localIP().toString()); - Serial.println("Signal Strength: " + String(WiFi.RSSI()) + " dBm"); - break; + return "Connected"; + case WL_NO_SHIELD: + return "No shield"; + case WL_IDLE_STATUS: + return "Idle status"; + case WL_NO_SSID_AVAIL: + return "No SSID available"; + case WL_SCAN_COMPLETED: + return "Scan completed"; case WL_CONNECT_FAILED: - Serial.println("Failed to connect to WiFi network: " + String(ssid)); - break; + return "Connect failed"; case WL_CONNECTION_LOST: - Serial.println("Connection to WiFi network: " + String(ssid) + " lost"); - break; + return "Connection lost"; case WL_DISCONNECTED: - Serial.println("Disconnected from WiFi network: " + String(ssid)); - break; - case WL_NO_SSID_AVAIL: - Serial.println("Could not find network with SSID: " + String(ssid)); - break; - case WL_IDLE_STATUS: - Serial.println("WiFi is in idle status. This can indicate it is currently trying to connect."); - break; - case WL_SCAN_COMPLETED: - Serial.println("WiFi scan completed"); - break; - case WL_NO_SHIELD: - Serial.println("No WiFi shield detected"); - break; + return "Disconnected"; default: - Serial.println("Unknown WiFi status: " + String(status)); - break; - } -} - -// Function to handle WiFi reconnection. Use some timeouts and backoffs here to avoid flooding reconnection attempts/spamming the serial console -void handle_WiFi_reconnection(unsigned long currentMillis, wl_status_t status) { - if (wifi_state == CONNECTING && currentMillis - wifi_connect_start_time > WIFI_CONNECT_TIMEOUT) { - // we are here if we were trying to connect to wifi, but it took too long (more than configured timeout) - Serial.println("Failed to connect to WiFi network before timeout"); - print_wifi_status_message(status); - WiFi.disconnect(); //disconnect to clear any previous settings - wifi_state = DISCONNECTED; - wifi_connect_start_time = currentMillis; //reset the start time to now so backoff is respected on next try - // We use a backoff time before trying to connect again. Increase backoff time, up to a maximum - wifi_reconnect_backoff_time = min(wifi_reconnect_backoff_time * 2, MAX_WIFI_RECONNECT_BACKOFF_TIME); - Serial.println("Will try again in " + String(wifi_reconnect_backoff_time / 1000) + " seconds."); - } else if (wifi_state != CONNECTING && currentMillis - wifi_connect_start_time > wifi_reconnect_backoff_time) { - // we are here if the connection failed for some reason and the backoff time has now passed - print_wifi_status_message(status); - init_WiFi_STA(ssid, password); + return "Unknown"; } } -// Function to handle WiFi connection -void WiFi_monitor_loop() { +void wifi_monitor() { unsigned long currentMillis = millis(); - if (currentMillis - last_wifi_monitor_run > WIFI_MONITOR_LOOP_TIME) { - last_wifi_monitor_run = currentMillis; + if (currentMillis - last_wifi_monitor_time > WIFI_MONITOR_INTERVAL_TIME) { + last_wifi_monitor_time = currentMillis; wl_status_t status = WiFi.status(); - switch (status) { - case WL_CONNECTED: - if (wifi_state != CONNECTED) { //we need to update our own wifi state to indicate we are connected - wifi_reconnect_backoff_time = - DEFAULT_WIFI_RECONNECT_BACKOFF_TIME; // Reset backoff time after maintaining connection - wifi_state = CONNECTED; - print_wifi_status_message(status); + if (status != WL_CONNECTED && status != WL_IDLE_STATUS) { + Serial.println(getConnectResultString(status)); + if (wifi_state == INIT) { //we haven't been connected yet, try the init logic + init_WiFi_STA(ssid, password, wifi_channel); + } else { //we were connected before, try the reconnect logic + if (currentMillis - last_wifi_attempt_time > wifi_reconnect_interval) { + last_wifi_attempt_time = currentMillis; + Serial.println("WiFi not connected, trying to reconnect..."); + wifi_state = RECONNECTING; + WiFi.reconnect(); + wifi_reconnect_interval = min(wifi_reconnect_interval * 2, MAX_WIFI_RETRY_INTERVAL); } - break; - case WL_CONNECT_FAILED: - case WL_CONNECTION_LOST: - case WL_DISCONNECTED: - case WL_NO_SSID_AVAIL: - handle_WiFi_reconnection(currentMillis, status); - break; - case WL_IDLE_STATUS: //this means the wifi is not ready to process any commands (it's probably trying to connect). do nothing - - case WL_SCAN_COMPLETED: //this will only be set when scanning for networks. We don't do that yet - case WL_NO_SHIELD: //should not happen, this means no wifi chip detected, so we can't do much - break; + } + } else if (status == WL_CONNECTED && wifi_state != CONNECTED) { + wifi_state = CONNECTED; + wifi_reconnect_interval = DEFAULT_WIFI_RECONNECT_INTERVAL; + // Print local IP address and start web server + Serial.print("Connected to WiFi network: " + String(ssid)); + Serial.print(" IP address: " + WiFi.localIP().toString()); + Serial.print(" Signal Strength: " + String(WiFi.RSSI()) + " dBm"); + Serial.println(" Channel: " + String(WiFi.channel())); + Serial.println(" Hostname: " + String(WiFi.getHostname())); } } } -// Function to initialize WiFi in Station Mode (i.e. connect to another access point) -void init_WiFi_STA(const char* ssid, const char* password) { - Serial.println("Connecting to: " + String(ssid)); - wifi_state = CONNECTING; - WiFi.begin(ssid, password); - WiFi.setAutoReconnect(true); - wifi_connect_start_time = millis(); -} - -// Function to convert WiFiState enum to String -String wifi_state_to_string(WiFiState state) { - switch (state) { - case DISCONNECTED: - return "Disconnected"; - case CONNECTING: - return "Connecting"; - case CONNECTED: - return "Connected"; - default: - return "Unknown"; - } -} - -// Function to initialize WiFi in Access Point Mode -void init_WiFi_AP() { - Serial.println("Creating Access Point: " + String(ssidAP)); - Serial.println("With password: " + String(passwordAP)); - WiFi.softAP(ssidAP, passwordAP); - IPAddress IP = WiFi.softAPIP(); - Serial.println("Access Point created."); - Serial.println("IP address: " + IP.toString()); +void init_WiFi_STA(const char* ssid, const char* password, const uint8_t wifi_channel) { + // Connect to Wi-Fi network with SSID and password + Serial.print("Connecting to "); + Serial.println(ssid); + WiFi.begin(ssid, password, wifi_channel); + WiFi.setAutoReconnect(true); // Enable auto reconnect + wl_status_t result = static_cast(WiFi.waitForConnectResult(INIT_WIFI_CONNECT_TIMEOUT)); } // Function to initialize ElegantOTA @@ -392,7 +353,7 @@ String processor(const String& var) { content += "
"; // Show version number - content += "

Software version: " + String(versionNumber) + "

"; + content += "

Software version: " + String(version_number) + "

"; // Display LED color content += "

LED color: "; @@ -415,13 +376,15 @@ String processor(const String& var) { default: break; } + wl_status_t status = WiFi.status(); // Display ssid of network connected to and, if connected to the WiFi, its own IP content += "

SSID: " + String(ssid) + "

"; - content += "

Wifi status: " + wifi_state_to_string(wifi_state) + "

"; - if (WiFi.status() == WL_CONNECTED) { + content += "

Wifi status: " + getConnectResultString(status) + "

"; + if (status == WL_CONNECTED) { content += "

IP: " + WiFi.localIP().toString() + "

"; // Get and display the signal strength (RSSI) content += "

Signal Strength: " + String(WiFi.RSSI()) + " dBm

"; + content += "

Channel: " + String(WiFi.channel()) + "

"; } // Close the block content += "
"; @@ -659,11 +622,16 @@ String processor(const String& var) { content += " "; content += ""; content += " "; + content += ""; + content += " "; content += ""; content += " +)====="; + +String events_processor(const String& var) { + if (var == "PLACEHOLDER") { + String content = ""; + content.reserve(5000); + // Page format + content.concat(FPSTR(EVENTS_HTML_START)); + for (int i = 0; i < EVENT_NOF_EVENTS; i++) { + Serial.println("Event: " + String(get_event_enum_string(static_cast(i))) + + " count: " + String(entries[i].occurences) + " seconds: " + String(entries[i].timestamp) + + " data: " + String(entries[i].data)); + if (entries[i].occurences > 0) { + content.concat("
"); + content.concat("
" + String(get_event_enum_string(static_cast(i))) + "
"); + content.concat("
" + String(get_led_color_display_text(entries[i].led_color)) + "
"); + content.concat("
" + String((millis() / 1000) - entries[i].timestamp) + "
"); + content.concat("
" + String(entries[i].occurences) + "
"); + content.concat("
" + String(entries[i].data) + "
"); + content.concat("
" + String(get_event_message(static_cast(i))) + "
"); + content.concat("
"); // End of event row + } + } + content.concat(FPSTR(EVENTS_HTML_END)); + return content; + } + return String(); +} +#endif void onOTAStart() { // Log when OTA has started Serial.println("OTA update started!"); diff --git a/Software/src/devboard/webserver/webserver.h b/Software/src/devboard/webserver/webserver.h index 79ac2646..c874b728 100644 --- a/Software/src/devboard/webserver/webserver.h +++ b/Software/src/devboard/webserver/webserver.h @@ -12,10 +12,12 @@ #include "../../lib/me-no-dev-ESPAsyncWebServer/src/ESPAsyncWebServer.h" #include "../../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h" #include "../config.h" // Needed for LED defines +#include "../utils/events.h" #ifdef MQTT #include "../mqtt/mqtt.h" #endif +extern const char* version_number; // The current software version, shown on webserver extern uint16_t SOC; //SOC%, 0-100.00 (0-10000) extern uint16_t StateOfHealth; //SOH%, 0-100.00 (0-10000) extern uint16_t battery_voltage; //V+1, 0-500.0 (0-5000) @@ -35,12 +37,13 @@ extern uint16_t cellvoltages[120]; //mV 0-4350 per cell extern uint8_t LEDcolor; //Enum, 0-10 extern bool batteryAllowsContactorClosing; //Bool, 1=true, 0=false extern bool inverterAllowsContactorClosing; //Bool, 1=true, 0=false +extern EVENTS_STRUCT_TYPE entries[EVENT_NOF_EVENTS]; extern const char* ssid; extern const char* password; +extern const uint8_t wifi_channel; extern const char* ssidAP; extern const char* passwordAP; -extern const char* versionNumber; // Common charger parameters extern float charger_stat_HVcur; @@ -63,32 +66,32 @@ extern uint16_t OBC_Charge_Power; void init_webserver(); /** - * @brief Initialization function that creates a WiFi Access Point. + * @brief Monitoring loop for WiFi. Will attempt to reconnect to access point if the connection goes down. * * @param[in] void * * @return void */ -void init_WiFi_AP(); +void wifi_monitor(); /** - * @brief Initialization function that connects to an existing network. + * @brief Initialization function that creates a WiFi Access Point. * - * @param[in] ssid WiFi network name - * @param[in] password WiFi network password + * @param[in] void * * @return void */ -void init_WiFi_STA(const char* ssid, const char* password); +void init_WiFi_AP(); /** - * @brief Monitoring loop for WiFi. Will attempt to reconnect to access point if the connection goes down. + * @brief Initialization function that connects to an existing network. * - * @param[in] void + * @param[in] ssid WiFi network name + * @param[in] password WiFi network password * * @return void */ -void WiFi_monitor_loop(); +void init_WiFi_STA(const char* ssid, const char* password, const uint8_t channel); // /** // * @brief Function to handle WiFi reconnection. @@ -135,6 +138,15 @@ String settings_processor(const String& var); */ String cellmonitor_processor(const String& var); +/** + * @brief Replaces placeholder with content section in web page + * + * @param[in] var + * + * @return String + */ +String events_processor(const String& var); + /** * @brief Executes on OTA start *