Using max discharge power from BMS, correction of scaling and mapping unknown values

This commit is contained in:
freddanastrom 2025-07-21 10:14:27 +02:00
parent dbf2def0e2
commit 45c084614b
4 changed files with 66 additions and 63 deletions

View file

@ -21,13 +21,13 @@ After battery has been unlocked, you can remove the "USE_ESTIMATED_SOC" from the
#define UNKNOWN_POLL_0 0x1FFE //0x64 19 C4 3B
#define UNKNOWN_POLL_1 0x1FFC //0x72 1F C4 3B
#define POLL_MAX_CHARGE_POWER 0x000A
#define UNKNOWN_POLL_3 0x000B //0x00B1 (177 interesting!)
#define UNKNOWN_POLL_4 0x000E //0x0B27 (2855 interesting!)
#define POLL_CHARGE_TIMES 0x000B // Using Carscanner name for now. Likely a counter for BMS 100% SOC calibration
#define POLL_MAX_DISCHARGE_POWER 0x000E
#define POLL_TOTAL_CHARGED_AH 0x000F
#define POLL_TOTAL_DISCHARGED_AH 0x0010
#define POLL_TOTAL_CHARGED_KWH 0x0011
#define POLL_TOTAL_DISCHARGED_KWH 0x0012
#define UNKNOWN_POLL_9 0x0004 //0x0034 (52 interesting!)
#define POLL_TIMES_FULL_POWER 0x0004 // Using Carscanner name for now. Unknown what it means for the moment
#define UNKNOWN_POLL_10 0x002A //0x5B
#define UNKNOWN_POLL_11 0x002E //0x08 (probably module number, or cell number?)
#define UNKNOWN_POLL_12 0x002C //0x43
@ -176,16 +176,16 @@ void BydAttoBattery::
// If using estimated SOC, ramp down max discharge power as SOC decreases.
rampdown_power = RAMPDOWN_POWER_ALLOWED * ((battery_estimated_SOC * 0.1) / RAMPDOWN_SOC);
if (rampdown_power < MAXPOWER_DISCHARGE_W) { // Never allow more than MAXPOWER_DISCHARGE_W
if (rampdown_power < BMS_allowed_discharge_power * 100) { // Never allow more than BMS_allowed_discharge_power
datalayer_battery->status.max_discharge_power_W = rampdown_power;
} else {
datalayer_battery->status.max_discharge_power_W = MAXPOWER_DISCHARGE_W; //TODO: Map from CAN later on
datalayer_battery->status.max_discharge_power_W = BMS_allowed_discharge_power * 100;
}
} else {
datalayer_battery->status.max_discharge_power_W = MAXPOWER_DISCHARGE_W; //TODO: Map from CAN later on
datalayer_battery->status.max_discharge_power_W = BMS_allowed_discharge_power * 100;
}
datalayer_battery->status.max_charge_power_W = BMS_allowed_charge_power * 10; //TODO: Scaling unknown, *10 best guess
datalayer_battery->status.max_charge_power_W = BMS_allowed_charge_power * 100;
datalayer_battery->status.cell_max_voltage_mV = BMS_highest_cell_voltage_mV;
@ -288,13 +288,13 @@ void BydAttoBattery::
datalayer_bydatto->unknown0 = BMS_unknown0;
datalayer_bydatto->unknown1 = BMS_unknown1;
datalayer_bydatto->chargePower = BMS_allowed_charge_power;
datalayer_bydatto->unknown3 = BMS_unknown3;
datalayer_bydatto->unknown4 = BMS_unknown4;
datalayer_bydatto->charge_times = BMS_charge_times;
datalayer_bydatto->dischargePower = BMS_allowed_discharge_power;
datalayer_bydatto->total_charged_ah = BMS_total_charged_ah;
datalayer_bydatto->total_discharged_ah = BMS_total_discharged_ah;
datalayer_bydatto->total_charged_kwh = BMS_total_charged_kwh;
datalayer_bydatto->total_discharged_kwh = BMS_total_discharged_kwh;
datalayer_bydatto->unknown9 = BMS_unknown9;
datalayer_bydatto->times_full_power = BMS_times_full_power;
datalayer_bydatto->unknown10 = BMS_unknown10;
datalayer_bydatto->unknown11 = BMS_unknown11;
datalayer_bydatto->unknown12 = BMS_unknown12;
@ -450,11 +450,11 @@ void BydAttoBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
case POLL_MAX_CHARGE_POWER:
BMS_allowed_charge_power = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4];
break;
case UNKNOWN_POLL_3:
BMS_unknown3 = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4];
case POLL_CHARGE_TIMES:
BMS_charge_times = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4];
break;
case UNKNOWN_POLL_4:
BMS_unknown4 = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4];
case POLL_MAX_DISCHARGE_POWER:
BMS_allowed_discharge_power = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4];
break;
case POLL_TOTAL_CHARGED_AH:
BMS_total_charged_ah = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4];
@ -468,8 +468,8 @@ void BydAttoBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
case POLL_TOTAL_DISCHARGED_KWH:
BMS_total_discharged_kwh = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4];
break;
case UNKNOWN_POLL_9:
BMS_unknown9 = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4];
case POLL_TIMES_FULL_POWER:
BMS_times_full_power = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4];
break;
case UNKNOWN_POLL_10:
BMS_unknown10 = rx_frame.data.u8[4];
@ -626,16 +626,16 @@ void BydAttoBattery::transmit_can(unsigned long currentMillis) {
case POLL_MAX_CHARGE_POWER:
ATTO_3_7E7_POLL.data.u8[2] = (uint8_t)((POLL_MAX_CHARGE_POWER & 0xFF00) >> 8);
ATTO_3_7E7_POLL.data.u8[3] = (uint8_t)(POLL_MAX_CHARGE_POWER & 0x00FF);
poll_state = UNKNOWN_POLL_3;
poll_state = POLL_CHARGE_TIMES;
break;
case UNKNOWN_POLL_3:
ATTO_3_7E7_POLL.data.u8[2] = (uint8_t)((UNKNOWN_POLL_3 & 0xFF00) >> 8);
ATTO_3_7E7_POLL.data.u8[3] = (uint8_t)(UNKNOWN_POLL_3 & 0x00FF);
poll_state = UNKNOWN_POLL_4;
case POLL_CHARGE_TIMES:
ATTO_3_7E7_POLL.data.u8[2] = (uint8_t)((POLL_CHARGE_TIMES & 0xFF00) >> 8);
ATTO_3_7E7_POLL.data.u8[3] = (uint8_t)(POLL_CHARGE_TIMES & 0x00FF);
poll_state = POLL_MAX_DISCHARGE_POWER;
break;
case UNKNOWN_POLL_4:
ATTO_3_7E7_POLL.data.u8[2] = (uint8_t)((UNKNOWN_POLL_4 & 0xFF00) >> 8);
ATTO_3_7E7_POLL.data.u8[3] = (uint8_t)(UNKNOWN_POLL_4 & 0x00FF);
case POLL_MAX_DISCHARGE_POWER:
ATTO_3_7E7_POLL.data.u8[2] = (uint8_t)((POLL_MAX_DISCHARGE_POWER & 0xFF00) >> 8);
ATTO_3_7E7_POLL.data.u8[3] = (uint8_t)(POLL_MAX_DISCHARGE_POWER & 0x00FF);
poll_state = POLL_TOTAL_CHARGED_AH;
break;
case POLL_TOTAL_CHARGED_AH:
@ -656,11 +656,11 @@ void BydAttoBattery::transmit_can(unsigned long currentMillis) {
case POLL_TOTAL_DISCHARGED_KWH:
ATTO_3_7E7_POLL.data.u8[2] = (uint8_t)((POLL_TOTAL_DISCHARGED_KWH & 0xFF00) >> 8);
ATTO_3_7E7_POLL.data.u8[3] = (uint8_t)(POLL_TOTAL_DISCHARGED_KWH & 0x00FF);
poll_state = UNKNOWN_POLL_9;
poll_state = POLL_TIMES_FULL_POWER;
break;
case UNKNOWN_POLL_9:
ATTO_3_7E7_POLL.data.u8[2] = (uint8_t)((UNKNOWN_POLL_9 & 0xFF00) >> 8);
ATTO_3_7E7_POLL.data.u8[3] = (uint8_t)(UNKNOWN_POLL_9 & 0x00FF);
case POLL_TIMES_FULL_POWER:
ATTO_3_7E7_POLL.data.u8[2] = (uint8_t)((POLL_TIMES_FULL_POWER & 0xFF00) >> 8);
ATTO_3_7E7_POLL.data.u8[3] = (uint8_t)(POLL_TIMES_FULL_POWER & 0x00FF);
poll_state = UNKNOWN_POLL_10;
break;
case UNKNOWN_POLL_10:

View file

@ -10,8 +10,6 @@
#define USE_ESTIMATED_SOC // If enabled, SOC is estimated from pack voltage. Useful for locked packs. \
// Comment out this only if you know your BMS is unlocked and able to send SOC%
#define MAXPOWER_CHARGE_W 10000
#define MAXPOWER_DISCHARGE_W 10000
//Uncomment and configure this line, if you want to filter out a broken temperature sensor (1-10)
//Make sure you understand risks associated with disabling. Values can be read via "More Battery info"
@ -121,13 +119,13 @@ class BydAttoBattery : public CanBattery {
uint32_t BMS_unknown0 = 0;
uint32_t BMS_unknown1 = 0;
uint16_t BMS_allowed_charge_power = 0;
uint16_t BMS_unknown3 = 0;
uint16_t BMS_unknown4 = 0;
uint16_t BMS_charge_times = 0;
uint16_t BMS_allowed_discharge_power = 0;
uint16_t BMS_total_charged_ah = 0;
uint16_t BMS_total_discharged_ah = 0;
uint16_t BMS_total_charged_kwh = 0;
uint16_t BMS_total_discharged_kwh = 0;
uint16_t BMS_unknown9 = 0;
uint16_t BMS_times_full_power = 0;
uint8_t BMS_unknown10 = 0;
uint8_t BMS_unknown11 = 0;
uint8_t BMS_unknown12 = 0;

View file

@ -12,33 +12,38 @@ class BydAtto3HtmlRenderer : public BatteryHtmlRenderer {
String get_status_html() {
String content;
float soc_estimated = static_cast<float>(byd_datalayer->SOC_estimated) * 0.01;
float soc_measured = static_cast<float>(byd_datalayer->SOC_highprec) * 0.1;
float BMS_maxChargePower = static_cast<float>(byd_datalayer->chargePower) * 0.1;
float BMS_maxDischargePower = static_cast<float>(byd_datalayer->dischargePower) * 0.1;
static const char* SOCmethod[2] = {"Estimated from voltage", "Measured by BMS"};
content += "<h4>SOC method used: " + String(SOCmethod[byd_datalayer->SOC_method]) + "</h4>";
content += "<h4>SOC estimated: " + String(byd_datalayer->SOC_estimated) + "</h4>";
content += "<h4>SOC highprec: " + String(byd_datalayer->SOC_highprec) + "</h4>";
content += "<h4>SOC OBD2: " + String(byd_datalayer->SOC_polled) + "</h4>";
content += "<h4>Voltage periodic: " + String(byd_datalayer->voltage_periodic) + "</h4>";
content += "<h4>Voltage OBD2: " + String(byd_datalayer->voltage_polled) + "</h4>";
content += "<h4>Temperature sensor 1: " + String(byd_datalayer->battery_temperatures[0]) + "</h4>";
content += "<h4>Temperature sensor 2: " + String(byd_datalayer->battery_temperatures[1]) + "</h4>";
content += "<h4>Temperature sensor 3: " + String(byd_datalayer->battery_temperatures[2]) + "</h4>";
content += "<h4>Temperature sensor 4: " + String(byd_datalayer->battery_temperatures[3]) + "</h4>";
content += "<h4>Temperature sensor 5: " + String(byd_datalayer->battery_temperatures[4]) + "</h4>";
content += "<h4>Temperature sensor 6: " + String(byd_datalayer->battery_temperatures[5]) + "</h4>";
content += "<h4>Temperature sensor 7: " + String(byd_datalayer->battery_temperatures[6]) + "</h4>";
content += "<h4>Temperature sensor 8: " + String(byd_datalayer->battery_temperatures[7]) + "</h4>";
content += "<h4>Temperature sensor 9: " + String(byd_datalayer->battery_temperatures[8]) + "</h4>";
content += "<h4>Temperature sensor 10: " + String(byd_datalayer->battery_temperatures[9]) + "</h4>";
content += "<h4>SOC estimated: " + String(soc_estimated) + "&percnt;</h4>";
content += "<h4>SOC measured: " + String(soc_measured) + "&percnt;</h4>";
content += "<h4>SOC OBD2: " + String(byd_datalayer->SOC_polled) + "&percnt;</h4>";
content += "<h4>Voltage periodic: " + String(byd_datalayer->voltage_periodic) + " V</h4>";
content += "<h4>Voltage OBD2: " + String(byd_datalayer->voltage_polled) + " V</h4>";
content += "<h4>Temperature sensor 1: " + String(byd_datalayer->battery_temperatures[0]) + " &deg;C</h4>";
content += "<h4>Temperature sensor 2: " + String(byd_datalayer->battery_temperatures[1]) + " &deg;C</h4>";
content += "<h4>Temperature sensor 3: " + String(byd_datalayer->battery_temperatures[2]) + " &deg;C</h4>";
content += "<h4>Temperature sensor 4: " + String(byd_datalayer->battery_temperatures[3]) + " &deg;C</h4>";
content += "<h4>Temperature sensor 5: " + String(byd_datalayer->battery_temperatures[4]) + " &deg;C</h4>";
content += "<h4>Temperature sensor 6: " + String(byd_datalayer->battery_temperatures[5]) + " &deg;C</h4>";
content += "<h4>Temperature sensor 7: " + String(byd_datalayer->battery_temperatures[6]) + " &deg;C</h4>";
content += "<h4>Temperature sensor 8: " + String(byd_datalayer->battery_temperatures[7]) + " &deg;C</h4>";
content += "<h4>Temperature sensor 9: " + String(byd_datalayer->battery_temperatures[8]) + " &deg;C</h4>";
content += "<h4>Temperature sensor 10: " + String(byd_datalayer->battery_temperatures[9]) + " &deg;C</h4>";
content += "<h4>Max discharge power: " + String(BMS_maxDischargePower) + " kW</h4>";
content += "<h4>Max charge (regen) power: " + String(BMS_maxChargePower) + " kW</h4>";
content += "<h4>Total charged: " + String(byd_datalayer->total_charged_kwh) + " kWh</h4>";
content += "<h4>Total discharged: " + String(byd_datalayer->total_discharged_kwh) + " kWh</h4>";
content += "<h4>Total charged: " + String(byd_datalayer->total_charged_ah) + " Ah</h4>";
content += "<h4>Total discharged: " + String(byd_datalayer->total_discharged_ah) + " Ah</h4>";
content += "<h4>Charge times: " + String(byd_datalayer->charge_times) + "</h4>";
content += "<h4>Times of full power: " + String(byd_datalayer->times_full_power) + "</h4>";
content += "<h4>Unknown0: " + String(byd_datalayer->unknown0) + "</h4>";
content += "<h4>Unknown1: " + String(byd_datalayer->unknown1) + "</h4>";
content += "<h4>Charge power raw: " + String(byd_datalayer->chargePower) + "</h4>";
content += "<h4>Unknown3: " + String(byd_datalayer->unknown3) + "</h4>";
content += "<h4>Unknown4: " + String(byd_datalayer->unknown4) + "</h4>";
content += "<h4>Total charged Ah: " + String(byd_datalayer->total_charged_ah) + "</h4>";
content += "<h4>Total discharged Ah: " + String(byd_datalayer->total_discharged_ah) + "</h4>";
content += "<h4>Total charged kWh: " + String(byd_datalayer->total_charged_kwh) + "</h4>";
content += "<h4>Total discharged kWh: " + String(byd_datalayer->total_discharged_kwh) + "</h4>";
content += "<h4>Unknown9: " + String(byd_datalayer->unknown9) + "</h4>";
content += "<h4>Unknown10: " + String(byd_datalayer->unknown10) + "</h4>";
content += "<h4>Unknown11: " + String(byd_datalayer->unknown11) + "</h4>";
content += "<h4>Unknown12: " + String(byd_datalayer->unknown12) + "</h4>";

View file

@ -125,13 +125,13 @@ typedef struct {
uint32_t unknown0 = 0;
uint32_t unknown1 = 0;
uint16_t chargePower = 0;
uint16_t unknown3 = 0;
uint16_t unknown4 = 0;
uint16_t charge_times = 0;
uint16_t dischargePower = 0;
uint16_t total_charged_ah = 0;
uint16_t total_discharged_ah = 0;
uint16_t total_charged_kwh = 0;
uint16_t total_discharged_kwh = 0;
uint16_t unknown9 = 0;
uint16_t times_full_power = 0;
uint8_t unknown10 = 0;
uint8_t unknown11 = 0;
uint8_t unknown12 = 0;