mirror of
https://github.com/dalathegreat/Battery-Emulator.git
synced 2025-10-03 09:49:32 +02:00
Adding ability to remotely trigger a BMS reset via MQTT and configure Device ID for multiple emulators (#746)
* Adding ability to remotely trigger a BMS reset via MQTT * Adding comment to remove build warning * Enabling configuration of HA Device Id to allow for multiple battery emulators in HA without collision
This commit is contained in:
parent
64e633f10f
commit
d2d67db844
7 changed files with 129 additions and 14 deletions
|
@ -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_"
|
"be_"; // Custom prefix for MQTT object ID. Previously, the prefix was automatically set to "esp32-XXXXXX_"
|
||||||
const char* mqtt_device_name =
|
const char* mqtt_device_name =
|
||||||
"Battery Emulator"; // Custom device name in Home Assistant. Previously, the name was automatically set to "BatteryEmulator_esp32-XXXXXX"
|
"Battery Emulator"; // Custom device name in Home Assistant. Previously, the name was automatically set to "BatteryEmulator_esp32-XXXXXX"
|
||||||
#endif // MQTT_MANUAL_TOPIC_OBJECT_NAME
|
const char* ha_device_id =
|
||||||
#endif // USE_MQTT
|
"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
|
#ifdef EQUIPMENT_STOP_BUTTON
|
||||||
// Equipment stop button behavior. Use NC button for safety reasons.
|
// Equipment stop button behavior. Use NC button for safety reasons.
|
||||||
|
|
|
@ -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 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 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 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 */
|
/* Shunt/Contactor settings */
|
||||||
//#define BMW_SBOX // SBOX relay control & battery current/voltage measurement
|
//#define BMW_SBOX // SBOX relay control & battery current/voltage measurement
|
||||||
|
|
|
@ -94,14 +94,14 @@ void init_contactors() {
|
||||||
pinMode(BMS_2_POWER, OUTPUT);
|
pinMode(BMS_2_POWER, OUTPUT);
|
||||||
digitalWrite(BMS_2_POWER, HIGH);
|
digitalWrite(BMS_2_POWER, HIGH);
|
||||||
#endif BMS_2_POWER
|
#endif BMS_2_POWER
|
||||||
#endif // HW with dedicated BMS pins
|
#endif // HW with dedicated BMS pins
|
||||||
#ifdef PERIODIC_BMS_RESET // User has enabled BMS reset, turn on output on start
|
#if defined(PERIODIC_BMS_RESET) || defined(REMOTE_BMS_RESET) // User has enabled BMS reset, turn on output on start
|
||||||
pinMode(BMS_POWER, OUTPUT);
|
pinMode(BMS_POWER, OUTPUT);
|
||||||
digitalWrite(BMS_POWER, HIGH);
|
digitalWrite(BMS_POWER, HIGH);
|
||||||
#ifdef BMS_2_POWER //Hardware supports 2x BMS
|
#ifdef BMS_2_POWER //Hardware supports 2x BMS
|
||||||
pinMode(BMS_2_POWER, OUTPUT);
|
pinMode(BMS_2_POWER, OUTPUT);
|
||||||
digitalWrite(BMS_2_POWER, HIGH);
|
digitalWrite(BMS_2_POWER, HIGH);
|
||||||
#endif BMS_2_POWER
|
#endif //BMS_2_POWER
|
||||||
#endif //PERIODIC_BMS_RESET
|
#endif //PERIODIC_BMS_RESET
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -222,17 +222,41 @@ void handle_contactors_battery2() {
|
||||||
}
|
}
|
||||||
#endif // CONTACTOR_CONTROL_DOUBLE_BATTERY
|
#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
|
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 */
|
Feature is only used if user has enabled PERIODIC_BMS_RESET in the USER_SETTINGS */
|
||||||
|
|
||||||
void handle_BMSpower() {
|
void handle_BMSpower() {
|
||||||
#ifdef PERIODIC_BMS_RESET
|
#if defined(PERIODIC_BMS_RESET) || defined(REMOTE_BMS_RESET)
|
||||||
// Get current time
|
// Get current time
|
||||||
currentTime = millis();
|
currentTime = millis();
|
||||||
|
|
||||||
|
#ifdef PERIODIC_BMS_RESET
|
||||||
// Check if 24 hours have passed since the last power removal
|
// 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
|
lastPowerRemovalTime = currentTime; // Record the time when BMS reset was started
|
||||||
|
|
||||||
// Set emulator state to paused (Max Charge/Discharge = 0 & CAN = stop)
|
// Set emulator state to paused (Max Charge/Discharge = 0 & CAN = stop)
|
||||||
|
|
|
@ -15,6 +15,15 @@
|
||||||
*/
|
*/
|
||||||
void handle_BMSpower();
|
void handle_BMSpower();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Start BMS reset sequence
|
||||||
|
*
|
||||||
|
* @param[in] void
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
void start_bms_reset();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Contactor initialization
|
* @brief Contactor initialization
|
||||||
*
|
*
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
#include "../../../USER_SECRETS.h"
|
#include "../../../USER_SECRETS.h"
|
||||||
#include "../../../USER_SETTINGS.h"
|
#include "../../../USER_SETTINGS.h"
|
||||||
#include "../../battery/BATTERIES.h"
|
#include "../../battery/BATTERIES.h"
|
||||||
|
#include "../../communication/contactorcontrol/comm_contactorcontrol.h"
|
||||||
#include "../../datalayer/datalayer.h"
|
#include "../../datalayer/datalayer.h"
|
||||||
#include "../../lib/bblanchon-ArduinoJson/ArduinoJson.h"
|
#include "../../lib/bblanchon-ArduinoJson/ArduinoJson.h"
|
||||||
#include "../../lib/knolleary-pubsubclient/PubSubClient.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 topic_name = "";
|
||||||
static String object_id_prefix = "";
|
static String object_id_prefix = "";
|
||||||
static String device_name = "";
|
static String device_name = "";
|
||||||
|
static String device_id = "";
|
||||||
|
|
||||||
// Tracking reconnection attempts and failures
|
// Tracking reconnection attempts and failures
|
||||||
static unsigned long lastReconnectAttempt = 0;
|
static unsigned long lastReconnectAttempt = 0;
|
||||||
|
@ -90,6 +92,8 @@ SensorConfig sensorConfigs[] = {
|
||||||
#endif // DOUBLE_BATTERY
|
#endif // DOUBLE_BATTERY
|
||||||
};
|
};
|
||||||
|
|
||||||
|
SensorConfig buttonConfigs[] = {{"BMSRESET", "Reset BMS", "", "", ""}};
|
||||||
|
|
||||||
static String generateCommonInfoAutoConfigTopic(const char* object_id) {
|
static String generateCommonInfoAutoConfigTopic(const char* object_id) {
|
||||||
return "homeassistant/sensor/" + topic_name + "/" + String(object_id) + "/config";
|
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";
|
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
|
#endif // HA_AUTODISCOVERY
|
||||||
|
|
||||||
static std::vector<EventData> order_events;
|
static std::vector<EventData> order_events;
|
||||||
|
@ -130,7 +142,7 @@ static void publish_common_info(void) {
|
||||||
}
|
}
|
||||||
doc["enabled_by_default"] = true;
|
doc["enabled_by_default"] = true;
|
||||||
doc["expire_after"] = 240;
|
doc["expire_after"] = 240;
|
||||||
doc["device"]["identifiers"][0] = "battery-emulator";
|
doc["device"]["identifiers"][0] = ha_device_id;
|
||||||
doc["device"]["manufacturer"] = "DalaTech";
|
doc["device"]["manufacturer"] = "DalaTech";
|
||||||
doc["device"]["model"] = "BatteryEmulator";
|
doc["device"]["model"] = "BatteryEmulator";
|
||||||
doc["device"]["name"] = device_name;
|
doc["device"]["name"] = device_name;
|
||||||
|
@ -235,7 +247,7 @@ static void publish_cell_voltages(void) {
|
||||||
doc["enabled_by_default"] = true;
|
doc["enabled_by_default"] = true;
|
||||||
doc["expire_after"] = 240;
|
doc["expire_after"] = 240;
|
||||||
doc["value_template"] = "{{ value_json.cell_voltages[" + String(i) + "] }}";
|
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"]["manufacturer"] = "DalaTech";
|
||||||
doc["device"]["model"] = "BatteryEmulator";
|
doc["device"]["model"] = "BatteryEmulator";
|
||||||
doc["device"]["name"] = device_name;
|
doc["device"]["name"] = device_name;
|
||||||
|
@ -264,7 +276,7 @@ static void publish_cell_voltages(void) {
|
||||||
doc["enabled_by_default"] = true;
|
doc["enabled_by_default"] = true;
|
||||||
doc["expire_after"] = 240;
|
doc["expire_after"] = 240;
|
||||||
doc["value_template"] = "{{ value_json.cell_voltages[" + String(i) + "] }}";
|
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"]["manufacturer"] = "DalaTech";
|
||||||
doc["device"]["model"] = "BatteryEmulator";
|
doc["device"]["model"] = "BatteryEmulator";
|
||||||
doc["device"]["name"] = device_name;
|
doc["device"]["name"] = device_name;
|
||||||
|
@ -343,7 +355,7 @@ void publish_events() {
|
||||||
doc["json_attributes_topic"] = state_topic;
|
doc["json_attributes_topic"] = state_topic;
|
||||||
doc["json_attributes_template"] = "{{ value_json | tojson }}";
|
doc["json_attributes_template"] = "{{ value_json | tojson }}";
|
||||||
doc["enabled_by_default"] = true;
|
doc["enabled_by_default"] = true;
|
||||||
doc["device"]["identifiers"][0] = "battery-emulator";
|
doc["device"]["identifiers"][0] = ha_device_id;
|
||||||
doc["device"]["manufacturer"] = "DalaTech";
|
doc["device"]["manufacturer"] = "DalaTech";
|
||||||
doc["device"]["model"] = "BatteryEmulator";
|
doc["device"]["model"] = "BatteryEmulator";
|
||||||
doc["device"]["name"] = device_name;
|
doc["device"]["name"] = device_name;
|
||||||
|
@ -400,6 +412,66 @@ void publish_events() {
|
||||||
#endif // HA_AUTODISCOVERY
|
#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 */
|
/* If we lose the connection, get it back */
|
||||||
static bool reconnect() {
|
static bool reconnect() {
|
||||||
// attempt one reconnection
|
// attempt one reconnection
|
||||||
|
@ -414,6 +486,10 @@ static bool reconnect() {
|
||||||
clear_event(EVENT_MQTT_DISCONNECT);
|
clear_event(EVENT_MQTT_DISCONNECT);
|
||||||
set_event(EVENT_MQTT_CONNECT, 0);
|
set_event(EVENT_MQTT_CONNECT, 0);
|
||||||
reconnectAttempts = 0; // Reset attempts on successful connection
|
reconnectAttempts = 0; // Reset attempts on successful connection
|
||||||
|
#ifdef HA_AUTODISCOVERY
|
||||||
|
publish_buttons_discovery();
|
||||||
|
#endif
|
||||||
|
subscribe();
|
||||||
#ifdef DEBUG_LOG
|
#ifdef DEBUG_LOG
|
||||||
logging.println("connected");
|
logging.println("connected");
|
||||||
#endif // DEBUG_LOG
|
#endif // DEBUG_LOG
|
||||||
|
@ -440,16 +516,18 @@ void init_mqtt(void) {
|
||||||
topic_name = mqtt_topic_name;
|
topic_name = mqtt_topic_name;
|
||||||
object_id_prefix = mqtt_object_id_prefix;
|
object_id_prefix = mqtt_object_id_prefix;
|
||||||
device_name = mqtt_device_name;
|
device_name = mqtt_device_name;
|
||||||
|
device_id = ha_device_id;
|
||||||
#else
|
#else
|
||||||
// Use default naming based on WiFi hostname for topic, object ID prefix, and device name
|
// Use default naming based on WiFi hostname for topic, object ID prefix, and device name
|
||||||
topic_name = "battery-emulator_" + String(WiFi.getHostname());
|
topic_name = "battery-emulator_" + String(WiFi.getHostname());
|
||||||
object_id_prefix = String(WiFi.getHostname()) + String("_");
|
object_id_prefix = String(WiFi.getHostname()) + String("_");
|
||||||
device_name = "BatteryEmulator_" + String(WiFi.getHostname());
|
device_name = "BatteryEmulator_" + String(WiFi.getHostname());
|
||||||
|
device_id = "battery-emulator";
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
client.setServer(MQTT_SERVER, MQTT_PORT);
|
client.setServer(MQTT_SERVER, MQTT_PORT);
|
||||||
|
client.setCallback(mqtt_message_received);
|
||||||
#ifdef DEBUG_LOG
|
#ifdef DEBUG_LOG
|
||||||
logging.println("MQTT initialized");
|
logging.println("MQTT initialized");
|
||||||
#endif // DEBUG_LOG
|
#endif // DEBUG_LOG
|
||||||
|
|
|
@ -47,6 +47,7 @@ extern const char* mqtt_password;
|
||||||
extern const char* mqtt_topic_name;
|
extern const char* mqtt_topic_name;
|
||||||
extern const char* mqtt_object_id_prefix;
|
extern const char* mqtt_object_id_prefix;
|
||||||
extern const char* mqtt_device_name;
|
extern const char* mqtt_device_name;
|
||||||
|
extern const char* ha_device_id;
|
||||||
|
|
||||||
extern char mqtt_msg[MQTT_MSG_BUFFER_SIZE];
|
extern char mqtt_msg[MQTT_MSG_BUFFER_SIZE];
|
||||||
|
|
||||||
|
|
|
@ -45,7 +45,7 @@
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef HW_LILYGO
|
#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)
|
#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
|
//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!
|
#error BMS RESET CANNOT BE USED AT SAME TIME AS CAN-ADDONS / CHADMEO! NOT ENOUGH GPIO!
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue