Merge pull request #603 from dalathegreat/feature/ampere-datalayer

Improvement: Add charge/discharge current to datalayer
This commit is contained in:
Daniel Öster 2024-11-11 12:23:23 +02:00 committed by GitHub
commit 19ed2ca662
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 121 additions and 329 deletions

View file

@ -293,7 +293,7 @@ void core_loop(void* task_time_us) {
#ifdef DOUBLE_BATTERY #ifdef DOUBLE_BATTERY
update_values_battery2(); update_values_battery2();
#endif #endif
update_scaled_values(); // Check if real or calculated SOC% value should be sent update_calculated_values();
#ifndef SERIAL_LINK_RECEIVER #ifndef SERIAL_LINK_RECEIVER
update_machineryprotection(); // Check safeties (Not on serial link reciever board) update_machineryprotection(); // Check safeties (Not on serial link reciever board)
#endif #endif
@ -401,11 +401,11 @@ void init_stored_settings() {
} }
temp = settings.getUInt("MAXCHARGEAMP", false); temp = settings.getUInt("MAXCHARGEAMP", false);
if (temp != 0) { if (temp != 0) {
datalayer.battery.info.max_charge_amp_dA = temp; datalayer.battery.settings.max_user_set_charge_dA = temp;
} }
temp = settings.getUInt("MAXDISCHARGEAMP", false); temp = settings.getUInt("MAXDISCHARGEAMP", false);
if (temp != 0) { if (temp != 0) {
datalayer.battery.info.max_discharge_amp_dA = temp; datalayer.battery.settings.max_user_set_discharge_dA = temp;
temp = settings.getBool("USE_SCALED_SOC", false); temp = settings.getBool("USE_SCALED_SOC", false);
datalayer.battery.settings.soc_scaling_active = temp; //This bool needs to be checked inside the temp!= block datalayer.battery.settings.soc_scaling_active = temp; //This bool needs to be checked inside the temp!= block
} // No way to know if it wasnt reset otherwise } // No way to know if it wasnt reset otherwise
@ -837,7 +837,23 @@ void handle_contactors() {
#endif // CONTACTOR_CONTROL #endif // CONTACTOR_CONTROL
} }
void update_scaled_values() { void update_calculated_values() {
/* Calculate allowed charge/discharge currents*/
if (datalayer.battery.status.voltage_dV > 10) {
// Only update value when we have voltage available to avoid div0. TODO: This should be based on nominal voltage
datalayer.battery.status.max_charge_current_dA =
((datalayer.battery.status.max_charge_power_W * 100) / datalayer.battery.status.voltage_dV);
datalayer.battery.status.max_discharge_current_dA =
((datalayer.battery.status.max_discharge_power_W * 100) / datalayer.battery.status.voltage_dV);
}
/* Restrict values from user settings if needed*/
if (datalayer.battery.status.max_charge_current_dA > datalayer.battery.settings.max_user_set_charge_dA) {
datalayer.battery.status.max_charge_current_dA = datalayer.battery.settings.max_user_set_charge_dA;
}
if (datalayer.battery.status.max_discharge_current_dA > datalayer.battery.settings.max_user_set_discharge_dA) {
datalayer.battery.status.max_discharge_current_dA = datalayer.battery.settings.max_user_set_discharge_dA;
}
if (datalayer.battery.settings.soc_scaling_active) { if (datalayer.battery.settings.soc_scaling_active) {
/** SOC Scaling /** SOC Scaling
* *
@ -896,7 +912,7 @@ void update_scaled_values() {
} }
#endif #endif
} else { // No SOC window wanted. Set scaled to same as real. } else { // soc_scaling_active == false. No SOC window wanted. Set scaled to same as real.
datalayer.battery.status.reported_soc = datalayer.battery.status.real_soc; datalayer.battery.status.reported_soc = datalayer.battery.status.real_soc;
datalayer.battery.status.reported_remaining_capacity_Wh = datalayer.battery.status.remaining_capacity_Wh; datalayer.battery.status.reported_remaining_capacity_Wh = datalayer.battery.status.remaining_capacity_Wh;
#ifdef DOUBLE_BATTERY #ifdef DOUBLE_BATTERY
@ -975,8 +991,8 @@ void storeSettings() {
datalayer.battery.settings.max_percentage / 10); // Divide by 10 for backwards compatibility datalayer.battery.settings.max_percentage / 10); // Divide by 10 for backwards compatibility
settings.putUInt("MINPERCENTAGE", settings.putUInt("MINPERCENTAGE",
datalayer.battery.settings.min_percentage / 10); // Divide by 10 for backwards compatibility datalayer.battery.settings.min_percentage / 10); // Divide by 10 for backwards compatibility
settings.putUInt("MAXCHARGEAMP", datalayer.battery.info.max_charge_amp_dA); settings.putUInt("MAXCHARGEAMP", datalayer.battery.settings.max_user_set_charge_dA);
settings.putUInt("MAXDISCHARGEAMP", datalayer.battery.info.max_discharge_amp_dA); settings.putUInt("MAXDISCHARGEAMP", datalayer.battery.settings.max_user_set_discharge_dA);
settings.putBool("USE_SCALED_SOC", datalayer.battery.settings.soc_scaling_active); settings.putBool("USE_SCALED_SOC", datalayer.battery.settings.soc_scaling_active);
settings.end(); settings.end();
} }

View file

@ -19,10 +19,6 @@ typedef struct {
uint16_t min_cell_voltage_mV = 2700; uint16_t min_cell_voltage_mV = 2700;
/** The maxumum allowed deviation between cells, in milliVolt. 500 = 0.500 V */ /** The maxumum allowed deviation between cells, in milliVolt. 500 = 0.500 V */
uint16_t max_cell_voltage_deviation_mV = 500; uint16_t max_cell_voltage_deviation_mV = 500;
/** BYD CAN specific setting, max charge in deciAmpere. 300 = 30.0 A */
uint16_t max_charge_amp_dA = BATTERY_MAX_CHARGE_AMP;
/** BYD CAN specific setting, max discharge in deciAmpere. 300 = 30.0 A */
uint16_t max_discharge_amp_dA = BATTERY_MAX_DISCHARGE_AMP;
/** uint8_t */ /** uint8_t */
/** Total number of cells in the pack */ /** Total number of cells in the pack */
@ -49,10 +45,14 @@ typedef struct {
*/ */
uint32_t reported_remaining_capacity_Wh; uint32_t reported_remaining_capacity_Wh;
/** Maximum allowed battery discharge power in Watts */ /** Maximum allowed battery discharge power in Watts. Set by battery */
uint32_t max_discharge_power_W = 0; uint32_t max_discharge_power_W = 0;
/** Maximum allowed battery charge power in Watts */ /** Maximum allowed battery charge power in Watts. Set by battery */
uint32_t max_charge_power_W = 0; uint32_t max_charge_power_W = 0;
/** Maximum allowed battery discharge current in dA. Calculated based on allowed W and Voltage */
uint16_t max_discharge_current_dA = 0;
/** Maximum allowed battery charge current in dA. Calculated based on allowed W and Voltage */
uint16_t max_charge_current_dA = 0;
/** int16_t */ /** int16_t */
/** Maximum temperature currently measured in the pack, in d°C. 150 = 15.0 °C */ /** Maximum temperature currently measured in the pack, in d°C. 150 = 15.0 °C */
@ -107,6 +107,10 @@ typedef struct {
* you want the inverter to be able to use. At this real SOC, the inverter * you want the inverter to be able to use. At this real SOC, the inverter
* will "see" 100% */ * will "see" 100% */
uint16_t max_percentage = BATTERY_MAXPERCENTAGE; uint16_t max_percentage = BATTERY_MAXPERCENTAGE;
/** The user specified maximum allowed charge rate, in deciAmpere. 300 = 30.0 A */
uint16_t max_user_set_charge_dA = BATTERY_MAX_CHARGE_AMP;
/** The user specified maximum allowed discharge rate, in deciAmpere. 300 = 30.0 A */
uint16_t max_user_set_discharge_dA = BATTERY_MAX_DISCHARGE_AMP;
} DATALAYER_BATTERY_SETTINGS_TYPE; } DATALAYER_BATTERY_SETTINGS_TYPE;
typedef struct { typedef struct {

View file

@ -229,6 +229,14 @@ void update_machineryprotection() {
} }
#endif // DOUBLE_BATTERY #endif // DOUBLE_BATTERY
//Safeties verified, Zero charge/discharge ampere values incase any safety wrote the W to 0
if (datalayer.battery.status.max_discharge_power_W == 0) {
datalayer.battery.status.max_discharge_current_dA = 0;
}
if (datalayer.battery.status.max_charge_power_W == 0) {
datalayer.battery.status.max_charge_current_dA = 0;
}
} }
//battery pause status begin //battery pause status begin

View file

@ -57,11 +57,11 @@ String settings_processor(const String& var) {
content += "<h4 style='color: " + String(datalayer.battery.settings.soc_scaling_active ? "white" : "darkgrey") + content += "<h4 style='color: " + String(datalayer.battery.settings.soc_scaling_active ? "white" : "darkgrey") +
";'>SOC min percentage: " + String(datalayer.battery.settings.min_percentage / 100.0, 1) + ";'>SOC min percentage: " + String(datalayer.battery.settings.min_percentage / 100.0, 1) +
" </span> <button onclick='editSocMin()'>Edit</button></h4>"; " </span> <button onclick='editSocMin()'>Edit</button></h4>";
content += content += "<h4 style='color: white;'>Max charge speed: " +
"<h4 style='color: white;'>Max charge speed: " + String(datalayer.battery.info.max_charge_amp_dA / 10.0, 1) + String(datalayer.battery.settings.max_user_set_charge_dA / 10.0, 1) +
" A </span> <button onclick='editMaxChargeA()'>Edit</button></h4>"; " A </span> <button onclick='editMaxChargeA()'>Edit</button></h4>";
content += "<h4 style='color: white;'>Max discharge speed: " + content += "<h4 style='color: white;'>Max discharge speed: " +
String(datalayer.battery.info.max_discharge_amp_dA / 10.0, 1) + String(datalayer.battery.settings.max_user_set_discharge_dA / 10.0, 1) +
" A </span> <button onclick='editMaxDischargeA()'>Edit</button></h4>"; " A </span> <button onclick='editMaxDischargeA()'>Edit</button></h4>";
// Close the block // Close the block
content += "</div>"; content += "</div>";

View file

@ -209,7 +209,7 @@ void init_webserver() {
return request->requestAuthentication(); return request->requestAuthentication();
if (request->hasParam("value")) { if (request->hasParam("value")) {
String value = request->getParam("value")->value(); String value = request->getParam("value")->value();
datalayer.battery.info.max_charge_amp_dA = static_cast<uint16_t>(value.toFloat() * 10); datalayer.battery.settings.max_user_set_charge_dA = static_cast<uint16_t>(value.toFloat() * 10);
storeSettings(); storeSettings();
request->send(200, "text/plain", "Updated successfully"); request->send(200, "text/plain", "Updated successfully");
} else { } else {
@ -223,7 +223,7 @@ void init_webserver() {
return request->requestAuthentication(); return request->requestAuthentication();
if (request->hasParam("value")) { if (request->hasParam("value")) {
String value = request->getParam("value")->value(); String value = request->getParam("value")->value();
datalayer.battery.info.max_discharge_amp_dA = static_cast<uint16_t>(value.toFloat() * 10); datalayer.battery.settings.max_user_set_discharge_dA = static_cast<uint16_t>(value.toFloat() * 10);
storeSettings(); storeSettings();
request->send(200, "text/plain", "Updated successfully"); request->send(200, "text/plain", "Updated successfully");
} else { } else {
@ -285,7 +285,7 @@ void init_webserver() {
String value = request->getParam("value")->value(); String value = request->getParam("value")->value();
float val = value.toFloat(); float val = value.toFloat();
if (!(val <= datalayer.battery.info.max_charge_amp_dA && val <= CHARGER_MAX_A)) { if (!(val <= datalayer.battery.settings.max_user_set_charge_dA && val <= CHARGER_MAX_A)) {
request->send(400, "text/plain", "Bad Request"); request->send(400, "text/plain", "Bad Request");
} }
@ -655,6 +655,10 @@ String processor(const String& var) {
float powerFloat = static_cast<float>(datalayer.battery.status.active_power_W); // Convert to float float powerFloat = static_cast<float>(datalayer.battery.status.active_power_W); // Convert to float
float tempMaxFloat = static_cast<float>(datalayer.battery.status.temperature_max_dC) / 10.0; // Convert to float float tempMaxFloat = static_cast<float>(datalayer.battery.status.temperature_max_dC) / 10.0; // Convert to float
float tempMinFloat = static_cast<float>(datalayer.battery.status.temperature_min_dC) / 10.0; // Convert to float float tempMinFloat = static_cast<float>(datalayer.battery.status.temperature_min_dC) / 10.0; // Convert to float
float maxCurrentChargeFloat =
static_cast<float>(datalayer.battery.status.max_charge_current_dA) / 10.0; // Convert to float
float maxCurrentDischargeFloat =
static_cast<float>(datalayer.battery.status.max_discharge_current_dA) / 10.0; // Convert to float
uint16_t cell_delta_mv = uint16_t cell_delta_mv =
datalayer.battery.status.cell_max_voltage_mV - datalayer.battery.status.cell_min_voltage_mV; datalayer.battery.status.cell_max_voltage_mV - datalayer.battery.status.cell_min_voltage_mV;
@ -672,9 +676,13 @@ String processor(const String& var) {
if (emulator_pause_status == NORMAL) { if (emulator_pause_status == NORMAL) {
content += formatPowerValue("Max discharge power", datalayer.battery.status.max_discharge_power_W, "", 1); content += formatPowerValue("Max discharge power", datalayer.battery.status.max_discharge_power_W, "", 1);
content += formatPowerValue("Max charge power", datalayer.battery.status.max_charge_power_W, "", 1); content += formatPowerValue("Max charge power", datalayer.battery.status.max_charge_power_W, "", 1);
content += "<h4 style='color: white;'>Max discharge current: " + String(maxCurrentDischargeFloat, 1) + " A</h4>";
content += "<h4 style='color: white;'>Max charge current: " + String(maxCurrentChargeFloat, 1) + " A</h4>";
} else { } else {
content += formatPowerValue("Max discharge power", datalayer.battery.status.max_discharge_power_W, "", 1, "red"); content += formatPowerValue("Max discharge power", datalayer.battery.status.max_discharge_power_W, "", 1, "red");
content += formatPowerValue("Max charge power", datalayer.battery.status.max_charge_power_W, "", 1, "red"); content += formatPowerValue("Max charge power", datalayer.battery.status.max_charge_power_W, "", 1, "red");
content += "<h4 style='color: red;'>Max discharge current: " + String(maxCurrentDischargeFloat, 1) + " A</h4>";
content += "<h4 style='color: red;'>Max charge current: " + String(maxCurrentChargeFloat, 1) + " A</h4>";
} }
content += "<h4>Cell max: " + String(datalayer.battery.status.cell_max_voltage_mV) + " mV</h4>"; content += "<h4>Cell max: " + String(datalayer.battery.status.cell_max_voltage_mV) + " mV</h4>";

View file

@ -72,31 +72,10 @@ CAN_frame AFORE_35A = {.FD = false,
.DLC = 8, .DLC = 8,
.ID = 0x35A, .ID = 0x35A,
.data = {0x65, 0x6D, 0x75, 0x6C, 0x61, 0x74, 0x6F, 0x72}}; // Emulator .data = {0x65, 0x6D, 0x75, 0x6C, 0x61, 0x74, 0x6F, 0x72}}; // Emulator
static int16_t max_charge_current_dA = 0;
static int16_t max_discharge_current_dA = 0;
void update_values_can_inverter() { //This function maps all the values fetched from battery CAN to the correct CAN messages void update_values_can_inverter() { //This function maps all the values fetched from battery CAN to the correct CAN messages
//There are more mappings that could be added, but this should be enough to use as a starting point //There are more mappings that could be added, but this should be enough to use as a starting point
// Note we map both 0 and 1 messages
if (datalayer.battery.status.voltage_dV > 10) { //div0 safeguard
max_charge_current_dA = (datalayer.battery.status.max_charge_power_W * 100) / datalayer.battery.status.voltage_dV;
if (max_charge_current_dA > datalayer.battery.info.max_charge_amp_dA) {
max_charge_current_dA =
datalayer.battery.info
.max_charge_amp_dA; //Cap the value to the max allowed Amp. Some inverters cannot handle large values.
}
max_discharge_current_dA =
(datalayer.battery.status.max_discharge_power_W * 100) / datalayer.battery.status.voltage_dV;
if (max_discharge_current_dA > datalayer.battery.info.max_discharge_amp_dA) {
max_discharge_current_dA =
datalayer.battery.info
.max_discharge_amp_dA; //Cap the value to the max allowed Amp. Some inverters cannot handle large values.
}
} else {
max_charge_current_dA = 0;
max_discharge_current_dA = 0;
}
/*0x350 Operation Information*/ /*0x350 Operation Information*/
AFORE_350.data.u8[0] = (datalayer.battery.status.voltage_dV & 0x00FF); AFORE_350.data.u8[0] = (datalayer.battery.status.voltage_dV & 0x00FF);
AFORE_350.data.u8[1] = (datalayer.battery.status.voltage_dV >> 8); AFORE_350.data.u8[1] = (datalayer.battery.status.voltage_dV >> 8);
@ -115,11 +94,11 @@ void update_values_can_inverter() { //This function maps all the values fetched
AFORE_351.data.u8[2] = SOCMAX; AFORE_351.data.u8[2] = SOCMAX;
AFORE_351.data.u8[3] = SOCMIN; AFORE_351.data.u8[3] = SOCMIN;
AFORE_351.data.u8[4] = 0x03; //Bit0 and Bit1 set AFORE_351.data.u8[4] = 0x03; //Bit0 and Bit1 set
if ((max_charge_current_dA == 0) || (datalayer.battery.status.reported_soc == 10000) || if ((datalayer.battery.status.max_charge_current_dA == 0) || (datalayer.battery.status.reported_soc == 10000) ||
(datalayer.battery.status.bms_status == FAULT)) { (datalayer.battery.status.bms_status == FAULT)) {
AFORE_351.data.u8[4] &= ~0x01; // Remove Bit0 (clear) Charge enable flag AFORE_351.data.u8[4] &= ~0x01; // Remove Bit0 (clear) Charge enable flag
} }
if ((max_discharge_current_dA == 0) || (datalayer.battery.status.reported_soc == 0) || if ((datalayer.battery.status.max_discharge_current_dA == 0) || (datalayer.battery.status.reported_soc == 0) ||
(datalayer.battery.status.bms_status == FAULT)) { (datalayer.battery.status.bms_status == FAULT)) {
AFORE_351.data.u8[4] &= ~0x02; // Remove Bit1 (clear) Discharge enable flag AFORE_351.data.u8[4] &= ~0x02; // Remove Bit1 (clear) Discharge enable flag
} }
@ -135,10 +114,10 @@ void update_values_can_inverter() { //This function maps all the values fetched
AFORE_351.data.u8[7] = (datalayer.battery.info.number_of_cells >> 8); AFORE_351.data.u8[7] = (datalayer.battery.info.number_of_cells >> 8);
/*0x352 - Protection parameters*/ /*0x352 - Protection parameters*/
AFORE_352.data.u8[0] = (max_charge_current_dA & 0x00FF); AFORE_352.data.u8[0] = (datalayer.battery.status.max_charge_current_dA & 0x00FF);
AFORE_352.data.u8[1] = (max_charge_current_dA >> 8); AFORE_352.data.u8[1] = (datalayer.battery.status.max_charge_current_dA >> 8);
AFORE_352.data.u8[2] = (max_discharge_current_dA & 0x00FF); AFORE_352.data.u8[2] = (datalayer.battery.status.max_discharge_current_dA & 0x00FF);
AFORE_352.data.u8[3] = (max_discharge_current_dA >> 8); AFORE_352.data.u8[3] = (datalayer.battery.status.max_discharge_current_dA >> 8);
AFORE_352.data.u8[4] = (datalayer.battery.info.max_design_voltage_dV & 0x00FF); AFORE_352.data.u8[4] = (datalayer.battery.info.max_design_voltage_dV & 0x00FF);
AFORE_352.data.u8[5] = (datalayer.battery.info.max_design_voltage_dV >> 8); AFORE_352.data.u8[5] = (datalayer.battery.info.max_design_voltage_dV >> 8);
AFORE_352.data.u8[6] = (datalayer.battery.info.min_design_voltage_dV & 0x00FF); AFORE_352.data.u8[6] = (datalayer.battery.info.min_design_voltage_dV & 0x00FF);

View file

@ -79,8 +79,6 @@ CAN_frame BYD_210 = {.FD = false,
.ID = 0x210, .ID = 0x210,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
static uint16_t discharge_current = 0;
static uint16_t charge_current = 0;
static int16_t temperature_average = 0; static int16_t temperature_average = 0;
static uint16_t inverter_voltage = 0; static uint16_t inverter_voltage = 0;
static uint16_t inverter_SOC = 0; static uint16_t inverter_SOC = 0;
@ -91,32 +89,6 @@ static bool initialDataSent = 0;
void update_values_can_inverter() { //This function maps all the values fetched from battery CAN to the correct CAN messages void update_values_can_inverter() { //This function maps all the values fetched from battery CAN to the correct CAN messages
/* Calculate allowed charge/discharge currents*/
if (datalayer.battery.status.voltage_dV > 10) { // Only update value when we have voltage available to avoid div0
charge_current =
((datalayer.battery.status.max_charge_power_W * 10) /
datalayer.battery.status.voltage_dV); //Charge power in W , max volt in V+1decimal (P=UI, solve for I)
//The above calculation results in (30 000*10)/3700=81A
charge_current = (charge_current * 10); //Value needs a decimal before getting sent to inverter (81.0A)
discharge_current =
((datalayer.battery.status.max_discharge_power_W * 10) /
datalayer.battery.status.voltage_dV); //Charge power in W , max volt in V+1decimal (P=UI, solve for I)
//The above calculation results in (30 000*10)/3700=81A
discharge_current = (discharge_current * 10); //Value needs a decimal before getting sent to inverter (81.0A)
}
/* Restrict values from user settings if needed*/
if (charge_current > datalayer.battery.info.max_charge_amp_dA) {
charge_current =
datalayer.battery.info
.max_charge_amp_dA; //Cap the value to the max allowed Amp. Some inverters cannot handle large values.
}
if (discharge_current > datalayer.battery.info.max_discharge_amp_dA) {
discharge_current =
datalayer.battery.info
.max_discharge_amp_dA; //Cap the value to the max allowed Amp. Some inverters cannot handle large values.
}
/* Calculate temperature */ /* Calculate temperature */
temperature_average = temperature_average =
((datalayer.battery.status.temperature_max_dC + datalayer.battery.status.temperature_min_dC) / 2); ((datalayer.battery.status.temperature_max_dC + datalayer.battery.status.temperature_min_dC) / 2);
@ -137,11 +109,11 @@ void update_values_can_inverter() { //This function maps all the values fetched
BYD_110.data.u8[2] = (datalayer.battery.info.min_design_voltage_dV >> 8); BYD_110.data.u8[2] = (datalayer.battery.info.min_design_voltage_dV >> 8);
BYD_110.data.u8[3] = (datalayer.battery.info.min_design_voltage_dV & 0x00FF); BYD_110.data.u8[3] = (datalayer.battery.info.min_design_voltage_dV & 0x00FF);
//Maximum discharge power allowed (Unit: A+1) //Maximum discharge power allowed (Unit: A+1)
BYD_110.data.u8[4] = (discharge_current >> 8); BYD_110.data.u8[4] = (datalayer.battery.status.max_discharge_current_dA >> 8);
BYD_110.data.u8[5] = (discharge_current & 0x00FF); BYD_110.data.u8[5] = (datalayer.battery.status.max_discharge_current_dA & 0x00FF);
//Maximum charge power allowed (Unit: A+1) //Maximum charge power allowed (Unit: A+1)
BYD_110.data.u8[6] = (charge_current >> 8); BYD_110.data.u8[6] = (datalayer.battery.status.max_charge_current_dA >> 8);
BYD_110.data.u8[7] = (charge_current & 0x00FF); BYD_110.data.u8[7] = (datalayer.battery.status.max_charge_current_dA & 0x00FF);
//SOC (100.00%) //SOC (100.00%)
BYD_150.data.u8[0] = (datalayer.battery.status.reported_soc >> 8); BYD_150.data.u8[0] = (datalayer.battery.status.reported_soc >> 8);

View file

@ -65,13 +65,13 @@ void handle_update_data_modbusp301_byd() {
} }
// Convert max discharge Amp value to max Watt // Convert max discharge Amp value to max Watt
user_configured_max_discharge_W = user_configured_max_discharge_W =
((datalayer.battery.info.max_discharge_amp_dA * datalayer.battery.info.max_design_voltage_dV) / 100); ((datalayer.battery.settings.max_user_set_discharge_dA * datalayer.battery.info.max_design_voltage_dV) / 100);
// Use the smaller value, battery reported value OR user configured value // Use the smaller value, battery reported value OR user configured value
max_discharge_W = std::min(datalayer.battery.status.max_discharge_power_W, user_configured_max_discharge_W); max_discharge_W = std::min(datalayer.battery.status.max_discharge_power_W, user_configured_max_discharge_W);
// Convert max charge Amp value to max Watt // Convert max charge Amp value to max Watt
user_configured_max_charge_W = user_configured_max_charge_W =
((datalayer.battery.info.max_charge_amp_dA * datalayer.battery.info.max_design_voltage_dV) / 100); ((datalayer.battery.settings.max_user_set_charge_dA * datalayer.battery.info.max_design_voltage_dV) / 100);
// Use the smaller value, battery reported value OR user configured value // Use the smaller value, battery reported value OR user configured value
max_charge_W = std::min(datalayer.battery.status.max_charge_power_W, user_configured_max_charge_W); max_charge_W = std::min(datalayer.battery.status.max_charge_power_W, user_configured_max_charge_W);

View file

@ -80,30 +80,6 @@ static uint16_t ampere_hours_remaining = 0;
void update_values_can_inverter() { //This function maps all the values fetched from battery CAN to the correct CAN messages void update_values_can_inverter() { //This function maps all the values fetched from battery CAN to the correct CAN messages
//Calculate values //Calculate values
if (datalayer.battery.status.voltage_dV > 10) { // Only update value when we have voltage available to avoid div0
discharge_current =
((datalayer.battery.status.max_discharge_power_W * 10) /
datalayer.battery.status.voltage_dV); //Charge power in W , max volt in V+1decimal (P=UI, solve for I)
discharge_current = (discharge_current * 10); //Value needs a decimal before getting sent to inverter (81.0A)
charge_current =
((datalayer.battery.status.max_charge_power_W * 10) /
datalayer.battery.status.voltage_dV); //Charge power in W , max volt in V+1decimal (P=UI, solve for I)
charge_current = (charge_current * 10); //Value needs a decimal before getting sent to inverter (81.0A)
}
if (charge_current > datalayer.battery.info.max_charge_amp_dA) {
charge_current =
datalayer.battery.info
.max_charge_amp_dA; //Cap the value to the max allowed Amp. Some inverters cannot handle large values.
}
if (discharge_current > datalayer.battery.info.max_discharge_amp_dA) {
discharge_current =
datalayer.battery.info
.max_discharge_amp_dA; //Cap the value to the max allowed Amp. Some inverters cannot handle large values.
}
temperature_average = temperature_average =
((datalayer.battery.status.temperature_max_dC + datalayer.battery.status.temperature_min_dC) / 2); ((datalayer.battery.status.temperature_max_dC + datalayer.battery.status.temperature_min_dC) / 2);
@ -118,15 +94,14 @@ void update_values_can_inverter() { //This function maps all the values fetched
SMA_358.data.u8[0] = (datalayer.battery.info.max_design_voltage_dV >> 8); SMA_358.data.u8[0] = (datalayer.battery.info.max_design_voltage_dV >> 8);
SMA_358.data.u8[1] = (datalayer.battery.info.max_design_voltage_dV & 0x00FF); SMA_358.data.u8[1] = (datalayer.battery.info.max_design_voltage_dV & 0x00FF);
//Minvoltage (eg 300.0V = 3000 , 16bits long) //Minvoltage (eg 300.0V = 3000 , 16bits long)
SMA_358.data.u8[2] = (datalayer.battery.info.min_design_voltage_dV >> SMA_358.data.u8[2] = (datalayer.battery.info.min_design_voltage_dV >> 8);
8); //Minvoltage behaves strange on SMA, cuts out at 56% of the set value?
SMA_358.data.u8[3] = (datalayer.battery.info.min_design_voltage_dV & 0x00FF); SMA_358.data.u8[3] = (datalayer.battery.info.min_design_voltage_dV & 0x00FF);
//Discharge limited current, 500 = 50A, (0.1, A) //Discharge limited current, 500 = 50A, (0.1, A)
SMA_358.data.u8[4] = (discharge_current >> 8); SMA_358.data.u8[4] = (datalayer.battery.status.max_discharge_current_dA >> 8);
SMA_358.data.u8[5] = (discharge_current & 0x00FF); SMA_358.data.u8[5] = (datalayer.battery.status.max_discharge_current_dA & 0x00FF);
//Charge limited current, 125 =12.5A (0.1, A) //Charge limited current, 125 =12.5A (0.1, A)
SMA_358.data.u8[6] = (charge_current >> 8); SMA_358.data.u8[6] = (datalayer.battery.status.max_charge_current_dA >> 8);
SMA_358.data.u8[7] = (charge_current & 0x00FF); SMA_358.data.u8[7] = (datalayer.battery.status.max_charge_current_dA & 0x00FF);
//SOC (100.00%) //SOC (100.00%)
SMA_3D8.data.u8[0] = (datalayer.battery.status.reported_soc >> 8); SMA_3D8.data.u8[0] = (datalayer.battery.status.reported_soc >> 8);

View file

@ -22,8 +22,6 @@ below that you can customize, incase you use a lower voltage battery with this p
#define TOTAL_LIFETIME_WH_ACCUMULATED 0 //We dont have this value in the emulator #define TOTAL_LIFETIME_WH_ACCUMULATED 0 //We dont have this value in the emulator
/* Do not change code below unless you are sure what you are doing */ /* Do not change code below unless you are sure what you are doing */
static uint16_t max_charge_rate_amp = 0;
static uint16_t max_discharge_rate_amp = 0;
static int16_t temperature_average = 0; static int16_t temperature_average = 0;
static uint16_t voltage_per_pack = 0; static uint16_t voltage_per_pack = 0;
static int16_t current_per_pack = 0; static int16_t current_per_pack = 0;
@ -364,50 +362,6 @@ void update_values_can_inverter() { //This function maps all the CAN values fet
temperature_average = temperature_average =
((datalayer.battery.status.temperature_max_dC + datalayer.battery.status.temperature_min_dC) / 2); ((datalayer.battery.status.temperature_max_dC + datalayer.battery.status.temperature_min_dC) / 2);
//datalayer.battery.status.max_charge_power_W (30000W max)
if (datalayer.battery.status.reported_soc > 9999) { // 99.99%
// Additional safety incase SOC% is 100, then do not charge battery further
max_charge_rate_amp = 0;
} else { // We can pass on the battery charge rate (in W) to the inverter (that takes A)
if (datalayer.battery.status.max_charge_power_W >= 30000) {
max_charge_rate_amp = 75; // Incase battery can take over 30kW, cap value to 75A
} else { // Calculate the W value into A
if (datalayer.battery.status.voltage_dV > 10) {
max_charge_rate_amp =
datalayer.battery.status.max_charge_power_W / (datalayer.battery.status.voltage_dV * 0.1); // P/U=I
} else { // We avoid dividing by 0 and crashing the board
// If we have no voltage, something has gone wrong, do not allow charging
max_charge_rate_amp = 0;
}
}
}
//datalayer.battery.status.max_discharge_power_W (30000W max)
if (datalayer.battery.status.reported_soc < 100) { // 1.00%
// Additional safety in case SOC% is below 1, then do not discharge battery further
max_discharge_rate_amp = 0;
} else { // We can pass on the battery discharge rate to the inverter
if (datalayer.battery.status.max_discharge_power_W >= 30000) {
max_discharge_rate_amp = 75; // Incase battery can be charged with over 30kW, cap value to 75A
} else { // Calculate the W value into A
if (datalayer.battery.status.voltage_dV > 10) {
max_discharge_rate_amp =
datalayer.battery.status.max_discharge_power_W / (datalayer.battery.status.voltage_dV * 0.1); // P/U=I
} else { // We avoid dividing by 0 and crashing the board
// If we have no voltage, something has gone wrong, do not allow discharging
max_discharge_rate_amp = 0;
}
}
}
//Cap the value according to user settings. Some inverters cannot handle large values.
if ((max_charge_rate_amp * 10) > datalayer.battery.info.max_charge_amp_dA) {
max_charge_rate_amp = (datalayer.battery.info.max_charge_amp_dA / 10);
}
if ((max_discharge_rate_amp * 10) > datalayer.battery.info.max_discharge_amp_dA) {
max_discharge_rate_amp = (datalayer.battery.info.max_discharge_amp_dA / 10);
}
if (inverterStillAlive > 0) { if (inverterStillAlive > 0) {
inverterStillAlive--; inverterStillAlive--;
} }
@ -424,10 +378,10 @@ void update_values_can_inverter() { //This function maps all the CAN values fet
FOXESS_1872.data.u8[1] = (datalayer.battery.info.max_design_voltage_dV >> 8); FOXESS_1872.data.u8[1] = (datalayer.battery.info.max_design_voltage_dV >> 8);
FOXESS_1872.data.u8[2] = (uint8_t)datalayer.battery.info.min_design_voltage_dV; FOXESS_1872.data.u8[2] = (uint8_t)datalayer.battery.info.min_design_voltage_dV;
FOXESS_1872.data.u8[3] = (datalayer.battery.info.min_design_voltage_dV >> 8); FOXESS_1872.data.u8[3] = (datalayer.battery.info.min_design_voltage_dV >> 8);
FOXESS_1872.data.u8[4] = (uint8_t)(max_charge_rate_amp * 10); FOXESS_1872.data.u8[4] = (uint8_t)datalayer.battery.status.max_charge_current_dA;
FOXESS_1872.data.u8[5] = ((max_charge_rate_amp * 10) >> 8); FOXESS_1872.data.u8[5] = (datalayer.battery.status.max_charge_current_dA >> 8);
FOXESS_1872.data.u8[6] = (uint8_t)(max_discharge_rate_amp * 10); FOXESS_1872.data.u8[6] = (uint8_t)datalayer.battery.status.max_discharge_current_dA;
FOXESS_1872.data.u8[7] = ((max_discharge_rate_amp * 10) >> 8); FOXESS_1872.data.u8[7] = (datalayer.battery.status.max_discharge_current_dA >> 8);
//BMS_PackData //BMS_PackData
FOXESS_1873.data.u8[0] = (uint8_t)datalayer.battery.status.voltage_dV; // OK FOXESS_1873.data.u8[0] = (uint8_t)datalayer.battery.status.voltage_dV; // OK
@ -463,7 +417,7 @@ void update_values_can_inverter() { //This function maps all the CAN values fet
// 0x1876 b0 bit 0 appears to be 1 when at maxsoc and BMS says charge is not allowed - // 0x1876 b0 bit 0 appears to be 1 when at maxsoc and BMS says charge is not allowed -
// when at 0 indicates charge is possible - additional note there is something more to it than this, // when at 0 indicates charge is possible - additional note there is something more to it than this,
// it's not as straight forward - needs more testing to find what sets/unsets bit0 of byte0 // it's not as straight forward - needs more testing to find what sets/unsets bit0 of byte0
if ((max_charge_rate_amp == 0) || (datalayer.battery.status.reported_soc == 10000) || if ((datalayer.battery.status.max_charge_current_dA == 0) || (datalayer.battery.status.reported_soc == 10000) ||
(datalayer.battery.status.bms_status == FAULT)) { (datalayer.battery.status.bms_status == FAULT)) {
FOXESS_1876.data.u8[0] = 0x01; FOXESS_1876.data.u8[0] = 0x01;
} else { //continue using battery } else { //continue using battery

View file

@ -134,32 +134,10 @@ CAN_frame PYLON_4291 = {.FD = false,
.ID = 0x4291, .ID = 0x4291,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
static int16_t max_charge_current = 0;
static int16_t max_discharge_current = 0;
void update_values_can_inverter() { //This function maps all the values fetched from battery CAN to the correct CAN messages void update_values_can_inverter() { //This function maps all the values fetched from battery CAN to the correct CAN messages
//There are more mappings that could be added, but this should be enough to use as a starting point //There are more mappings that could be added, but this should be enough to use as a starting point
// Note we map both 0 and 1 messages // Note we map both 0 and 1 messages
if (datalayer.battery.status.voltage_dV > 10) { //div0 safeguard
max_charge_current = (datalayer.battery.status.max_charge_power_W * 100) / datalayer.battery.status.voltage_dV;
if (max_charge_current > datalayer.battery.info.max_charge_amp_dA) {
max_charge_current =
datalayer.battery.info
.max_charge_amp_dA; //Cap the value to the max allowed Amp. Some inverters cannot handle large values.
}
max_discharge_current =
(datalayer.battery.status.max_discharge_power_W * 100) / datalayer.battery.status.voltage_dV;
if (max_discharge_current > datalayer.battery.info.max_discharge_amp_dA) {
max_discharge_current =
datalayer.battery.info
.max_discharge_amp_dA; //Cap the value to the max allowed Amp. Some inverters cannot handle large values.
}
} else {
max_charge_current = 0;
max_discharge_current = 0;
}
//Charge / Discharge allowed //Charge / Discharge allowed
PYLON_4280.data.u8[0] = 0; PYLON_4280.data.u8[0] = 0;
PYLON_4280.data.u8[1] = 0; PYLON_4280.data.u8[1] = 0;
@ -253,28 +231,28 @@ void update_values_can_inverter() { //This function maps all the values fetched
#ifdef SET_30K_OFFSET #ifdef SET_30K_OFFSET
//Max ChargeCurrent //Max ChargeCurrent
PYLON_4220.data.u8[4] = ((max_charge_current + 30000) & 0x00FF); PYLON_4220.data.u8[4] = ((datalayer.battery.status.max_charge_current_dA + 30000) & 0x00FF);
PYLON_4220.data.u8[5] = ((max_charge_current + 30000) >> 8); PYLON_4220.data.u8[5] = ((datalayer.battery.status.max_charge_current_dA + 30000) >> 8);
PYLON_4221.data.u8[4] = ((max_charge_current + 30000) & 0x00FF); PYLON_4221.data.u8[4] = ((datalayer.battery.status.max_charge_current_dA + 30000) & 0x00FF);
PYLON_4221.data.u8[5] = ((max_charge_current + 30000) >> 8); PYLON_4221.data.u8[5] = ((datalayer.battery.status.max_charge_current_dA + 30000) >> 8);
//Max DischargeCurrent //Max DischargeCurrent
PYLON_4220.data.u8[6] = ((30000 - max_discharge_current) & 0x00FF); PYLON_4220.data.u8[6] = ((30000 - datalayer.battery.status.max_discharge_current_dA) & 0x00FF);
PYLON_4220.data.u8[7] = ((30000 - max_discharge_current) >> 8); PYLON_4220.data.u8[7] = ((30000 - datalayer.battery.status.max_discharge_current_dA) >> 8);
PYLON_4221.data.u8[6] = ((30000 - max_discharge_current) & 0x00FF); PYLON_4221.data.u8[6] = ((30000 - datalayer.battery.status.max_discharge_current_dA) & 0x00FF);
PYLON_4221.data.u8[7] = ((30000 - max_discharge_current) >> 8); PYLON_4221.data.u8[7] = ((30000 - datalayer.battery.status.max_discharge_current_dA) >> 8);
#else #else
//Max ChargeCurrent //Max ChargeCurrent
PYLON_4220.data.u8[4] = (max_charge_current & 0x00FF); PYLON_4220.data.u8[4] = (datalayer.battery.status.max_charge_current_dA & 0x00FF);
PYLON_4220.data.u8[5] = (max_charge_current >> 8); PYLON_4220.data.u8[5] = (datalayer.battery.status.max_charge_current_dA >> 8);
PYLON_4221.data.u8[4] = (max_charge_current & 0x00FF); PYLON_4221.data.u8[4] = (datalayer.battery.status.max_charge_current_dA & 0x00FF);
PYLON_4221.data.u8[5] = (max_charge_current >> 8); PYLON_4221.data.u8[5] = (datalayer.battery.status.max_charge_current_dA >> 8);
//Max DishargeCurrent //Max DishargeCurrent
PYLON_4220.data.u8[6] = (max_discharge_current & 0x00FF); PYLON_4220.data.u8[6] = (datalayer.battery.status.max_discharge_current_dA & 0x00FF);
PYLON_4220.data.u8[7] = (max_discharge_current >> 8); PYLON_4220.data.u8[7] = (datalayer.battery.status.max_discharge_current_dA >> 8);
PYLON_4221.data.u8[6] = (max_discharge_current & 0x00FF); PYLON_4221.data.u8[6] = (datalayer.battery.status.max_discharge_current_dA & 0x00FF);
PYLON_4221.data.u8[7] = (max_discharge_current >> 8); PYLON_4221.data.u8[7] = (datalayer.battery.status.max_discharge_current_dA >> 8);
#endif #endif
//Max cell voltage //Max cell voltage

View file

@ -42,20 +42,13 @@ CAN_frame PYLON_35E = {.FD = false,
void update_values_can_inverter() { void update_values_can_inverter() {
// This function maps all the values fetched from battery CAN to the correct CAN messages // This function maps all the values fetched from battery CAN to the correct CAN messages
// do not update values unless we have some voltage, as we will run into IntegerDivideByZero exceptions otherwise
if (datalayer.battery.status.voltage_dV == 0)
return;
// TODO: officially this value is "battery charge voltage". Do we need to add something here to the actual voltage? // TODO: officially this value is "battery charge voltage". Do we need to add something here to the actual voltage?
PYLON_351.data.u8[0] = datalayer.battery.status.voltage_dV & 0xff; PYLON_351.data.u8[0] = datalayer.battery.status.voltage_dV & 0xff;
PYLON_351.data.u8[1] = datalayer.battery.status.voltage_dV >> 8; PYLON_351.data.u8[1] = datalayer.battery.status.voltage_dV >> 8;
int16_t maxChargeCurrent = datalayer.battery.status.max_charge_power_W * 100 / datalayer.battery.status.voltage_dV; PYLON_351.data.u8[2] = datalayer.battery.status.max_charge_current_dA & 0xff;
PYLON_351.data.u8[2] = maxChargeCurrent & 0xff; PYLON_351.data.u8[3] = datalayer.battery.status.max_charge_current_dA >> 8;
PYLON_351.data.u8[3] = maxChargeCurrent >> 8; PYLON_351.data.u8[4] = datalayer.battery.status.max_discharge_current_dA & 0xff;
int16_t maxDischargeCurrent = PYLON_351.data.u8[5] = datalayer.battery.status.max_discharge_current_dA >> 8;
datalayer.battery.status.max_discharge_power_W * 100 / datalayer.battery.status.voltage_dV;
PYLON_351.data.u8[4] = maxDischargeCurrent & 0xff;
PYLON_351.data.u8[5] = maxDischargeCurrent >> 8;
PYLON_355.data.u8[0] = (datalayer.battery.status.reported_soc / 10) & 0xff; PYLON_355.data.u8[0] = (datalayer.battery.status.reported_soc / 10) & 0xff;
PYLON_355.data.u8[1] = (datalayer.battery.status.reported_soc / 10) >> 8; PYLON_355.data.u8[1] = (datalayer.battery.status.reported_soc / 10) >> 8;
@ -75,11 +68,11 @@ void update_values_can_inverter() {
PYLON_359.data.u8[2] = 0x00; PYLON_359.data.u8[2] = 0x00;
PYLON_359.data.u8[3] = 0x00; PYLON_359.data.u8[3] = 0x00;
PYLON_359.data.u8[4] = PACK_NUMBER; PYLON_359.data.u8[4] = PACK_NUMBER;
PYLON_359.data.u8[5] = 'P'; PYLON_359.data.u8[5] = 0x50; //P
PYLON_359.data.u8[6] = 'N'; PYLON_359.data.u8[6] = 0x4E; //N
// ERRORS // ERRORS
if (datalayer.battery.status.current_dA >= maxDischargeCurrent) if (datalayer.battery.status.current_dA >= (datalayer.battery.status.max_discharge_current_dA + 10))
PYLON_359.data.u8[0] |= 0x80; PYLON_359.data.u8[0] |= 0x80;
if (datalayer.battery.status.temperature_min_dC <= BATTERY_MINTEMPERATURE) if (datalayer.battery.status.temperature_min_dC <= BATTERY_MINTEMPERATURE)
PYLON_359.data.u8[0] |= 0x10; PYLON_359.data.u8[0] |= 0x10;
@ -88,11 +81,11 @@ void update_values_can_inverter() {
if (datalayer.battery.status.voltage_dV * 100 <= datalayer.battery.info.min_cell_voltage_mV) if (datalayer.battery.status.voltage_dV * 100 <= datalayer.battery.info.min_cell_voltage_mV)
PYLON_359.data.u8[0] |= 0x04; PYLON_359.data.u8[0] |= 0x04;
// we never set PYLON_359.data.u8[1] |= 0x80 called "BMS internal" // we never set PYLON_359.data.u8[1] |= 0x80 called "BMS internal"
if (datalayer.battery.status.current_dA <= -1 * maxChargeCurrent) if (datalayer.battery.status.current_dA <= -1 * datalayer.battery.status.max_charge_current_dA)
PYLON_359.data.u8[1] |= 0x01; PYLON_359.data.u8[1] |= 0x01;
// WARNINGS (using same rules as errors but reporting earlier) // WARNINGS (using same rules as errors but reporting earlier)
if (datalayer.battery.status.current_dA >= maxDischargeCurrent * WARNINGS_PERCENT / 100) if (datalayer.battery.status.current_dA >= datalayer.battery.status.max_discharge_current_dA * WARNINGS_PERCENT / 100)
PYLON_359.data.u8[2] |= 0x80; PYLON_359.data.u8[2] |= 0x80;
if (datalayer.battery.status.temperature_min_dC <= BATTERY_MINTEMPERATURE * WARNINGS_PERCENT / 100) if (datalayer.battery.status.temperature_min_dC <= BATTERY_MINTEMPERATURE * WARNINGS_PERCENT / 100)
PYLON_359.data.u8[2] |= 0x10; PYLON_359.data.u8[2] |= 0x10;
@ -101,7 +94,8 @@ void update_values_can_inverter() {
if (datalayer.battery.status.voltage_dV * 100 <= datalayer.battery.info.min_cell_voltage_mV + 100) if (datalayer.battery.status.voltage_dV * 100 <= datalayer.battery.info.min_cell_voltage_mV + 100)
PYLON_359.data.u8[2] |= 0x04; PYLON_359.data.u8[2] |= 0x04;
// we never set PYLON_359.data.u8[3] |= 0x80 called "BMS internal" // we never set PYLON_359.data.u8[3] |= 0x80 called "BMS internal"
if (datalayer.battery.status.current_dA <= -1 * maxChargeCurrent * WARNINGS_PERCENT / 100) if (datalayer.battery.status.current_dA <=
-1 * datalayer.battery.status.max_charge_current_dA * WARNINGS_PERCENT / 100)
PYLON_359.data.u8[3] |= 0x01; PYLON_359.data.u8[3] |= 0x01;
PYLON_35C.data.u8[0] = 0xC0; // enable charging and discharging PYLON_35C.data.u8[0] = 0xC0; // enable charging and discharging

View file

@ -70,37 +70,12 @@ CAN_frame SMA_158 = {.FD = false,
.ID = 0x158, .ID = 0x158,
.data = {0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x6A, 0xAA, 0xAA}}; .data = {0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x6A, 0xAA, 0xAA}};
static int16_t discharge_current = 0;
static int16_t charge_current = 0;
static int16_t temperature_average = 0; static int16_t temperature_average = 0;
static uint16_t ampere_hours_remaining = 0; static uint16_t ampere_hours_remaining = 0;
void update_values_can_inverter() { //This function maps all the values fetched from battery CAN to the correct CAN messages void update_values_can_inverter() { //This function maps all the values fetched from battery CAN to the correct CAN messages
//Calculate values //Calculate values
if (datalayer.battery.status.voltage_dV > 10) { // Only update value when we have voltage available to avoid div0
discharge_current =
((datalayer.battery.status.max_discharge_power_W * 10) /
datalayer.battery.status.voltage_dV); //Charge power in W , max volt in V+1decimal (P=UI, solve for I)
discharge_current = (discharge_current * 10); //Value needs a decimal before getting sent to inverter (81.0A)
charge_current =
((datalayer.battery.status.max_charge_power_W * 10) /
datalayer.battery.status.voltage_dV); //Charge power in W , max volt in V+1decimal (P=UI, solve for I)
charge_current = (charge_current * 10); //Value needs a decimal before getting sent to inverter (81.0A)
}
if (charge_current > datalayer.battery.info.max_charge_amp_dA) {
charge_current =
datalayer.battery.info
.max_charge_amp_dA; //Cap the value to the max allowed Amp. Some inverters cannot handle large values.
}
if (discharge_current > datalayer.battery.info.max_discharge_amp_dA) {
discharge_current =
datalayer.battery.info
.max_discharge_amp_dA; //Cap the value to the max allowed Amp. Some inverters cannot handle large values.
}
temperature_average = temperature_average =
((datalayer.battery.status.temperature_max_dC + datalayer.battery.status.temperature_min_dC) / 2); ((datalayer.battery.status.temperature_max_dC + datalayer.battery.status.temperature_min_dC) / 2);
@ -119,11 +94,11 @@ void update_values_can_inverter() { //This function maps all the values fetched
8); //Minvoltage behaves strange on SMA, cuts out at 56% of the set value? 8); //Minvoltage behaves strange on SMA, cuts out at 56% of the set value?
SMA_358.data.u8[3] = (datalayer.battery.info.min_design_voltage_dV & 0x00FF); SMA_358.data.u8[3] = (datalayer.battery.info.min_design_voltage_dV & 0x00FF);
//Discharge limited current, 500 = 50A, (0.1, A) //Discharge limited current, 500 = 50A, (0.1, A)
SMA_358.data.u8[4] = (discharge_current >> 8); SMA_358.data.u8[4] = (datalayer.battery.status.max_discharge_current_dA >> 8);
SMA_358.data.u8[5] = (discharge_current & 0x00FF); SMA_358.data.u8[5] = (datalayer.battery.status.max_discharge_current_dA & 0x00FF);
//Charge limited current, 125 =12.5A (0.1, A) //Charge limited current, 125 =12.5A (0.1, A)
SMA_358.data.u8[6] = (charge_current >> 8); SMA_358.data.u8[6] = (datalayer.battery.status.max_charge_current_dA >> 8);
SMA_358.data.u8[7] = (charge_current & 0x00FF); SMA_358.data.u8[7] = (datalayer.battery.status.max_charge_current_dA & 0x00FF);
//SOC (100.00%) //SOC (100.00%)
SMA_3D8.data.u8[0] = (datalayer.battery.status.reported_soc >> 8); SMA_3D8.data.u8[0] = (datalayer.battery.status.reported_soc >> 8);

View file

@ -81,8 +81,6 @@ CAN_frame SMA_018 = {.FD = false,
.ID = 0x018, .ID = 0x018,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
static uint16_t discharge_current = 0;
static uint16_t charge_current = 0;
static int16_t temperature_average = 0; static int16_t temperature_average = 0;
static uint16_t ampere_hours_remaining = 0; static uint16_t ampere_hours_remaining = 0;
static uint16_t ampere_hours_max = 0; static uint16_t ampere_hours_max = 0;
@ -123,29 +121,6 @@ InvInitState invInitState = SYSTEM_FREQUENCY;
void update_values_can_inverter() { //This function maps all the values fetched from battery CAN to the inverter CAN void update_values_can_inverter() { //This function maps all the values fetched from battery CAN to the inverter CAN
//Calculate values //Calculate values
if (datalayer.battery.status.voltage_dV > 10) { // Only update value when we have voltage available to avoid div0
charge_current =
((datalayer.battery.status.max_charge_power_W * 10) /
datalayer.battery.status.voltage_dV); //Charge power in W , max volt in V+1decimal (P=UI, solve for I)
charge_current = (charge_current * 10); //Value needs a decimal before getting sent to inverter (81.0A)
discharge_current =
((datalayer.battery.status.max_discharge_power_W * 10) /
datalayer.battery.status.voltage_dV); //Charge power in W , max volt in V+1decimal (P=UI, solve for I)
discharge_current = (discharge_current * 10); //Value needs a decimal before getting sent to inverter (81.0A)
}
if (charge_current > datalayer.battery.info.max_charge_amp_dA) {
charge_current =
datalayer.battery.info
.max_charge_amp_dA; //Cap the value to the max allowed Amp. Some inverters cannot handle large values.
}
if (discharge_current > datalayer.battery.info.max_discharge_amp_dA) {
discharge_current =
datalayer.battery.info
.max_discharge_amp_dA; //Cap the value to the max allowed Amp. Some inverters cannot handle large values.
}
temperature_average = temperature_average =
((datalayer.battery.status.temperature_max_dC + datalayer.battery.status.temperature_min_dC) / 2); ((datalayer.battery.status.temperature_max_dC + datalayer.battery.status.temperature_min_dC) / 2);
@ -167,11 +142,11 @@ void update_values_can_inverter() { //This function maps all the values fetched
SMA_00D.data.u8[2] = (datalayer.battery.info.min_design_voltage_dV >> 8); SMA_00D.data.u8[2] = (datalayer.battery.info.min_design_voltage_dV >> 8);
SMA_00D.data.u8[3] = (datalayer.battery.info.min_design_voltage_dV & 0x00FF); SMA_00D.data.u8[3] = (datalayer.battery.info.min_design_voltage_dV & 0x00FF);
//Discharge limited current, 500 = 50A, (0.1, A) //Discharge limited current, 500 = 50A, (0.1, A)
SMA_00D.data.u8[4] = (discharge_current >> 8); SMA_00D.data.u8[4] = (datalayer.battery.status.max_discharge_current_dA >> 8);
SMA_00D.data.u8[5] = (discharge_current & 0x00FF); SMA_00D.data.u8[5] = (datalayer.battery.status.max_discharge_current_dA & 0x00FF);
//Charge limited current, 125 =12.5A (0.1, A) //Charge limited current, 125 =12.5A (0.1, A)
SMA_00D.data.u8[6] = (charge_current >> 8); SMA_00D.data.u8[6] = (datalayer.battery.status.max_charge_current_dA >> 8);
SMA_00D.data.u8[7] = (charge_current & 0x00FF); SMA_00D.data.u8[7] = (datalayer.battery.status.max_charge_current_dA & 0x00FF);
// Battery State // Battery State
//SOC (100.00%) //SOC (100.00%)

View file

@ -207,10 +207,10 @@ void update_values_can_inverter() { //This function maps all the values fetched
//Maxvoltage (eg 400.0V = 4000 , 16bits long) Charge Cutoff Voltage //Maxvoltage (eg 400.0V = 4000 , 16bits long) Charge Cutoff Voltage
SOFAR_351.data.u8[0] = (datalayer.battery.info.max_design_voltage_dV >> 8); SOFAR_351.data.u8[0] = (datalayer.battery.info.max_design_voltage_dV >> 8);
SOFAR_351.data.u8[1] = (datalayer.battery.info.max_design_voltage_dV & 0x00FF); SOFAR_351.data.u8[1] = (datalayer.battery.info.max_design_voltage_dV & 0x00FF);
//SOFAR_351.data.u8[2] = DC charge current limitation (Default 25.0A) SOFAR_351.data.u8[2] = (datalayer.battery.status.max_charge_current_dA >> 8);
//SOFAR_351.data.u8[3] = DC charge current limitation SOFAR_351.data.u8[3] = (datalayer.battery.status.max_charge_current_dA & 0x00FF);
//SOFAR_351.data.u8[4] = DC discharge current limitation (Default 25.0A) SOFAR_351.data.u8[4] = (datalayer.battery.status.max_discharge_current_dA >> 8);
//SOFAR_351.data.u8[5] = DC discharge current limitation SOFAR_351.data.u8[5] = (datalayer.battery.status.max_discharge_current_dA & 0x00FF);
//Minvoltage (eg 300.0V = 3000 , 16bits long) Discharge Cutoff Voltage //Minvoltage (eg 300.0V = 3000 , 16bits long) Discharge Cutoff Voltage
SOFAR_351.data.u8[6] = (datalayer.battery.info.min_design_voltage_dV >> 8); SOFAR_351.data.u8[6] = (datalayer.battery.info.min_design_voltage_dV >> 8);
SOFAR_351.data.u8[7] = (datalayer.battery.info.min_design_voltage_dV & 0x00FF); SOFAR_351.data.u8[7] = (datalayer.battery.info.min_design_voltage_dV & 0x00FF);

View file

@ -10,8 +10,6 @@
// https://github.com/dalathegreat/Battery-Emulator/wiki/Solax-inverters // https://github.com/dalathegreat/Battery-Emulator/wiki/Solax-inverters
/* Do not change code below unless you are sure what you are doing */ /* Do not change code below unless you are sure what you are doing */
static uint16_t max_charge_rate_amp = 0;
static uint16_t max_discharge_rate_amp = 0;
static int16_t temperature_average = 0; static int16_t temperature_average = 0;
static uint8_t STATE = BATTERY_ANNOUNCE; static uint8_t STATE = BATTERY_ANNOUNCE;
static unsigned long LastFrameTime = 0; static unsigned long LastFrameTime = 0;
@ -93,50 +91,6 @@ void update_values_can_inverter() { //This function maps all the values fetched
temperature_average = temperature_average =
((datalayer.battery.status.temperature_max_dC + datalayer.battery.status.temperature_min_dC) / 2); ((datalayer.battery.status.temperature_max_dC + datalayer.battery.status.temperature_min_dC) / 2);
//datalayer.battery.status.max_charge_power_W (30000W max)
if (datalayer.battery.status.reported_soc > 9999) { // 99.99%
// Additional safety incase SOC% is 100, then do not charge battery further
max_charge_rate_amp = 0;
} else { // We can pass on the battery charge rate (in W) to the inverter (that takes A)
if (datalayer.battery.status.max_charge_power_W >= 30000) {
max_charge_rate_amp = 75; // Incase battery can take over 30kW, cap value to 75A
} else { // Calculate the W value into A
if (datalayer.battery.status.voltage_dV > 10) {
max_charge_rate_amp =
datalayer.battery.status.max_charge_power_W / (datalayer.battery.status.voltage_dV * 0.1); // P/U=I
} else { // We avoid dividing by 0 and crashing the board
// If we have no voltage, something has gone wrong, do not allow charging
max_charge_rate_amp = 0;
}
}
}
//datalayer.battery.status.max_discharge_power_W (30000W max)
if (datalayer.battery.status.reported_soc < 100) { // 1.00%
// Additional safety in case SOC% is below 1, then do not discharge battery further
max_discharge_rate_amp = 0;
} else { // We can pass on the battery discharge rate to the inverter
if (datalayer.battery.status.max_discharge_power_W >= 30000) {
max_discharge_rate_amp = 75; // Incase battery can be charged with over 30kW, cap value to 75A
} else { // Calculate the W value into A
if (datalayer.battery.status.voltage_dV > 10) {
max_discharge_rate_amp =
datalayer.battery.status.max_discharge_power_W / (datalayer.battery.status.voltage_dV * 0.1); // P/U=I
} else { // We avoid dividing by 0 and crashing the board
// If we have no voltage, something has gone wrong, do not allow discharging
max_discharge_rate_amp = 0;
}
}
}
//Cap the value according to user settings. Some inverters cannot handle large values.
if ((max_charge_rate_amp * 10) > datalayer.battery.info.max_charge_amp_dA) {
max_charge_rate_amp = (datalayer.battery.info.max_charge_amp_dA / 10);
}
if ((max_discharge_rate_amp * 10) > datalayer.battery.info.max_discharge_amp_dA) {
max_discharge_rate_amp = (datalayer.battery.info.max_discharge_amp_dA / 10);
}
// Batteries might be larger than uint16_t value can take // Batteries might be larger than uint16_t value can take
if (datalayer.battery.info.total_capacity_Wh > 65000) { if (datalayer.battery.info.total_capacity_Wh > 65000) {
capped_capacity_Wh = 65000; capped_capacity_Wh = 65000;
@ -156,10 +110,10 @@ void update_values_can_inverter() { //This function maps all the values fetched
SOLAX_1872.data.u8[1] = (datalayer.battery.info.max_design_voltage_dV >> 8); SOLAX_1872.data.u8[1] = (datalayer.battery.info.max_design_voltage_dV >> 8);
SOLAX_1872.data.u8[2] = (uint8_t)datalayer.battery.info.min_design_voltage_dV; SOLAX_1872.data.u8[2] = (uint8_t)datalayer.battery.info.min_design_voltage_dV;
SOLAX_1872.data.u8[3] = (datalayer.battery.info.min_design_voltage_dV >> 8); SOLAX_1872.data.u8[3] = (datalayer.battery.info.min_design_voltage_dV >> 8);
SOLAX_1872.data.u8[4] = (uint8_t)(max_charge_rate_amp * 10); SOLAX_1872.data.u8[4] = (uint8_t)datalayer.battery.status.max_charge_current_dA;
SOLAX_1872.data.u8[5] = ((max_charge_rate_amp * 10) >> 8); SOLAX_1872.data.u8[5] = (datalayer.battery.status.max_charge_current_dA >> 8);
SOLAX_1872.data.u8[6] = (uint8_t)(max_discharge_rate_amp * 10); SOLAX_1872.data.u8[6] = (uint8_t)datalayer.battery.status.max_discharge_current_dA;
SOLAX_1872.data.u8[7] = ((max_discharge_rate_amp * 10) >> 8); SOLAX_1872.data.u8[7] = (datalayer.battery.status.max_discharge_current_dA >> 8);
//BMS_PackData //BMS_PackData
SOLAX_1873.data.u8[0] = (uint8_t)datalayer.battery.status.voltage_dV; // OK SOLAX_1873.data.u8[0] = (uint8_t)datalayer.battery.status.voltage_dV; // OK