diff --git a/Software/USER_SETTINGS.cpp b/Software/USER_SETTINGS.cpp index dc3363e2..3026ac80 100644 --- a/Software/USER_SETTINGS.cpp +++ b/Software/USER_SETTINGS.cpp @@ -47,8 +47,10 @@ const char* mqtt_object_id_prefix = "be_"; // Custom prefix for MQTT object ID. Previously, the prefix was automatically set to "esp32-XXXXXX_" const char* mqtt_device_name = "Battery Emulator"; // Custom device name in Home Assistant. Previously, the name was automatically set to "BatteryEmulator_esp32-XXXXXX" -#endif // MQTT_MANUAL_TOPIC_OBJECT_NAME -#endif // USE_MQTT +const char* ha_device_id = + "battery-emulator"; // Custom device ID in Home Assistant. Previously, the ID was always "battery-emulator" +#endif // MQTT_MANUAL_TOPIC_OBJECT_NAME +#endif // USE_MQTT #ifdef EQUIPMENT_STOP_BUTTON // Equipment stop button behavior. Use NC button for safety reasons. diff --git a/Software/USER_SETTINGS.h b/Software/USER_SETTINGS.h index b4d027fb..6ce53902 100644 --- a/Software/USER_SETTINGS.h +++ b/Software/USER_SETTINGS.h @@ -67,6 +67,7 @@ //#define PWM_CONTACTOR_CONTROL //Enable this line to use PWM for CONTACTOR_CONTROL, which lowers power consumption and heat generation. CONTACTOR_CONTROL must be enabled. //#define NC_CONTACTORS //Enable this line to control normally closed contactors. CONTACTOR_CONTROL must be enabled for this option. Extremely rare setting! //#define PERIODIC_BMS_RESET //Enable to have the emulator powercycle the connected battery every 24hours via GPIO. Useful for some batteries like Nissan LEAF +//#define REMOTE_BMS_RESET //Enable to allow the emulator to remotely trigger a powercycle of the battery via MQTT. Useful for some batteries like Nissan LEAF /* Shunt/Contactor settings */ //#define BMW_SBOX // SBOX relay control & battery current/voltage measurement diff --git a/Software/src/communication/contactorcontrol/comm_contactorcontrol.cpp b/Software/src/communication/contactorcontrol/comm_contactorcontrol.cpp index 8accc245..c7714fe4 100644 --- a/Software/src/communication/contactorcontrol/comm_contactorcontrol.cpp +++ b/Software/src/communication/contactorcontrol/comm_contactorcontrol.cpp @@ -94,14 +94,14 @@ void init_contactors() { pinMode(BMS_2_POWER, OUTPUT); digitalWrite(BMS_2_POWER, HIGH); #endif BMS_2_POWER -#endif // HW with dedicated BMS pins -#ifdef PERIODIC_BMS_RESET // User has enabled BMS reset, turn on output on start +#endif // HW with dedicated BMS pins +#if defined(PERIODIC_BMS_RESET) || defined(REMOTE_BMS_RESET) // User has enabled BMS reset, turn on output on start pinMode(BMS_POWER, OUTPUT); digitalWrite(BMS_POWER, HIGH); #ifdef BMS_2_POWER //Hardware supports 2x BMS pinMode(BMS_2_POWER, OUTPUT); digitalWrite(BMS_2_POWER, HIGH); -#endif BMS_2_POWER +#endif //BMS_2_POWER #endif //PERIODIC_BMS_RESET } @@ -222,17 +222,41 @@ void handle_contactors_battery2() { } #endif // CONTACTOR_CONTROL_DOUBLE_BATTERY -/* Once every 24 hours we remove power from the BMS_power pin for 30 seconds. This makes the BMS recalculate all SOC% and avoid memory leaks +/* PERIODIC_BMS_RESET - Once every 24 hours we remove power from the BMS_power pin for 30 seconds. +REMOTE_BMS_RESET - Allows the user to remotely powercycle the BMS by sending a command to the emulator via MQTT. + +This makes the BMS recalculate all SOC% and avoid memory leaks During that time we also set the emulator state to paused in order to not try and send CAN messages towards the battery Feature is only used if user has enabled PERIODIC_BMS_RESET in the USER_SETTINGS */ void handle_BMSpower() { -#ifdef PERIODIC_BMS_RESET +#if defined(PERIODIC_BMS_RESET) || defined(REMOTE_BMS_RESET) // Get current time currentTime = millis(); +#ifdef PERIODIC_BMS_RESET // Check if 24 hours have passed since the last power removal - if (currentTime - lastPowerRemovalTime >= powerRemovalInterval && !isBMSResetActive) { + if (currentTime - lastPowerRemovalTime >= powerRemovalInterval) { + start_bms_reset(); + } +#endif //PERIODIC_BMS_RESET + + // If power has been removed for 30 seconds, restore the power and resume the emulator + if (isBMSResetActive && currentTime - lastPowerRemovalTime >= powerRemovalDuration) { + // Reapply power to the BMS + digitalWrite(BMS_POWER, HIGH); + + //Resume the battery pause and CAN communication + setBatteryPause(false, false, false, false); + + isBMSResetActive = false; // Reset the power removal flag + } +#endif //defined(PERIODIC_BMS_RESET) || defined(REMOTE_BMS_RESET) +} + +void start_bms_reset() { +#if defined(PERIODIC_BMS_RESET) || defined(REMOTE_BMS_RESET) + if (!isBMSResetActive) { lastPowerRemovalTime = currentTime; // Record the time when BMS reset was started // Set emulator state to paused (Max Charge/Discharge = 0 & CAN = stop) diff --git a/Software/src/communication/contactorcontrol/comm_contactorcontrol.h b/Software/src/communication/contactorcontrol/comm_contactorcontrol.h index ce2c0e84..febfd118 100644 --- a/Software/src/communication/contactorcontrol/comm_contactorcontrol.h +++ b/Software/src/communication/contactorcontrol/comm_contactorcontrol.h @@ -15,6 +15,15 @@ */ void handle_BMSpower(); +/** + * @brief Start BMS reset sequence + * + * @param[in] void + * + * @return void + */ +void start_bms_reset(); + /** * @brief Contactor initialization * diff --git a/Software/src/devboard/mqtt/mqtt.cpp b/Software/src/devboard/mqtt/mqtt.cpp index 2797db3b..e0aef893 100644 --- a/Software/src/devboard/mqtt/mqtt.cpp +++ b/Software/src/devboard/mqtt/mqtt.cpp @@ -5,6 +5,7 @@ #include "../../../USER_SECRETS.h" #include "../../../USER_SETTINGS.h" #include "../../battery/BATTERIES.h" +#include "../../communication/contactorcontrol/comm_contactorcontrol.h" #include "../../datalayer/datalayer.h" #include "../../lib/bblanchon-ArduinoJson/ArduinoJson.h" #include "../../lib/knolleary-pubsubclient/PubSubClient.h" @@ -20,6 +21,7 @@ MyTimer check_global_timer(800); // check timmer - low-priority MQTT checks, static String topic_name = ""; static String object_id_prefix = ""; static String device_name = ""; +static String device_id = ""; // Tracking reconnection attempts and failures static unsigned long lastReconnectAttempt = 0; @@ -90,6 +92,8 @@ SensorConfig sensorConfigs[] = { #endif // DOUBLE_BATTERY }; +SensorConfig buttonConfigs[] = {{"BMSRESET", "Reset BMS", "", "", ""}}; + static String generateCommonInfoAutoConfigTopic(const char* object_id) { return "homeassistant/sensor/" + topic_name + "/" + String(object_id) + "/config"; } @@ -102,6 +106,14 @@ static String generateEventsAutoConfigTopic(const char* object_id) { return "homeassistant/sensor/" + topic_name + "/" + String(object_id) + "/config"; } +static String generateButtonTopic(const char* subtype) { + return "homeassistant/button/" + topic_name + "/" + String(subtype); +} + +static String generateButtonAutoConfigTopic(const char* subtype) { + return generateButtonTopic(subtype) + "/config"; +} + #endif // HA_AUTODISCOVERY static std::vector order_events; @@ -130,7 +142,7 @@ static void publish_common_info(void) { } doc["enabled_by_default"] = true; doc["expire_after"] = 240; - doc["device"]["identifiers"][0] = "battery-emulator"; + doc["device"]["identifiers"][0] = ha_device_id; doc["device"]["manufacturer"] = "DalaTech"; doc["device"]["model"] = "BatteryEmulator"; doc["device"]["name"] = device_name; @@ -235,7 +247,7 @@ static void publish_cell_voltages(void) { doc["enabled_by_default"] = true; doc["expire_after"] = 240; doc["value_template"] = "{{ value_json.cell_voltages[" + String(i) + "] }}"; - doc["device"]["identifiers"][0] = "battery-emulator"; + doc["device"]["identifiers"][0] = ha_device_id; doc["device"]["manufacturer"] = "DalaTech"; doc["device"]["model"] = "BatteryEmulator"; doc["device"]["name"] = device_name; @@ -264,7 +276,7 @@ static void publish_cell_voltages(void) { doc["enabled_by_default"] = true; doc["expire_after"] = 240; doc["value_template"] = "{{ value_json.cell_voltages[" + String(i) + "] }}"; - doc["device"]["identifiers"][0] = "battery-emulator"; + doc["device"]["identifiers"][0] = ha_device_id; doc["device"]["manufacturer"] = "DalaTech"; doc["device"]["model"] = "BatteryEmulator"; doc["device"]["name"] = device_name; @@ -343,7 +355,7 @@ void publish_events() { doc["json_attributes_topic"] = state_topic; doc["json_attributes_template"] = "{{ value_json | tojson }}"; doc["enabled_by_default"] = true; - doc["device"]["identifiers"][0] = "battery-emulator"; + doc["device"]["identifiers"][0] = ha_device_id; doc["device"]["manufacturer"] = "DalaTech"; doc["device"]["model"] = "BatteryEmulator"; doc["device"]["name"] = device_name; @@ -400,6 +412,66 @@ void publish_events() { #endif // HA_AUTODISCOVERY } +static void publish_buttons_discovery(void) { +#ifdef HA_AUTODISCOVERY + static bool mqtt_first_transmission = true; + if (mqtt_first_transmission == true) { + mqtt_first_transmission = false; + +#ifdef DEBUG_LOG + logging.println("Publishing buttons discovery"); +#endif // DEBUG_LOG + + static JsonDocument doc; + for (int i = 0; i < sizeof(buttonConfigs) / sizeof(buttonConfigs[0]); i++) { + SensorConfig& config = buttonConfigs[i]; + doc["name"] = config.name; + doc["unique_id"] = config.object_id; + doc["command_topic"] = generateButtonTopic(config.object_id); + doc["enabled_by_default"] = true; + doc["expire_after"] = 240; + doc["device"]["identifiers"][0] = ha_device_id; + doc["device"]["manufacturer"] = "DalaTech"; + doc["device"]["model"] = "BatteryEmulator"; + doc["device"]["name"] = device_name; + doc["origin"]["name"] = "BatteryEmulator"; + doc["origin"]["sw"] = String(version_number) + "-mqtt"; + doc["origin"]["url"] = "https://github.com/dalathegreat/Battery-Emulator"; + serializeJson(doc, mqtt_msg); + mqtt_publish(generateButtonAutoConfigTopic(config.object_id).c_str(), mqtt_msg, true); + doc.clear(); + } + } +#endif // HA_AUTODISCOVERY +} + +static void subscribe() { + for (int i = 0; i < sizeof(buttonConfigs) / sizeof(buttonConfigs[0]); i++) { + SensorConfig& config = buttonConfigs[i]; + const char* topic = generateButtonTopic(config.object_id).c_str(); +#ifdef DEBUG_LOG + logging.printf("Subscribing to topic: [%s]\n", topic); +#endif // DEBUG_LOG + client.subscribe(topic); + } +} + +void mqtt_message_received(char* topic, byte* payload, unsigned int length) { +#ifdef DEBUG_LOG + logging.printf("MQTT message arrived: [%s]\n", topic); +#endif // DEBUG_LOG + +#ifdef REMOTE_BMS_RESET + const char* bmsreset_topic = generateButtonTopic("BMSRESET").c_str(); + if (strcmp(topic, bmsreset_topic) == 0) { +#ifdef DEBUG_LOG + logging.println("Triggering BMS reset"); +#endif // DEBUG_LOG + start_bms_reset(); + } +#endif // REMOTE_BMS_RESET +} + /* If we lose the connection, get it back */ static bool reconnect() { // attempt one reconnection @@ -414,6 +486,10 @@ static bool reconnect() { clear_event(EVENT_MQTT_DISCONNECT); set_event(EVENT_MQTT_CONNECT, 0); reconnectAttempts = 0; // Reset attempts on successful connection +#ifdef HA_AUTODISCOVERY + publish_buttons_discovery(); +#endif + subscribe(); #ifdef DEBUG_LOG logging.println("connected"); #endif // DEBUG_LOG @@ -440,16 +516,18 @@ void init_mqtt(void) { topic_name = mqtt_topic_name; object_id_prefix = mqtt_object_id_prefix; device_name = mqtt_device_name; + device_id = ha_device_id; #else // Use default naming based on WiFi hostname for topic, object ID prefix, and device name topic_name = "battery-emulator_" + String(WiFi.getHostname()); object_id_prefix = String(WiFi.getHostname()) + String("_"); device_name = "BatteryEmulator_" + String(WiFi.getHostname()); - + device_id = "battery-emulator"; #endif #endif client.setServer(MQTT_SERVER, MQTT_PORT); + client.setCallback(mqtt_message_received); #ifdef DEBUG_LOG logging.println("MQTT initialized"); #endif // DEBUG_LOG diff --git a/Software/src/devboard/mqtt/mqtt.h b/Software/src/devboard/mqtt/mqtt.h index 91ddc0ea..1ee57f30 100644 --- a/Software/src/devboard/mqtt/mqtt.h +++ b/Software/src/devboard/mqtt/mqtt.h @@ -47,6 +47,7 @@ extern const char* mqtt_password; extern const char* mqtt_topic_name; extern const char* mqtt_object_id_prefix; extern const char* mqtt_device_name; +extern const char* ha_device_id; extern char mqtt_msg[MQTT_MSG_BUFFER_SIZE]; diff --git a/Software/src/include.h b/Software/src/include.h index bb49e865..792b8446 100644 --- a/Software/src/include.h +++ b/Software/src/include.h @@ -45,7 +45,7 @@ #endif #ifdef HW_LILYGO -#ifdef PERIODIC_BMS_RESET +#if defined(PERIODIC_BMS_RESET) || defined(REMOTE_BMS_RESET) #if defined(CAN_ADDON) || defined(CANFD_ADDON) || defined(CHADEMO_BATTERY) //Check that BMS reset is not used at the same time as Chademo and CAN addons #error BMS RESET CANNOT BE USED AT SAME TIME AS CAN-ADDONS / CHADMEO! NOT ENOUGH GPIO!