Skip to content

Commit

Permalink
Merge pull request #773 from dalathegreat/main
Browse files Browse the repository at this point in the history
Update branch from main
  • Loading branch information
wjcloudy authored Jan 9, 2025
2 parents 5a65b22 + d2d67db commit a584499
Show file tree
Hide file tree
Showing 15 changed files with 354 additions and 21 deletions.
6 changes: 4 additions & 2 deletions Software/USER_SETTINGS.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions Software/USER_SETTINGS.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,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
Expand Down
10 changes: 10 additions & 0 deletions Software/src/battery/TESLA-BATTERY.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -901,6 +901,16 @@ void update_values_battery() { //This function maps all the values fetched via
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_NCA_NCM;
datalayer.battery.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_NCA_NCM;
}

// During forced balancing request via webserver, we allow the battery to exceed normal safety parameters
if (datalayer.battery.settings.user_requests_balancing) {
datalayer.battery.status.real_soc = 9900; //Force battery to show up as 99% when balancing
datalayer.battery.info.max_design_voltage_dV = datalayer.battery.settings.balancing_max_pack_voltage_dV;
datalayer.battery.info.max_cell_voltage_mV = datalayer.battery.settings.balancing_max_cell_voltage_mV;
datalayer.battery.info.max_cell_voltage_deviation_mV =
datalayer.battery.settings.balancing_max_deviation_cell_voltage_mV;
datalayer.battery.status.max_charge_power_W = datalayer.battery.settings.balancing_float_power_W;
}
#endif // TESLA_MODEL_3Y_BATTERY

// Update webserver datalayer
Expand Down
13 changes: 7 additions & 6 deletions Software/src/battery/TESLA-BATTERY.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@
#define MAXDISCHARGEPOWERALLOWED 60000 // 60000W we use a define since the value supplied by Tesla is always 0

/* Do not change the defines below */
#define RAMPDOWN_SOC 900 // 90.0 SOC% to start ramping down from max charge power towards 0 at 100.00%
#define RAMPDOWNPOWERALLOWED 15000 // What power we ramp down from towards top balancing
#define FLOAT_MAX_POWER_W 200 // W, what power to allow for top balancing battery
#define FLOAT_START_MV 20 // mV, how many mV under overvoltage to start float charging
#define RAMPDOWN_SOC 900 // 90.0 SOC% to start ramping down from max charge power towards 0 at 100.00%
#define RAMPDOWNPOWERALLOWED \
15000 // What power we ramp down from towards top balancing (usually same as MAXCHARGEPOWERALLOWED)
#define FLOAT_MAX_POWER_W 200 // W, what power to allow for top balancing battery
#define FLOAT_START_MV 20 // mV, how many mV under overvoltage to start float charging

#define MAX_PACK_VOLTAGE_SX_NCMA 4600 // V+1, if pack voltage goes over this, charge stops
#define MIN_PACK_VOLTAGE_SX_NCMA 3100 // V+1, if pack voltage goes over this, charge stops
Expand All @@ -22,10 +23,10 @@
#define MAX_PACK_VOLTAGE_3Y_LFP 3880 // V+1, if pack voltage goes over this, charge stops
#define MIN_PACK_VOLTAGE_3Y_LFP 2968 // V+1, if pack voltage goes below this, discharge stops
#define MAX_CELL_DEVIATION_NCA_NCM 500 //LED turns yellow on the board if mv delta exceeds this value
#define MAX_CELL_DEVIATION_LFP 200 //LED turns yellow on the board if mv delta exceeds this value
#define MAX_CELL_DEVIATION_LFP 400 //LED turns yellow on the board if mv delta exceeds this value
#define MAX_CELL_VOLTAGE_NCA_NCM 4250 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_NCA_NCM 2950 //Battery is put into emergency stop if one cell goes below this value
#define MAX_CELL_VOLTAGE_LFP 3550 //Battery is put into emergency stop if one cell goes over this value
#define MAX_CELL_VOLTAGE_LFP 3650 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_LFP 2800 //Battery is put into emergency stop if one cell goes below this value

//#define EXP_TESLA_BMS_DIGITAL_HVIL // Experimental parameter. Forces the transmission of additional CAN frames for experimental purposes, to test potential HVIL issues in 3/Y packs with newer firmware.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,15 @@
*/
void handle_BMSpower();

/**
* @brief Start BMS reset sequence
*
* @param[in] void
*
* @return void
*/
void start_bms_reset();

/**
* @brief Contactor initialization
*
Expand Down
15 changes: 15 additions & 0 deletions Software/src/datalayer/datalayer.h
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,21 @@ typedef struct {
/** The user specified maximum allowed discharge voltage, in deciVolt. 3000 = 300.0 V */
uint16_t max_user_set_discharge_voltage_dV = BATTERY_MAX_DISCHARGE_VOLTAGE;

/** Tesla specific settings that are edited on the fly when manually forcing a balance charge for LFP chemistry */
/* Bool for specifying if user has requested manual balancing */
bool user_requests_balancing = false;
/* Forced balancing max time & start timestamp */
uint32_t balancing_time_ms = 3600000; //1h default, (60min*60sec*1000ms)
uint32_t balancing_start_time_ms = 0; //For keeping track when balancing started
/* Max cell voltage during forced balancing */
uint16_t balancing_max_cell_voltage_mV = 3650;
/* Max cell deviation allowed during forced balancing */
uint16_t balancing_max_deviation_cell_voltage_mV = 400;
/* Float max power during forced balancing */
uint16_t balancing_float_power_W = 1000;
/* Maximum voltage for entire battery pack during forced balancing */
uint16_t balancing_max_pack_voltage_dV = 3940;

} DATALAYER_BATTERY_SETTINGS_TYPE;

typedef struct {
Expand Down
88 changes: 83 additions & 5 deletions Software/src/devboard/mqtt/mqtt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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;
Expand Down Expand Up @@ -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";
}
Expand All @@ -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<EventData> order_events;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
1 change: 1 addition & 0 deletions Software/src/devboard/mqtt/mqtt.h
Original file line number Diff line number Diff line change
Expand Up @@ -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];

Expand Down
20 changes: 20 additions & 0 deletions Software/src/devboard/safety/safety.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,26 @@ void update_machineryprotection() {
if (datalayer.battery.status.max_charge_power_W == 0) {
datalayer.battery.status.max_charge_current_dA = 0;
}

//Decrement the forced balancing timer incase user requested it
if (datalayer.battery.settings.user_requests_balancing) {
// If this is the start of the balancing period, capture the current time
if (datalayer.battery.settings.balancing_start_time_ms == 0) {
datalayer.battery.settings.balancing_start_time_ms = millis();
set_event(EVENT_BALANCING_START, 0);
} else {
clear_event(EVENT_BALANCING_START);
}

// Check if the elapsed time exceeds the balancing time
if (millis() - datalayer.battery.settings.balancing_start_time_ms >= datalayer.battery.settings.balancing_time_ms) {
datalayer.battery.settings.user_requests_balancing = false;
datalayer.battery.settings.balancing_start_time_ms = 0; // Reset the start time
set_event(EVENT_BALANCING_END, 0);
} else {
clear_event(EVENT_BALANCING_END);
}
}
}

//battery pause status begin
Expand Down
6 changes: 6 additions & 0 deletions Software/src/devboard/utils/events.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,8 @@ void init_events(void) {
events.entries[EVENT_SOC_PLAUSIBILITY_ERROR].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_SOC_UNAVAILABLE].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_KWH_PLAUSIBILITY_ERROR].level = EVENT_LEVEL_INFO;
events.entries[EVENT_BALANCING_START].level = EVENT_LEVEL_INFO;
events.entries[EVENT_BALANCING_END].level = EVENT_LEVEL_INFO;
events.entries[EVENT_BATTERY_EMPTY].level = EVENT_LEVEL_INFO;
events.entries[EVENT_BATTERY_FULL].level = EVENT_LEVEL_INFO;
events.entries[EVENT_BATTERY_FROZEN].level = EVENT_LEVEL_INFO;
Expand Down Expand Up @@ -302,6 +304,10 @@ const char* get_event_message_string(EVENTS_ENUM_TYPE event) {
return "Warning: SOC not sent by BMS. Calibrate BMS via app.";
case EVENT_KWH_PLAUSIBILITY_ERROR:
return "Info: kWh remaining reported by battery not plausible. Battery needs cycling.";
case EVENT_BALANCING_START:
return "Info: Balancing has started";
case EVENT_BALANCING_END:
return "Info: Balancing has ended";
case EVENT_BATTERY_EMPTY:
return "Info: Battery is completely discharged";
case EVENT_BATTERY_FULL:
Expand Down
Loading

0 comments on commit a584499

Please sign in to comment.