From 6109211c965cd2d77509dd867f1cf63e5172c0f5 Mon Sep 17 00:00:00 2001 From: Matt Holmes Date: Sat, 8 Mar 2025 08:41:54 +0000 Subject: [PATCH 1/7] Fixes to mqtt to prevent delays when the broker is unavailable which can trigger the watchdog timer reset --- Software/USER_SETTINGS.h | 4 ++- Software/src/devboard/mqtt/mqtt.cpp | 55 ++++++++++++++++++++++------- 2 files changed, 46 insertions(+), 13 deletions(-) diff --git a/Software/USER_SETTINGS.h b/Software/USER_SETTINGS.h index 178311e6..a7167890 100644 --- a/Software/USER_SETTINGS.h +++ b/Software/USER_SETTINGS.h @@ -121,7 +121,9 @@ /* MQTT options */ // #define MQTT // Enable this line to enable MQTT -#define MQTT_QOS 0 // MQTT Quality of Service (0, 1, or 2) +#define MQTT_QOS 0 // MQTT Quality of Service (0, 1, or 2) +#define MQTT_PUBLISH_CELL_VOLTAGES // Enable this line to publish cell voltages to MQTT +#define MQTT_TIMEOUT 2000 // MQTT timeout in milliseconds #define MQTT_MANUAL_TOPIC_OBJECT_NAME // Enable MQTT_MANUAL_TOPIC_OBJECT_NAME to use custom MQTT topic, object ID prefix, and device name. // WARNING: If this is not defined, the previous default naming format 'battery-emulator_esp32-XXXXXX' (based on hardware ID) will be used. diff --git a/Software/src/devboard/mqtt/mqtt.cpp b/Software/src/devboard/mqtt/mqtt.cpp index 29b8c7ea..65a95c35 100644 --- a/Software/src/devboard/mqtt/mqtt.cpp +++ b/Software/src/devboard/mqtt/mqtt.cpp @@ -25,16 +25,30 @@ static String object_id_prefix = ""; static String device_name = ""; static String device_id = ""; -static void publish_common_info(void); -static void publish_cell_voltages(void); -static void publish_events(void); +static bool publish_common_info(void); +static bool publish_cell_voltages(void); +static bool publish_events(void); /** Publish global values and call callbacks for specific modules */ static void publish_values(void) { - mqtt_publish((topic_name + "/status").c_str(), "online", false); - publish_events(); - publish_common_info(); - publish_cell_voltages(); + + if (mqtt_publish((topic_name + "/status").c_str(), "online", false) == false) { + return; + } + + if (publish_events() == false) { + return; + } + + if (publish_common_info() == false) { + return; + } + +#ifdef MQTT_PUBLISH_CELL_VOLTAGES + if (publish_cell_voltages() == false) { + return; + } +#endif } #ifdef HA_AUTODISCOVERY @@ -169,7 +183,7 @@ void set_battery_attributes(JsonDocument& doc, const DATALAYER_BATTERY_TYPE& bat static std::vector order_events; -static void publish_common_info(void) { +static bool publish_common_info(void) { static JsonDocument doc; static String state_topic = topic_name + "/info"; #ifdef HA_AUTODISCOVERY @@ -191,6 +205,8 @@ static void publish_common_info(void) { serializeJson(doc, mqtt_msg); if (mqtt_publish(generateCommonInfoAutoConfigTopic(config.object_id).c_str(), mqtt_msg, true)) { ha_common_info_published = true; + } else { + return false; } doc.clear(); } @@ -211,18 +227,20 @@ static void publish_common_info(void) { } #endif // DOUBLE_BATTERY serializeJson(doc, mqtt_msg); - if (!mqtt_publish(state_topic.c_str(), mqtt_msg, false)) { + if (mqtt_publish(state_topic.c_str(), mqtt_msg, false) == false) { #ifdef DEBUG_LOG logging.println("Common info MQTT msg could not be sent"); #endif // DEBUG_LOG + return false; } doc.clear(); #ifdef HA_AUTODISCOVERY } #endif // HA_AUTODISCOVERY + return true; } -static void publish_cell_voltages(void) { +static bool publish_cell_voltages(void) { static JsonDocument doc; static String state_topic = topic_name + "/spec_data"; #ifdef DOUBLE_BATTERY @@ -244,6 +262,7 @@ static void publish_cell_voltages(void) { serializeJson(doc, mqtt_msg, sizeof(mqtt_msg)); if (mqtt_publish(generateCellVoltageAutoConfigTopic(cellNumber, "").c_str(), mqtt_msg, true) == false) { failed_to_publish = true; + return false; } } doc.clear(); // clear after sending autoconfig @@ -260,6 +279,7 @@ static void publish_cell_voltages(void) { serializeJson(doc, mqtt_msg, sizeof(mqtt_msg)); if (mqtt_publish(generateCellVoltageAutoConfigTopic(cellNumber, "_2_").c_str(), mqtt_msg, true) == false) { failed_to_publish = true; + return false; } } doc.clear(); // clear after sending autoconfig @@ -286,6 +306,7 @@ static void publish_cell_voltages(void) { #ifdef DEBUG_LOG logging.println("Cell voltage MQTT msg could not be sent"); #endif // DEBUG_LOG + return false; } doc.clear(); } @@ -306,13 +327,15 @@ static void publish_cell_voltages(void) { #ifdef DEBUG_LOG logging.println("Cell voltage MQTT msg could not be sent"); #endif // DEBUG_LOG + return false; } doc.clear(); } #endif // DOUBLE_BATTERY + return true; } -void publish_events() { +bool publish_events() { static JsonDocument doc; static String state_topic = topic_name + "/events"; #ifdef HA_AUTODISCOVERY @@ -331,6 +354,8 @@ void publish_events() { serializeJson(doc, mqtt_msg); if (mqtt_publish(generateEventsAutoConfigTopic("event").c_str(), mqtt_msg, true)) { ha_events_published = true; + } else { + return false; } doc.clear(); @@ -368,6 +393,7 @@ void publish_events() { #ifdef DEBUG_LOG logging.println("Common info MQTT msg could not be sent"); #endif // DEBUG_LOG + return false; } else { set_event_MQTTpublished(event_handle); } @@ -378,9 +404,10 @@ void publish_events() { #ifdef HA_AUTODISCOVERY } #endif // HA_AUTODISCOVERY + return true; } -static void publish_buttons_discovery(void) { +static bool publish_buttons_discovery(void) { #ifdef HA_AUTODISCOVERY if (ha_buttons_published == false) { #ifdef DEBUG_LOG @@ -397,11 +424,14 @@ static void publish_buttons_discovery(void) { serializeJson(doc, mqtt_msg); if (mqtt_publish(generateButtonAutoConfigTopic(config.object_id).c_str(), mqtt_msg, true)) { ha_buttons_published = true; + } else { + return false; } doc.clear(); } } #endif // HA_AUTODISCOVERY + return true; } static void subscribe() { @@ -511,6 +541,7 @@ void init_mqtt(void) { mqtt_cfg.session.last_will.retain = true; mqtt_cfg.session.last_will.msg = "offline"; mqtt_cfg.session.last_will.msg_len = strlen(mqtt_cfg.session.last_will.msg); + mqtt_cfg.network.timeout_ms = MQTT_TIMEOUT; client = esp_mqtt_client_init(&mqtt_cfg); esp_mqtt_client_register_event(client, MQTT_EVENT_ANY, mqtt_event_handler, client); } From d9167c3cc7eb1f50692969083d806c0bfb2bcbc4 Mon Sep 17 00:00:00 2001 From: Matt Holmes Date: Sun, 9 Mar 2025 18:33:14 +0000 Subject: [PATCH 2/7] Moving mqtt loop to new task with lower priority than wifi and http to limit impact of mqtt load on wifi and http performance. --- Software/Software.ino | 40 ++++++++++++++++++++++++++-------- Software/src/system_settings.h | 1 + 2 files changed, 32 insertions(+), 9 deletions(-) diff --git a/Software/Software.ino b/Software/Software.ino index 73e7c7cf..6b457798 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -70,9 +70,13 @@ MyTimer loop_task_timer_10s(INTERVAL_10_S); MyTimer check_pause_2s(INTERVAL_2_S); +int64_t mqtt_task_time_us; +MyTimer mqtt_task_timer_10s(INTERVAL_10_S); + TaskHandle_t main_loop_task; TaskHandle_t connectivity_loop_task; TaskHandle_t logging_loop_task; +TaskHandle_t mqtt_loop_task; Logging logging; @@ -99,6 +103,11 @@ void setup() { TASK_CONNECTIVITY_PRIO, &logging_loop_task, WIFI_CORE); #endif +#ifdef MQTT + xTaskCreatePinnedToCore((TaskFunction_t)&mqtt_loop, "mqtt_loop", 4096, &mqtt_task_time_us, TASK_MQTT_PRIO, + &mqtt_loop_task, WIFI_CORE); +#endif + init_CAN(); init_contactors(); @@ -180,9 +189,6 @@ void connectivity_loop(void* task_time_us) { #ifdef MDNSRESPONDER init_mDNS(); #endif -#ifdef MQTT - init_mqtt(); -#endif while (true) { START_TIME_MEASUREMENT(wifi); @@ -191,15 +197,9 @@ void connectivity_loop(void* task_time_us) { ota_monitor(); #endif END_TIME_MEASUREMENT_MAX(wifi, datalayer.system.status.wifi_task_10s_max_us); -#ifdef MQTT - START_TIME_MEASUREMENT(mqtt); - mqtt_loop(); - END_TIME_MEASUREMENT_MAX(mqtt, datalayer.system.status.mqtt_task_10s_max_us); -#endif #ifdef FUNCTION_TIME_MEASUREMENT if (connectivity_task_timer_10s.elapsed()) { - datalayer.system.status.mqtt_task_10s_max_us = 0; datalayer.system.status.wifi_task_10s_max_us = 0; } #endif @@ -209,6 +209,28 @@ void connectivity_loop(void* task_time_us) { } #endif +#ifdef MQTT +void mqtt_loop(void* task_time_us) { + esp_task_wdt_add(NULL); // Register this task with WDT + + init_mqtt(); + + while (true) { + START_TIME_MEASUREMENT(mqtt); + mqtt_loop(); + END_TIME_MEASUREMENT_MAX(mqtt, datalayer.system.status.mqtt_task_10s_max_us); + +#ifdef FUNCTION_TIME_MEASUREMENT + if (mqtt_task_timer_10s.elapsed()) { + datalayer.system.status.mqtt_task_10s_max_us = 0; + } +#endif + esp_task_wdt_reset(); // Reset watchdog + delay(1); + } +} +#endif + void core_loop(void* task_time_us) { esp_task_wdt_add(NULL); // Register this task with WDT TickType_t xLastWakeTime = xTaskGetTickCount(); diff --git a/Software/src/system_settings.h b/Software/src/system_settings.h index 93da44a9..a19b18e1 100644 --- a/Software/src/system_settings.h +++ b/Software/src/system_settings.h @@ -25,6 +25,7 @@ */ #define TASK_CORE_PRIO 4 #define TASK_CONNECTIVITY_PRIO 3 +#define TASK_MQTT_PRIO 2 #define TASK_MODBUS_PRIO 8 #define TASK_ACAN2515_PRIORITY 10 #define TASK_ACAN2517FD_PRIORITY 10 From e122567c6b20c4dc93a25cd010abfce7aeedae8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakob=20L=C3=B6w?= Date: Wed, 26 Feb 2025 08:23:23 +0100 Subject: [PATCH 3/7] :lipstick: correctly display battery type when using a RS485 battery --- Software/src/devboard/webserver/settings_html.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Software/src/devboard/webserver/settings_html.cpp b/Software/src/devboard/webserver/settings_html.cpp index ef94fe56..0fc0dae9 100644 --- a/Software/src/devboard/webserver/settings_html.cpp +++ b/Software/src/devboard/webserver/settings_html.cpp @@ -25,8 +25,13 @@ String settings_processor(const String& var) { "

Password: ########

"; +#ifndef RS485_BATTERY_SELECTED content += "

Battery interface: " + String(getCANInterfaceName(can_config.battery)) + "

"; +#endif +#ifdef RS485_BATTERY_SELECTED + content += "

Battery interface: RS485

"; +#endif #ifdef DOUBLE_BATTERY content += "

Battery #2 interface: " + From 6f9b72ed85bd87e516516a33b9fc10b845acb76b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakob=20L=C3=B6w?= Date: Wed, 26 Feb 2025 08:27:26 +0100 Subject: [PATCH 4/7] :sparkles: add a configurable low temperature limit to DALY BMS max power calculations --- Software/src/battery/DALY-BMS.cpp | 3 +++ Software/src/battery/DALY-BMS.h | 2 ++ 2 files changed, 5 insertions(+) diff --git a/Software/src/battery/DALY-BMS.cpp b/Software/src/battery/DALY-BMS.cpp index 06cabea8..d0af8a42 100644 --- a/Software/src/battery/DALY-BMS.cpp +++ b/Software/src/battery/DALY-BMS.cpp @@ -37,6 +37,9 @@ void update_values_battery() { else if (SOC > 8000) adaptive_power_limit = ((10000 - (uint32_t)SOC) * POWER_PER_PERCENT) / 100; + if (temperature_min_dC < LOW_TEMP_POWER_LIMIT_START && adaptive_power_limit > LOW_TEMP_POWER_LIMIT) + adaptive_power_limit = LOW_TEMP_POWER_LIMIT; + if (adaptive_power_limit < datalayer.battery.status.max_charge_power_W) datalayer.battery.status.max_charge_power_W = adaptive_power_limit; if (SOC < 2000 && adaptive_power_limit < datalayer.battery.status.max_discharge_power_W) diff --git a/Software/src/battery/DALY-BMS.h b/Software/src/battery/DALY-BMS.h index dfc3df02..df0c0da2 100644 --- a/Software/src/battery/DALY-BMS.h +++ b/Software/src/battery/DALY-BMS.h @@ -8,6 +8,8 @@ #define MAX_CELL_VOLTAGE_MV 4250 //Battery is put into emergency stop if one cell goes over this value #define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value #define POWER_PER_PERCENT 50 // below 20% and above 80% limit power to 50W * SOC (i.e. 150W at 3%, 500W at 10%, ...) +#define LOW_TEMP_POWER_LIMIT 800 // max power when temperature below limit +#define LOW_TEMP_POWER_LIMIT_START 50 // start limiting when below 50 = 5.0 °C /* Do not modify any rows below*/ #define BATTERY_SELECTED From 29b88778243ac262bd6b2acc6151585f4c9908fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakob=20L=C3=B6w?= Date: Wed, 26 Feb 2025 08:28:43 +0100 Subject: [PATCH 5/7] daly bms: change default configured voltages to fit standard 52V batteries --- Software/src/battery/DALY-BMS.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Software/src/battery/DALY-BMS.h b/Software/src/battery/DALY-BMS.h index df0c0da2..ade06529 100644 --- a/Software/src/battery/DALY-BMS.h +++ b/Software/src/battery/DALY-BMS.h @@ -3,10 +3,10 @@ /* Tweak these according to your battery build */ #define CELL_COUNT 14 -#define MAX_PACK_VOLTAGE_DV 588 //588 = 58.8V -#define MIN_PACK_VOLTAGE_DV 518 //518 = 51.8V -#define MAX_CELL_VOLTAGE_MV 4250 //Battery is put into emergency stop if one cell goes over this value -#define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value +#define MAX_PACK_VOLTAGE_DV 580 //580 = 58.0V +#define MIN_PACK_VOLTAGE_DV 460 //480 = 48.0V +#define MAX_CELL_VOLTAGE_MV 4200 //Battery is put into emergency stop if one cell goes over this value +#define MIN_CELL_VOLTAGE_MV 3200 //Battery is put into emergency stop if one cell goes below this value #define POWER_PER_PERCENT 50 // below 20% and above 80% limit power to 50W * SOC (i.e. 150W at 3%, 500W at 10%, ...) #define LOW_TEMP_POWER_LIMIT 800 // max power when temperature below limit #define LOW_TEMP_POWER_LIMIT_START 50 // start limiting when below 50 = 5.0 °C From 3ca3b2e0cb2cd40144cf8ce2a91ac80d8f57dd36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakob=20L=C3=B6w?= Date: Tue, 4 Mar 2025 21:06:45 +0100 Subject: [PATCH 6/7] :sparkles: daly bms: change the static temperature limit to a linearly scaled one --- Software/src/battery/DALY-BMS.cpp | 17 ++++++++++------- Software/src/battery/DALY-BMS.h | 6 +++--- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/Software/src/battery/DALY-BMS.cpp b/Software/src/battery/DALY-BMS.cpp index d0af8a42..a1637662 100644 --- a/Software/src/battery/DALY-BMS.cpp +++ b/Software/src/battery/DALY-BMS.cpp @@ -33,21 +33,24 @@ void update_values_battery() { // limit power when SoC is low or high uint32_t adaptive_power_limit = 999999; if (SOC < 2000) - adaptive_power_limit = ((uint32_t)SOC * POWER_PER_PERCENT) / 100; + adaptive_power_limit = ((uint32_t)(SOC + 100) * POWER_PER_PERCENT) / 100; else if (SOC > 8000) adaptive_power_limit = ((10000 - (uint32_t)SOC) * POWER_PER_PERCENT) / 100; - if (temperature_min_dC < LOW_TEMP_POWER_LIMIT_START && adaptive_power_limit > LOW_TEMP_POWER_LIMIT) - adaptive_power_limit = LOW_TEMP_POWER_LIMIT; - if (adaptive_power_limit < datalayer.battery.status.max_charge_power_W) datalayer.battery.status.max_charge_power_W = adaptive_power_limit; if (SOC < 2000 && adaptive_power_limit < datalayer.battery.status.max_discharge_power_W) datalayer.battery.status.max_discharge_power_W = adaptive_power_limit; - // always allow to charge at least a little bit - if (datalayer.battery.status.max_charge_power_W < POWER_PER_PERCENT) - datalayer.battery.status.max_charge_power_W = POWER_PER_PERCENT; + int32_t temperature_limit = (POWER_PER_DEGREE_C * (int32_t)temperature_min_dC + POWER_AT_0_DEGREE_C) / 10; + if (temperature_limit <= 0 || temperature_min_dC < BATTERY_MINTEMPERATURE || + temperature_max_dC > BATTERY_MAXTEMPERATURE) + temperature_limit = 0; + + if (temperature_limit < datalayer.battery.status.max_discharge_power_W) + datalayer.battery.status.max_discharge_power_W = temperature_limit; + if (temperature_limit < datalayer.battery.status.max_charge_power_W) + datalayer.battery.status.max_charge_power_W = temperature_limit; memcpy(datalayer.battery.status.cell_voltages_mV, cellvoltages_mV, sizeof(cellvoltages_mV)); datalayer.battery.status.cell_min_voltage_mV = cellvoltage_min_mV; diff --git a/Software/src/battery/DALY-BMS.h b/Software/src/battery/DALY-BMS.h index ade06529..cdc304c7 100644 --- a/Software/src/battery/DALY-BMS.h +++ b/Software/src/battery/DALY-BMS.h @@ -7,9 +7,9 @@ #define MIN_PACK_VOLTAGE_DV 460 //480 = 48.0V #define MAX_CELL_VOLTAGE_MV 4200 //Battery is put into emergency stop if one cell goes over this value #define MIN_CELL_VOLTAGE_MV 3200 //Battery is put into emergency stop if one cell goes below this value -#define POWER_PER_PERCENT 50 // below 20% and above 80% limit power to 50W * SOC (i.e. 150W at 3%, 500W at 10%, ...) -#define LOW_TEMP_POWER_LIMIT 800 // max power when temperature below limit -#define LOW_TEMP_POWER_LIMIT_START 50 // start limiting when below 50 = 5.0 °C +#define POWER_PER_PERCENT 50 // below 20% and above 80% limit power to 50W * SOC (i.e. 150W at 3%, 500W at 10%, ...) +#define POWER_PER_DEGREE_C 60 // max power added/removed per degree above/below 0°C +#define POWER_AT_0_DEGREE_C 800 // power at 0°C /* Do not modify any rows below*/ #define BATTERY_SELECTED From 3366e946ae7cf9ff8217a33709c8327033c862bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakob=20L=C3=B6w?= Date: Sun, 9 Mar 2025 21:31:42 +0100 Subject: [PATCH 7/7] :bug: daly: fix includes and temperature power slope --- Software/src/battery/DALY-BMS.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Software/src/battery/DALY-BMS.cpp b/Software/src/battery/DALY-BMS.cpp index a1637662..3d7f5b71 100644 --- a/Software/src/battery/DALY-BMS.cpp +++ b/Software/src/battery/DALY-BMS.cpp @@ -1,11 +1,9 @@ -#include "DALY-BMS.h" +#include "../include.h" #ifdef DALY_BMS #include #include "../datalayer/datalayer.h" #include "../devboard/utils/events.h" -#include "../include.h" -#include "RENAULT-TWIZY.h" -#include "RJXZS-BMS.h" +#include "DALY-BMS.h" /* Do not change code below unless you are sure what you are doing */ @@ -42,7 +40,7 @@ void update_values_battery() { if (SOC < 2000 && adaptive_power_limit < datalayer.battery.status.max_discharge_power_W) datalayer.battery.status.max_discharge_power_W = adaptive_power_limit; - int32_t temperature_limit = (POWER_PER_DEGREE_C * (int32_t)temperature_min_dC + POWER_AT_0_DEGREE_C) / 10; + int32_t temperature_limit = POWER_PER_DEGREE_C * (int32_t)temperature_min_dC / 10 + POWER_AT_0_DEGREE_C; if (temperature_limit <= 0 || temperature_min_dC < BATTERY_MINTEMPERATURE || temperature_max_dC > BATTERY_MAXTEMPERATURE) temperature_limit = 0;