From cbdb2392d98c8680a413e678c60521b0fdaa1ef8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Tue, 27 Aug 2024 16:33:36 +0300 Subject: [PATCH 001/210] Add Kostal RS485 protocol --- Software/Software.ino | 6 + Software/USER_SETTINGS.h | 1 + Software/src/include.h | 7 + Software/src/inverter/INVERTERS.h | 9 + Software/src/inverter/KOSTAL-RS485.cpp | 219 +++++++++++++++++++++++++ Software/src/inverter/KOSTAL-RS485.h | 8 + 6 files changed, 250 insertions(+) create mode 100644 Software/src/inverter/KOSTAL-RS485.cpp create mode 100644 Software/src/inverter/KOSTAL-RS485.h diff --git a/Software/Software.ino b/Software/Software.ino index b927a729..e6aa6f3a 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -209,6 +209,9 @@ void core_loop(void* task_time_us) { #ifdef DUAL_CAN receive_can_addonMCP2515(); // Receive CAN messages on add-on MCP2515 chip #endif +#ifdef RS485_INVERTER_SELECTED + receive_RS485(); // Process serial2 RS485 interface +#endif #if defined(SERIAL_LINK_RECEIVER) || defined(SERIAL_LINK_TRANSMITTER) runSerialDataLink(); #endif @@ -768,6 +771,9 @@ void update_values_inverter() { #ifdef MODBUS_INVERTER_SELECTED update_modbus_registers_inverter(); #endif +#ifdef RS485_INVERTER_SELECTED + update_RS485_registers_inverter(); +#endif } #if defined(SERIAL_LINK_RECEIVER) || defined(SERIAL_LINK_TRANSMITTER) diff --git a/Software/USER_SETTINGS.h b/Software/USER_SETTINGS.h index 67ea352f..3a0c21fc 100644 --- a/Software/USER_SETTINGS.h +++ b/Software/USER_SETTINGS.h @@ -32,6 +32,7 @@ //#define BYD_CAN //Enable this line to emulate a "BYD Battery-Box Premium HVS" over CAN Bus //#define BYD_SMA //Enable this line to emulate a SMA compatible "BYD Battery-Box HVS 10.2KW battery" over CAN bus //#define BYD_MODBUS //Enable this line to emulate a "BYD 11kWh HVM battery" over Modbus RTU +//#define BYD_KOSTAL_RS485 //Enable this line to emulate a "BYD 11kWh HVM battery" over Kostal RS485 //#define LUNA2000_MODBUS //Enable this line to emulate a "Luna2000 battery" over Modbus RTU //#define PYLON_CAN //Enable this line to emulate a "Pylontech battery" over CAN bus //#define SMA_CAN //Enable this line to emulate a "BYD Battery-Box H 8.9kWh, 7 mod" over CAN bus diff --git a/Software/src/include.h b/Software/src/include.h index 83f2290d..72c49f31 100644 --- a/Software/src/include.h +++ b/Software/src/include.h @@ -41,6 +41,13 @@ #endif #endif +#ifdef RS485_INVERTER_SELECTED +#if defined(SERIAL_LINK_RECEIVER) || defined(SERIAL_LINK_TRANSMITTER) +// Check that Dual LilyGo via RS485 option isn't enabled, this collides with Modbus! +#error RS485 CANNOT BE USED IN DOUBLE LILYGO SETUPS! CHECK USER SETTINGS! +#endif +#endif + #ifndef BATTERY_SELECTED #error No battery selected! Choose one from the USER_SETTINGS.h file #endif diff --git a/Software/src/inverter/INVERTERS.h b/Software/src/inverter/INVERTERS.h index c448697e..0ba96296 100644 --- a/Software/src/inverter/INVERTERS.h +++ b/Software/src/inverter/INVERTERS.h @@ -15,6 +15,10 @@ #include "BYD-SMA.h" #endif +#ifdef BYD_KOSTAL_RS485 +#include "KOSTAL-RS485.h" +#endif + #ifdef LUNA2000_MODBUS #include "LUNA2000-MODBUS.h" #endif @@ -53,4 +57,9 @@ void send_can_inverter(); void update_modbus_registers_inverter(); #endif +#ifdef RS485_INVERTER_SELECTED +void receive_RS485(); +void update_RS485_registers_inverter(); +#endif + #endif diff --git a/Software/src/inverter/KOSTAL-RS485.cpp b/Software/src/inverter/KOSTAL-RS485.cpp new file mode 100644 index 00000000..9e488cc6 --- /dev/null +++ b/Software/src/inverter/KOSTAL-RS485.cpp @@ -0,0 +1,219 @@ +#include "../include.h" +#ifdef BYD_KOSTAL_RS485 +#include "../datalayer/datalayer.h" +#include "../devboard/utils/events.h" +#include "KOSTAL-RS485.h" + +static const byte KOSTAL_FRAMEHEADER[5] = {0x62, 0xFF, 0x02, 0xFF, 0x29}; +static const byte KOSTAL_FRAMEHEADER2[5] = {0x63, 0xFF, 0x02, 0xFF, 0x29}; + +union f32b { + float f; + byte b[4]; +}; + +byte frame1[40] = { + 0x06, 0xE2, 0xFF, 0x02, 0xFF, 0x29, 0x01, 0x08, 0x80, 0x43, // 256.063 Max Charge? first byte 0x01 or 0x04 + 0xE4, 0x70, 0x8A, 0x5C, 0xB5, 0x02, 0xD3, 0x01, 0x01, 0x05, 0xC8, 0x41, // 25.0024 TEMP?? + 0xC2, 0x18, 0x01, 0x03, 0x59, 0x42, 0x01, 0x01, 0x01, 0x02, 0x05, 0x02, 0xA0, 0x01, 0x01, 0x02, 0x4D, 0x00}; + +// +// values will be overwritten at update_modbus_registers_inverter() +// +byte frame2[64] = {0x0A, 0xE2, 0xFF, 0x02, 0xFF, 0x29, // frame Header + 0x1D, 0x5A, 0x85, 0x43, // Voltage (float) Modbus register 216 + + 0x01, 0x03, 0x8D, 0x43, // Max Voltage (2 byte float) + + 0x01, 0x03, 0xAC, 0x41, // Temp (2 byte float) Modbus register 214 + 0x01, 0x01, 0x01, 0x01, // Current + 0x01, 0x01, 0x01, 0x01, // Current + + 0x01, 0x03, 0x48, 0x42, // Peak discharge current (2 byte float) + + 0x01, 0x03, 0xC8, 0x41, // Nominal discharge I (2 byte float) + + 0x01, 0x01, // Unknown + 0x01, 0x05, // Max charge? (2 byte float) + + 0xCD, 0xCC, // Unknown + 0xB4, 0x41, // MaxCellTemp (2 byte float) + + 0x01, 0x0C, // Maybe cell information? + 0xA4, 0x41, // MinCellTemp (2 byte float) + + 0xA4, 0x70, 0x55, 0x40, // MaxCellVolt (float) + 0x7D, 0x3F, 0x55, 0x40, // MinCellVolt (float) + + 0xFE, // Cylce count + 0x04, // Cycle count? + 0x01, 0x40, // ?? + 0x64, // SOC + 0x01, // Unknown, Mostly 0x01, seen also 0x02 + 0x01, // Unknown, Seen only 0x01 + 0x02, // Unknown, Mostly 0x02. seen also 0x01 + 0x00, // CRC (inverted sum of bytes 1-62 + 0xC0) + 0x00}; + +byte frame3[9] = { + 0x08, 0xE2, 0xFF, 0x02, 0xFF, 0x29, //header + 0x06, //Unknown + 0xEF, //CRC + 0x00 //endbyte +}; + +byte frameB1[10] = {0x07, 0x63, 0xFF, 0x02, 0xFF, 0x29, 0x5E, 0x02, 0x16, 0x00}; +byte frameB1b[10] = {0x07, 0xE3, 0xFF, 0x02, 0xFF, 0x29, 0xF4, 0x00}; + +byte RS485_RXFRAME[10]; +size_t RS485_RECEIVEDBYTES; + +boolean reg_data_ok = false; + +void float2frame(byte* arr, float value, byte framepointer) { + f32b g; + g.f = value; + arr[framepointer] = g.b[0]; + arr[framepointer + 1] = g.b[1]; + arr[framepointer + 2] = g.b[2]; + arr[framepointer + 3] = g.b[3]; +} + +void float2frameMSB(byte* arr, float value, byte framepointer) { + f32b g; + g.f = value; + arr[framepointer + 0] = g.b[2]; + arr[framepointer + 1] = g.b[3]; +} + +void send_kostal(byte* arr, int alen) { +#ifdef DEBUG_KOSTAL_RS485_DATA + Serial.print("TX: "); + for (int i = 0; i < alen; i++) { + if (arr[i] < 0x10) { + Serial.print("0") + } + Serial.print(arr[i], HEX); + Serial.print(" "); + } + Serial.println("\n"); +#endif + Serial2.write(arr, alen); +} + +byte calculate_longframe_crc(byte* lfc, int lastbyte) { + unsigned int sum = 0; + for (int i = 0; i < lastbyte; ++i) { + sum += lfc[i]; + } + return ((byte) ~(sum + 0xc0) & 0xff); +} + +boolean check_kostal_frame_crc() { + unsigned int sum = 0; + for (int i = 1; i < 8; ++i) { + sum += RS485_RXFRAME[i]; + } + if (((~sum + 1) & 0xff) == (RS485_RXFRAME[8] & 0xff)) { + return (true); + } else { + return (false); + } +} + +byte rx_index = 0; + +void receive_RS485() // Runs as fast as possible to handle the serial stream +{ + if (Serial2.available()) { + RS485_RXFRAME[rx_index] = Serial2.read(); + rx_index++; + if (RS485_RXFRAME[rx_index - 1] == 0x00) // + { + if ((rx_index == 10) && (RS485_RXFRAME[0] == 0x09) && reg_data_ok) { +#ifdef DEBUG_KOSTAL_RS485_DATA + Serial.print("RX: "); + for (int i = 0; i < 10; i++) { + Serial.print(RS485_RXFRAME[i], HEX); + Serial.print(" "); + } + Serial.println(""); +#endif + rx_index = 0; + if (check_kostal_frame_crc()) { + boolean notheaderA = 0; + boolean notheaderB = 0; + for (int i = 0; i < 5; i++) { + if (RS485_RXFRAME[i + 1] != KOSTAL_FRAMEHEADER[i]) { + notheaderA = true; + } + if (RS485_RXFRAME[i + 1] != KOSTAL_FRAMEHEADER2[i]) { + notheaderB = true; + } + } + + if (!notheaderB && (RS485_RXFRAME[6] == 0x5E) && + (RS485_RXFRAME[7] == 0xFF)) // "frame B1", maybe reset request, seen after battery power on/partial data + { + send_kostal(frameB1, 10); + Serial2.flush(); + delay(1); + send_kostal(frameB1b, 10); + } + + if (!notheaderA && (RS485_RXFRAME[6] == 0x4A) && (RS485_RXFRAME[7] == 0x08)) // "frame 1" + { + send_kostal(frame1, 40); + } + if (!notheaderA && (RS485_RXFRAME[6] == 0x4A) && (RS485_RXFRAME[7] == 0x04)) // "frame 2" + { + byte tmpframe[64]; //copy values to prevent data manipulation during rewrite/crc calculation + memcpy(tmpframe, frame2, 64); + for (int i = 1; i < 63; i++) { + if (tmpframe[i] == 0x00) { + tmpframe[i] = 0x01; + } + } + tmpframe[62] = calculate_longframe_crc(tmpframe, 62); + send_kostal(tmpframe, 64); + } + if (!notheaderA && (RS485_RXFRAME[6] == 0x53) && (RS485_RXFRAME[7] == 0x03)) // "frame 3" + { + send_kostal(frame3, 9); + } + } + } + rx_index = 0; + } + if (rx_index == 10) { + rx_index = 0; + } + } +} + +void update_RS485_registers_inverter() { + + float2frame(frame2, (float)datalayer.battery.status.voltage_dV / 10, 6); + + float2frameMSB(frame2, (float)datalayer.battery.info.max_design_voltage_dV / 10, 12); + float2frameMSB( + frame2, (float)(datalayer.battery.status.temperature_max_dC + datalayer.battery.status.temperature_min_dC) / 20, + 16); + + float2frameMSB(frame2, (float)datalayer.battery.status.current_dA / 10, 20); + float2frameMSB(frame2, (float)datalayer.battery.status.current_dA / 10, 24); + + float2frameMSB(frame2, (float)datalayer.battery.info.max_discharge_amp_dA / 10, 28); + float2frameMSB(frame2, (float)datalayer.battery.info.max_discharge_amp_dA / 10, 32); + + float2frameMSB(frame2, (float)datalayer.battery.status.temperature_max_dC / 10, 40); + float2frameMSB(frame2, (float)datalayer.battery.status.temperature_min_dC / 10, 44); + + float2frame(frame2, (float)datalayer.battery.status.cell_max_voltage_mV / 1000, 46); + float2frame(frame2, (float)datalayer.battery.status.cell_min_voltage_mV / 1000, 50); + + frame2[58] = (byte)datalayer.battery.status.reported_soc / 100; + + reg_data_ok = true; +} +#endif diff --git a/Software/src/inverter/KOSTAL-RS485.h b/Software/src/inverter/KOSTAL-RS485.h new file mode 100644 index 00000000..727ba978 --- /dev/null +++ b/Software/src/inverter/KOSTAL-RS485.h @@ -0,0 +1,8 @@ +#ifndef BYD_KOSTAL_RS485_H +#define BYD_KOSTAL_RS485_H +#include +#include "../include.h" + +#define RS485_INVERTER_SELECTED + +#endif From ad42d44a57737ab3f7bff0e21534fb0633cb3bad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Wed, 28 Aug 2024 22:34:38 +0300 Subject: [PATCH 002/210] Add Serial2 init --- Software/Software.ino | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Software/Software.ino b/Software/Software.ino index e6aa6f3a..7843c0e9 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -471,7 +471,9 @@ void init_rs485() { pinMode(PIN_5V_EN, OUTPUT); digitalWrite(PIN_5V_EN, HIGH); #endif - +#ifdef RS485_INVERTER_SELECTED + Serial2.begin(57600, SERIAL_8N1, RS485_RX_PIN, RS485_TX_PIN); +#endif #ifdef MODBUS_INVERTER_SELECTED #ifdef BYD_MODBUS // Init Static data to the RTU Modbus From 6fd3f200b5954f9d10d342200a832be1efa6406d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Wed, 28 Aug 2024 22:35:56 +0300 Subject: [PATCH 003/210] Add webserver printout for Kostal --- Software/src/devboard/webserver/webserver.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Software/src/devboard/webserver/webserver.cpp b/Software/src/devboard/webserver/webserver.cpp index 449f1638..9162e115 100644 --- a/Software/src/devboard/webserver/webserver.cpp +++ b/Software/src/devboard/webserver/webserver.cpp @@ -457,6 +457,9 @@ String processor(const String& var) { #ifdef BYD_MODBUS content += "BYD 11kWh HVM battery over Modbus RTU"; #endif +#ifdef BYD_KOSTAL_RS485 + content += "BYD 11kWh HVM battery over Kostal RS485"; +#endif #ifdef LUNA2000_MODBUS content += "Luna2000 battery over Modbus RTU"; #endif From 4b01b1235dae2ed5f352e6d432d359e884037444 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Fri, 30 Aug 2024 16:06:27 +0300 Subject: [PATCH 004/210] Add USB debug while developing --- Software/src/inverter/KOSTAL-RS485.cpp | 2 +- Software/src/inverter/KOSTAL-RS485.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Software/src/inverter/KOSTAL-RS485.cpp b/Software/src/inverter/KOSTAL-RS485.cpp index 9e488cc6..f02f951c 100644 --- a/Software/src/inverter/KOSTAL-RS485.cpp +++ b/Software/src/inverter/KOSTAL-RS485.cpp @@ -91,7 +91,7 @@ void send_kostal(byte* arr, int alen) { Serial.print("TX: "); for (int i = 0; i < alen; i++) { if (arr[i] < 0x10) { - Serial.print("0") + Serial.print("0"); } Serial.print(arr[i], HEX); Serial.print(" "); diff --git a/Software/src/inverter/KOSTAL-RS485.h b/Software/src/inverter/KOSTAL-RS485.h index 727ba978..a89b8c81 100644 --- a/Software/src/inverter/KOSTAL-RS485.h +++ b/Software/src/inverter/KOSTAL-RS485.h @@ -4,5 +4,6 @@ #include "../include.h" #define RS485_INVERTER_SELECTED +#define DEBUG_KOSTAL_RS485_DATA #endif From 06bbb667bb4f5ab1d380b7812bb63eb3b6596e44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Fri, 30 Aug 2024 18:42:43 +0300 Subject: [PATCH 005/210] Add capping of charge value --- Software/src/inverter/KOSTAL-RS485.cpp | 36 +++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/Software/src/inverter/KOSTAL-RS485.cpp b/Software/src/inverter/KOSTAL-RS485.cpp index f02f951c..45e7a767 100644 --- a/Software/src/inverter/KOSTAL-RS485.cpp +++ b/Software/src/inverter/KOSTAL-RS485.cpp @@ -6,6 +6,8 @@ static const byte KOSTAL_FRAMEHEADER[5] = {0x62, 0xFF, 0x02, 0xFF, 0x29}; static const byte KOSTAL_FRAMEHEADER2[5] = {0x63, 0xFF, 0x02, 0xFF, 0x29}; +static uint16_t discharge_current = 0; +static uint16_t charge_current = 0; union f32b { float f; @@ -193,6 +195,32 @@ void receive_RS485() // Runs as fast as possible to handle the serial stream void update_RS485_registers_inverter() { + 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) + } + + 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. + } + float2frame(frame2, (float)datalayer.battery.status.voltage_dV / 10, 6); float2frameMSB(frame2, (float)datalayer.battery.info.max_design_voltage_dV / 10, 12); @@ -200,11 +228,11 @@ void update_RS485_registers_inverter() { frame2, (float)(datalayer.battery.status.temperature_max_dC + datalayer.battery.status.temperature_min_dC) / 20, 16); - float2frameMSB(frame2, (float)datalayer.battery.status.current_dA / 10, 20); - float2frameMSB(frame2, (float)datalayer.battery.status.current_dA / 10, 24); + float2frameMSB(frame2, (float)datalayer.battery.status.current_dA, 20); + float2frameMSB(frame2, (float)datalayer.battery.status.current_dA, 24); - float2frameMSB(frame2, (float)datalayer.battery.info.max_discharge_amp_dA / 10, 28); - float2frameMSB(frame2, (float)datalayer.battery.info.max_discharge_amp_dA / 10, 32); + float2frameMSB(frame2, (float)discharge_current / 10, 28); + float2frameMSB(frame2, (float)discharge_current / 10, 32); float2frameMSB(frame2, (float)datalayer.battery.status.temperature_max_dC / 10, 40); float2frameMSB(frame2, (float)datalayer.battery.status.temperature_min_dC / 10, 44); From 2cb74f32dee5feacc4c86b18a608613d3268f692 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Fri, 30 Aug 2024 20:29:29 +0300 Subject: [PATCH 006/210] Code cleanup --- Software/src/inverter/KOSTAL-RS485.cpp | 110 ++++++++++++------------- Software/src/inverter/KOSTAL-RS485.h | 2 +- 2 files changed, 52 insertions(+), 60 deletions(-) diff --git a/Software/src/inverter/KOSTAL-RS485.cpp b/Software/src/inverter/KOSTAL-RS485.cpp index 45e7a767..f74eadf7 100644 --- a/Software/src/inverter/KOSTAL-RS485.cpp +++ b/Software/src/inverter/KOSTAL-RS485.cpp @@ -4,8 +4,8 @@ #include "../devboard/utils/events.h" #include "KOSTAL-RS485.h" -static const byte KOSTAL_FRAMEHEADER[5] = {0x62, 0xFF, 0x02, 0xFF, 0x29}; -static const byte KOSTAL_FRAMEHEADER2[5] = {0x63, 0xFF, 0x02, 0xFF, 0x29}; +static const uint8_t KOSTAL_FRAMEHEADER[5] = {0x62, 0xFF, 0x02, 0xFF, 0x29}; +static const uint8_t KOSTAL_FRAMEHEADER2[5] = {0x63, 0xFF, 0x02, 0xFF, 0x29}; static uint16_t discharge_current = 0; static uint16_t charge_current = 0; @@ -14,63 +14,60 @@ union f32b { byte b[4]; }; -byte frame1[40] = { +uint8_t frame1[40] = { 0x06, 0xE2, 0xFF, 0x02, 0xFF, 0x29, 0x01, 0x08, 0x80, 0x43, // 256.063 Max Charge? first byte 0x01 or 0x04 0xE4, 0x70, 0x8A, 0x5C, 0xB5, 0x02, 0xD3, 0x01, 0x01, 0x05, 0xC8, 0x41, // 25.0024 TEMP?? 0xC2, 0x18, 0x01, 0x03, 0x59, 0x42, 0x01, 0x01, 0x01, 0x02, 0x05, 0x02, 0xA0, 0x01, 0x01, 0x02, 0x4D, 0x00}; -// -// values will be overwritten at update_modbus_registers_inverter() -// -byte frame2[64] = {0x0A, 0xE2, 0xFF, 0x02, 0xFF, 0x29, // frame Header - 0x1D, 0x5A, 0x85, 0x43, // Voltage (float) Modbus register 216 +// values in frame2 will be overwritten at update_modbus_registers_inverter() +uint8_t frame2[64] = {0x0A, 0xE2, 0xFF, 0x02, 0xFF, 0x29, // frame Header + 0x1D, 0x5A, 0x85, 0x43, // Voltage (float) Modbus register 216 - 0x01, 0x03, 0x8D, 0x43, // Max Voltage (2 byte float) + 0x01, 0x03, 0x8D, 0x43, // Max Voltage (2 byte float) - 0x01, 0x03, 0xAC, 0x41, // Temp (2 byte float) Modbus register 214 - 0x01, 0x01, 0x01, 0x01, // Current - 0x01, 0x01, 0x01, 0x01, // Current + 0x01, 0x03, 0xAC, 0x41, // Temp (2 byte float) Modbus register 214 + 0x01, 0x01, 0x01, 0x01, // Current + 0x01, 0x01, 0x01, 0x01, // Current - 0x01, 0x03, 0x48, 0x42, // Peak discharge current (2 byte float) + 0x01, 0x03, 0x48, 0x42, // Peak discharge current (2 byte float) - 0x01, 0x03, 0xC8, 0x41, // Nominal discharge I (2 byte float) + 0x01, 0x03, 0xC8, 0x41, // Nominal discharge I (2 byte float) - 0x01, 0x01, // Unknown - 0x01, 0x05, // Max charge? (2 byte float) + 0x01, 0x01, // Unknown + 0x01, 0x05, // Max charge? (2 byte float) - 0xCD, 0xCC, // Unknown - 0xB4, 0x41, // MaxCellTemp (2 byte float) + 0xCD, 0xCC, // Unknown + 0xB4, 0x41, // MaxCellTemp (2 byte float) - 0x01, 0x0C, // Maybe cell information? - 0xA4, 0x41, // MinCellTemp (2 byte float) + 0x01, 0x0C, // Maybe cell information? + 0xA4, 0x41, // MinCellTemp (2 byte float) - 0xA4, 0x70, 0x55, 0x40, // MaxCellVolt (float) - 0x7D, 0x3F, 0x55, 0x40, // MinCellVolt (float) + 0xA4, 0x70, 0x55, 0x40, // MaxCellVolt (float) + 0x7D, 0x3F, 0x55, 0x40, // MinCellVolt (float) - 0xFE, // Cylce count - 0x04, // Cycle count? - 0x01, 0x40, // ?? - 0x64, // SOC - 0x01, // Unknown, Mostly 0x01, seen also 0x02 - 0x01, // Unknown, Seen only 0x01 - 0x02, // Unknown, Mostly 0x02. seen also 0x01 - 0x00, // CRC (inverted sum of bytes 1-62 + 0xC0) - 0x00}; + 0xFE, // Cylce count + 0x04, // Cycle count? + 0x01, 0x40, // ?? + 0x64, // SOC + 0x01, // Unknown, Mostly 0x01, seen also 0x02 + 0x01, // Unknown, Seen only 0x01 + 0x02, // Unknown, Mostly 0x02. seen also 0x01 + 0x00, // CRC (inverted sum of bytes 1-62 + 0xC0) + 0x00}; -byte frame3[9] = { +uint8_t frame3[9] = { 0x08, 0xE2, 0xFF, 0x02, 0xFF, 0x29, //header 0x06, //Unknown 0xEF, //CRC 0x00 //endbyte }; -byte frameB1[10] = {0x07, 0x63, 0xFF, 0x02, 0xFF, 0x29, 0x5E, 0x02, 0x16, 0x00}; -byte frameB1b[10] = {0x07, 0xE3, 0xFF, 0x02, 0xFF, 0x29, 0xF4, 0x00}; +uint8_t frameB1[10] = {0x07, 0x63, 0xFF, 0x02, 0xFF, 0x29, 0x5E, 0x02, 0x16, 0x00}; +uint8_t frameB1b[10] = {0x07, 0xE3, 0xFF, 0x02, 0xFF, 0x29, 0xF4, 0x00}; -byte RS485_RXFRAME[10]; -size_t RS485_RECEIVEDBYTES; +uint8_t RS485_RXFRAME[10]; -boolean reg_data_ok = false; +bool register_content_ok = false; void float2frame(byte* arr, float value, byte framepointer) { f32b g; @@ -111,7 +108,7 @@ byte calculate_longframe_crc(byte* lfc, int lastbyte) { return ((byte) ~(sum + 0xc0) & 0xff); } -boolean check_kostal_frame_crc() { +bool check_kostal_frame_crc() { unsigned int sum = 0; for (int i = 1; i < 8; ++i) { sum += RS485_RXFRAME[i]; @@ -123,19 +120,18 @@ boolean check_kostal_frame_crc() { } } -byte rx_index = 0; +static uint8_t rx_index = 0; void receive_RS485() // Runs as fast as possible to handle the serial stream { if (Serial2.available()) { RS485_RXFRAME[rx_index] = Serial2.read(); rx_index++; - if (RS485_RXFRAME[rx_index - 1] == 0x00) // - { - if ((rx_index == 10) && (RS485_RXFRAME[0] == 0x09) && reg_data_ok) { + if (RS485_RXFRAME[rx_index - 1] == 0x00) { + if ((rx_index == 10) && (RS485_RXFRAME[0] == 0x09) && register_content_ok) { #ifdef DEBUG_KOSTAL_RS485_DATA Serial.print("RX: "); - for (int i = 0; i < 10; i++) { + for (uint8_t i = 0; i < 10; i++) { Serial.print(RS485_RXFRAME[i], HEX); Serial.print(" "); } @@ -143,32 +139,29 @@ void receive_RS485() // Runs as fast as possible to handle the serial stream #endif rx_index = 0; if (check_kostal_frame_crc()) { - boolean notheaderA = 0; - boolean notheaderB = 0; - for (int i = 0; i < 5; i++) { + bool headerA = true; + bool headerB = true; + for (uint8_t i = 0; i < 5; i++) { if (RS485_RXFRAME[i + 1] != KOSTAL_FRAMEHEADER[i]) { - notheaderA = true; + headerA = false; } if (RS485_RXFRAME[i + 1] != KOSTAL_FRAMEHEADER2[i]) { - notheaderB = true; + headerB = false; } } - if (!notheaderB && (RS485_RXFRAME[6] == 0x5E) && - (RS485_RXFRAME[7] == 0xFF)) // "frame B1", maybe reset request, seen after battery power on/partial data - { + // "frame B1", maybe reset request, seen after battery power on/partial data + if (headerB && (RS485_RXFRAME[6] == 0x5E) && (RS485_RXFRAME[7] == 0xFF)) { send_kostal(frameB1, 10); Serial2.flush(); delay(1); send_kostal(frameB1b, 10); } - if (!notheaderA && (RS485_RXFRAME[6] == 0x4A) && (RS485_RXFRAME[7] == 0x08)) // "frame 1" - { + if (headerA && (RS485_RXFRAME[6] == 0x4A) && (RS485_RXFRAME[7] == 0x08)) { // "frame 1" send_kostal(frame1, 40); } - if (!notheaderA && (RS485_RXFRAME[6] == 0x4A) && (RS485_RXFRAME[7] == 0x04)) // "frame 2" - { + if (headerA && (RS485_RXFRAME[6] == 0x4A) && (RS485_RXFRAME[7] == 0x04)) { // "frame 2" byte tmpframe[64]; //copy values to prevent data manipulation during rewrite/crc calculation memcpy(tmpframe, frame2, 64); for (int i = 1; i < 63; i++) { @@ -179,15 +172,14 @@ void receive_RS485() // Runs as fast as possible to handle the serial stream tmpframe[62] = calculate_longframe_crc(tmpframe, 62); send_kostal(tmpframe, 64); } - if (!notheaderA && (RS485_RXFRAME[6] == 0x53) && (RS485_RXFRAME[7] == 0x03)) // "frame 3" - { + if (headerA && (RS485_RXFRAME[6] == 0x53) && (RS485_RXFRAME[7] == 0x03)) { // "frame 3" send_kostal(frame3, 9); } } } rx_index = 0; } - if (rx_index == 10) { + if (rx_index >= 10) { rx_index = 0; } } @@ -242,6 +234,6 @@ void update_RS485_registers_inverter() { frame2[58] = (byte)datalayer.battery.status.reported_soc / 100; - reg_data_ok = true; + register_content_ok = true; } #endif diff --git a/Software/src/inverter/KOSTAL-RS485.h b/Software/src/inverter/KOSTAL-RS485.h index a89b8c81..22598b29 100644 --- a/Software/src/inverter/KOSTAL-RS485.h +++ b/Software/src/inverter/KOSTAL-RS485.h @@ -4,6 +4,6 @@ #include "../include.h" #define RS485_INVERTER_SELECTED -#define DEBUG_KOSTAL_RS485_DATA +#define DEBUG_KOSTAL_RS485_DATA // Enable this line to get TX / RX printed out via serial #endif From 79ae74122a50f9741ce20af6de82b8c08754f46a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Sun, 1 Sep 2024 17:42:21 +0300 Subject: [PATCH 007/210] Fix SOC% value --- Software/USER_SETTINGS.h | 4 ++-- Software/src/inverter/KOSTAL-RS485.cpp | 20 ++++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Software/USER_SETTINGS.h b/Software/USER_SETTINGS.h index fb7ebe51..57153f02 100644 --- a/Software/USER_SETTINGS.h +++ b/Software/USER_SETTINGS.h @@ -17,7 +17,7 @@ //#define KIA_E_GMP_BATTERY //#define KIA_HYUNDAI_HYBRID_BATTERY //#define MG_5_BATTERY -#define NISSAN_LEAF_BATTERY +//#define NISSAN_LEAF_BATTERY //#define PYLON_BATTERY //#define RENAULT_KANGOO_BATTERY //#define RENAULT_ZOE_GEN1_BATTERY @@ -32,7 +32,7 @@ //#define BYD_CAN //Enable this line to emulate a "BYD Battery-Box Premium HVS" over CAN Bus //#define BYD_SMA //Enable this line to emulate a SMA compatible "BYD Battery-Box HVS 10.2KW battery" over CAN bus //#define BYD_MODBUS //Enable this line to emulate a "BYD 11kWh HVM battery" over Modbus RTU -#define BYD_KOSTAL_RS485 //Enable this line to emulate a "BYD 11kWh HVM battery" over Kostal RS485 +//#define BYD_KOSTAL_RS485 //Enable this line to emulate a "BYD 11kWh HVM battery" over Kostal RS485 //#define PYLON_CAN //Enable this line to emulate a "Pylontech battery" over CAN bus //#define SMA_CAN //Enable this line to emulate a "BYD Battery-Box H 8.9kWh, 7 mod" over CAN bus //#define SMA_TRIPOWER_CAN //Enable this line to emulate a "SMA Home Storage battery" over CAN bus diff --git a/Software/src/inverter/KOSTAL-RS485.cpp b/Software/src/inverter/KOSTAL-RS485.cpp index f74eadf7..2f3f62f3 100644 --- a/Software/src/inverter/KOSTAL-RS485.cpp +++ b/Software/src/inverter/KOSTAL-RS485.cpp @@ -213,26 +213,26 @@ void update_RS485_registers_inverter() { .max_discharge_amp_dA; //Cap the value to the max allowed Amp. Some inverters cannot handle large values. } - float2frame(frame2, (float)datalayer.battery.status.voltage_dV / 10, 6); + float2frame(frame2, (float)(datalayer.battery.status.voltage_dV / 10), 6); - float2frameMSB(frame2, (float)datalayer.battery.info.max_design_voltage_dV / 10, 12); + float2frameMSB(frame2, (float)(datalayer.battery.info.max_design_voltage_dV / 10), 12); float2frameMSB( - frame2, (float)(datalayer.battery.status.temperature_max_dC + datalayer.battery.status.temperature_min_dC) / 20, + frame2, (float)((datalayer.battery.status.temperature_max_dC + datalayer.battery.status.temperature_min_dC) / 20), 16); float2frameMSB(frame2, (float)datalayer.battery.status.current_dA, 20); float2frameMSB(frame2, (float)datalayer.battery.status.current_dA, 24); - float2frameMSB(frame2, (float)discharge_current / 10, 28); - float2frameMSB(frame2, (float)discharge_current / 10, 32); + float2frameMSB(frame2, (float)(discharge_current / 10), 28); + float2frameMSB(frame2, (float)(discharge_current / 10), 32); - float2frameMSB(frame2, (float)datalayer.battery.status.temperature_max_dC / 10, 40); - float2frameMSB(frame2, (float)datalayer.battery.status.temperature_min_dC / 10, 44); + float2frameMSB(frame2, (float)(datalayer.battery.status.temperature_max_dC / 10), 40); + float2frameMSB(frame2, (float)(datalayer.battery.status.temperature_min_dC / 10), 44); - float2frame(frame2, (float)datalayer.battery.status.cell_max_voltage_mV / 1000, 46); - float2frame(frame2, (float)datalayer.battery.status.cell_min_voltage_mV / 1000, 50); + float2frame(frame2, (float)(datalayer.battery.status.cell_max_voltage_mV / 1000), 46); + float2frame(frame2, (float)(datalayer.battery.status.cell_min_voltage_mV / 1000), 50); - frame2[58] = (byte)datalayer.battery.status.reported_soc / 100; + frame2[58] = (byte)(datalayer.battery.status.reported_soc / 100); register_content_ok = true; } From d3eeeca7b3942f8b2404a67cc86a4d4c606a9ab2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Mon, 2 Sep 2024 22:20:26 +0300 Subject: [PATCH 008/210] Tweaks to protocol --- Software/src/inverter/KOSTAL-RS485.cpp | 83 ++++++++++++++------------ 1 file changed, 46 insertions(+), 37 deletions(-) diff --git a/Software/src/inverter/KOSTAL-RS485.cpp b/Software/src/inverter/KOSTAL-RS485.cpp index 2f3f62f3..aa00ccb0 100644 --- a/Software/src/inverter/KOSTAL-RS485.cpp +++ b/Software/src/inverter/KOSTAL-RS485.cpp @@ -6,8 +6,9 @@ static const uint8_t KOSTAL_FRAMEHEADER[5] = {0x62, 0xFF, 0x02, 0xFF, 0x29}; static const uint8_t KOSTAL_FRAMEHEADER2[5] = {0x63, 0xFF, 0x02, 0xFF, 0x29}; -static uint16_t discharge_current = 0; -static uint16_t charge_current = 0; +static uint16_t discharge_current_dA = 0; +static uint16_t charge_current_dA = 0; +static int16_t average_temperature_dC = 0; union f32b { float f; @@ -21,38 +22,40 @@ uint8_t frame1[40] = { // values in frame2 will be overwritten at update_modbus_registers_inverter() uint8_t frame2[64] = {0x0A, 0xE2, 0xFF, 0x02, 0xFF, 0x29, // frame Header - 0x1D, 0x5A, 0x85, 0x43, // Voltage (float) Modbus register 216 + 0x1D, 0x5A, 0x85, 0x43, // Voltage (float) Modbus register 216, Bit 6-9 - 0x01, 0x03, 0x8D, 0x43, // Max Voltage (2 byte float) + 0x01, 0x03, 0x8D, 0x43, // Max Voltage (2 byte float), Bit 10-13 + // 0x8D43 = 36163 (361.63) DALA: Is this nominal voltage? + 0x01, 0x03, 0xAC, 0x41, // Temp (2 byte float) Modbus register 214, Bit 14-17 + 0x01, 0x01, 0x01, 0x01, // Current, Bit 18-21 + 0x01, 0x01, 0x01, 0x01, // Current, Bit 22-25 - 0x01, 0x03, 0xAC, 0x41, // Temp (2 byte float) Modbus register 214 - 0x01, 0x01, 0x01, 0x01, // Current - 0x01, 0x01, 0x01, 0x01, // Current + 0x01, 0x03, 0x48, 0x42, // Peak discharge current (2 byte float), Bit 26-29 - 0x01, 0x03, 0x48, 0x42, // Peak discharge current (2 byte float) - - 0x01, 0x03, 0xC8, 0x41, // Nominal discharge I (2 byte float) + 0x01, 0x03, 0xC8, 0x41, // Nominal discharge I (2 byte float) , Bit 30-33 0x01, 0x01, // Unknown - 0x01, 0x05, // Max charge? (2 byte float) + 0x01, 0x05, // Max charge? (2 byte float) Bit 36-37 0xCD, 0xCC, // Unknown - 0xB4, 0x41, // MaxCellTemp (2 byte float) + 0xB4, 0x41, // MaxCellTemp (2 byte float) Bit 40-41 0x01, 0x0C, // Maybe cell information? - 0xA4, 0x41, // MinCellTemp (2 byte float) + 0xA4, 0x41, // MinCellTemp (2 byte float) Bit 44-45 - 0xA4, 0x70, 0x55, 0x40, // MaxCellVolt (float) - 0x7D, 0x3F, 0x55, 0x40, // MinCellVolt (float) + 0xA4, 0x70, 0x55, 0x40, // MaxCellVolt (float), Bit 46-49 + // 0xA470 = 4.2096V + 0x7D, 0x3F, 0x55, 0x40, // MinCellVolt (float), Bit 50-53 + // 0x7D3F = 3.2063V - 0xFE, // Cylce count - 0x04, // Cycle count? - 0x01, 0x40, // ?? - 0x64, // SOC + 0xFE, // Cylce count , Bit 54 + 0x04, // Cycle count? , Bit 55 + 0x01, 0x40, // ?? , Bit 56, 57 + 0x64, // SOC , Bit 58 0x01, // Unknown, Mostly 0x01, seen also 0x02 0x01, // Unknown, Seen only 0x01 0x02, // Unknown, Mostly 0x02. seen also 0x01 - 0x00, // CRC (inverted sum of bytes 1-62 + 0xC0) + 0x00, // CRC (inverted sum of bytes 1-62 + 0xC0), Bit 62 0x00}; uint8_t frame3[9] = { @@ -188,43 +191,49 @@ void receive_RS485() // Runs as fast as possible to handle the serial stream void update_RS485_registers_inverter() { if (datalayer.battery.status.voltage_dV > 10) { // Only update value when we have voltage available to avoid div0 - charge_current = + charge_current_dA = ((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) + charge_current_dA = (charge_current_dA * 10); //Value needs a decimal before getting sent to inverter (81.0A) - discharge_current = + discharge_current_dA = ((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) + discharge_current_dA = (discharge_current_dA * 10); //Value needs a decimal before getting sent to inverter (81.0A) } - if (charge_current > datalayer.battery.info.max_charge_amp_dA) { - charge_current = + if (charge_current_dA > datalayer.battery.info.max_charge_amp_dA) { + charge_current_dA = 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 = + if (discharge_current_dA > datalayer.battery.info.max_discharge_amp_dA) { + discharge_current_dA = datalayer.battery.info .max_discharge_amp_dA; //Cap the value to the max allowed Amp. Some inverters cannot handle large values. } - float2frame(frame2, (float)(datalayer.battery.status.voltage_dV / 10), 6); + average_temperature_dC = + ((datalayer.battery.status.temperature_max_dC + datalayer.battery.status.temperature_min_dC) / 2); + if (datalayer.battery.status.temperature_min_dC < 0) { + average_temperature_dC = 0; + } + + float2frame(frame2, (float)(datalayer.battery.status.voltage_dV / 10), 6); // Confirmed OK mapping float2frameMSB(frame2, (float)(datalayer.battery.info.max_design_voltage_dV / 10), 12); - float2frameMSB( - frame2, (float)((datalayer.battery.status.temperature_max_dC + datalayer.battery.status.temperature_min_dC) / 20), - 16); - float2frameMSB(frame2, (float)datalayer.battery.status.current_dA, 20); - float2frameMSB(frame2, (float)datalayer.battery.status.current_dA, 24); + frame2[16] = (uint8_t)(average_temperature_dC / 10); + + float2frameMSB(frame2, (float)datalayer.battery.status.current_dA / 10, + 20); // Peak discharge? current (2 byte float) + float2frameMSB(frame2, (float)datalayer.battery.status.current_dA / 10, 24); - float2frameMSB(frame2, (float)(discharge_current / 10), 28); - float2frameMSB(frame2, (float)(discharge_current / 10), 32); + float2frameMSB(frame2, (float)(discharge_current_dA / 10), 28); // Nominal discharge? I (2 byte float) + float2frameMSB(frame2, (float)(discharge_current_dA / 10), 32); float2frameMSB(frame2, (float)(datalayer.battery.status.temperature_max_dC / 10), 40); float2frameMSB(frame2, (float)(datalayer.battery.status.temperature_min_dC / 10), 44); @@ -232,7 +241,7 @@ void update_RS485_registers_inverter() { float2frame(frame2, (float)(datalayer.battery.status.cell_max_voltage_mV / 1000), 46); float2frame(frame2, (float)(datalayer.battery.status.cell_min_voltage_mV / 1000), 50); - frame2[58] = (byte)(datalayer.battery.status.reported_soc / 100); + frame2[58] = (byte)(datalayer.battery.status.reported_soc / 100); // Confirmed OK mapping register_content_ok = true; } From 09e366df958d89de68bfebb387ab898cc7ebeba1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Mon, 16 Sep 2024 22:02:27 +0300 Subject: [PATCH 009/210] Add SOC candidates to USB printout --- Software/src/battery/BYD-ATTO-3-BATTERY.cpp | 22 ++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/Software/src/battery/BYD-ATTO-3-BATTERY.cpp b/Software/src/battery/BYD-ATTO-3-BATTERY.cpp index d36e4166..7dab03cc 100644 --- a/Software/src/battery/BYD-ATTO-3-BATTERY.cpp +++ b/Software/src/battery/BYD-ATTO-3-BATTERY.cpp @@ -26,6 +26,8 @@ static int16_t highest_temperature = 0; static int16_t calc_min_temperature = 0; static int16_t calc_max_temperature = 0; +static uint16_t highprecision_SOC = 0; +static uint16_t lowprecision_SOC = 0; static uint16_t BMS_SOC = 0; static uint16_t BMS_voltage = 0; static int16_t BMS_current = 0; @@ -129,6 +131,14 @@ void update_values_battery() { //This function maps all the values fetched via datalayer.battery.status.temperature_min_dC = calc_min_temperature * 10; // Add decimals datalayer.battery.status.temperature_max_dC = calc_max_temperature * 10; + //TODO: Remove once confirmed which work + Serial.print("Polled: "); + Serial.println(BMS_SOC); + Serial.print("Highprec: "); + Serial.println(highprecision_SOC); + Serial.print("Lowprec: "); + Serial.println(lowprecision_SOC); + #ifdef DEBUG_VIA_USB #endif @@ -209,16 +219,18 @@ void receive_can_battery(CAN_frame rx_frame) { case 0x444: //9E,01,88,13,64,64,98,65 //9A,01,B6,13,64,64,98,3B //407.5V 18deg //9B,01,B8,13,64,64,98,38 //408.5V 14deg + //lowprecision_SOC = ??? break; case 0x445: //00,98,FF,FF,63,20,4E,98 - Static, values never changes between logs break; case 0x446: //2C,D4,0C,4D,21,DC,0C,9D - 0,1,7th frame varies a lot break; - case 0x447: // Seems to contain more temperatures, highest and lowest? - //06,38,01,3B,E0,03,39,69 - //06,36,02,36,E0,03,36,72, - lowest_temperature = (rx_frame.data.u8[1] - 40); //Best guess for now - highest_temperature = (rx_frame.data.u8[3] - 40); //Best guess for now + case 0x447: // Seems to contain more temperatures, highest and lowest? + //06,38,01,3B,E0,03,39,69 + //06,36,02,36,E0,03,36,72, + highprecision_SOC = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4]; // 03 E0 = 992 = 99.2% + lowest_temperature = (rx_frame.data.u8[1] - 40); //Best guess for now + highest_temperature = (rx_frame.data.u8[3] - 40); //Best guess for now break; case 0x47B: //01,FF,FF,FF,FF,FF,FF,FF - Static, values never changes between logs break; From 198e9ff945f6829fd35c23a859e2c50a329e739e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Tue, 17 Sep 2024 12:00:04 +0300 Subject: [PATCH 010/210] Add way to not use estimated SOC --- Software/src/battery/BYD-ATTO-3-BATTERY.cpp | 18 +++++++----------- Software/src/battery/BYD-ATTO-3-BATTERY.h | 2 ++ 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/Software/src/battery/BYD-ATTO-3-BATTERY.cpp b/Software/src/battery/BYD-ATTO-3-BATTERY.cpp index 7dab03cc..a753d261 100644 --- a/Software/src/battery/BYD-ATTO-3-BATTERY.cpp +++ b/Software/src/battery/BYD-ATTO-3-BATTERY.cpp @@ -93,10 +93,14 @@ void update_values_battery() { //This function maps all the values fetched via datalayer.battery.status.voltage_dV = BMS_voltage * 10; } - //datalayer.battery.status.real_soc = BMS_SOC * 100; //TODO: This is not yet found! - // We instead estimate the SOC% based on the battery voltage - // This is a very bad solution, and as soon as an usable SOC% value has been found on CAN, we should switch to that! +#ifdef USE_ESTIMATED_SOC + // When the battery is crashed hard, it locks itself and SOC becomes unavailable. + // We instead estimate the SOC% based on the battery voltage. + // This is a bad solution, you wont be able to use 100% of the battery datalayer.battery.status.real_soc = estimateSOC(datalayer.battery.status.voltage_dV); +#else // Pack is not crashed, we can use periodically transmitted SOC + datalayer.battery.status.real_soc = highprecision_SOC * 10; +#endif datalayer.battery.status.current_dA = -BMS_current; @@ -131,14 +135,6 @@ void update_values_battery() { //This function maps all the values fetched via datalayer.battery.status.temperature_min_dC = calc_min_temperature * 10; // Add decimals datalayer.battery.status.temperature_max_dC = calc_max_temperature * 10; - //TODO: Remove once confirmed which work - Serial.print("Polled: "); - Serial.println(BMS_SOC); - Serial.print("Highprec: "); - Serial.println(highprecision_SOC); - Serial.print("Lowprec: "); - Serial.println(lowprecision_SOC); - #ifdef DEBUG_VIA_USB #endif diff --git a/Software/src/battery/BYD-ATTO-3-BATTERY.h b/Software/src/battery/BYD-ATTO-3-BATTERY.h index 550975d5..5f4a7d71 100644 --- a/Software/src/battery/BYD-ATTO-3-BATTERY.h +++ b/Software/src/battery/BYD-ATTO-3-BATTERY.h @@ -5,6 +5,8 @@ #define BATTERY_SELECTED #define MAX_CELL_DEVIATION_MV 150 +#define USE_ESTIMATED_SOC // If enabled, SOC is estimated from pack voltage. Useful for locked packs. \ + // Uncomment this if you know your BMS is unlocked and able to send SOC% void setup_battery(void); void transmit_can(CAN_frame* tx_frame, int interface); From af45cbd2eb67c7444a5212ef1741f9e16cebb6a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Tue, 17 Sep 2024 21:53:04 +0300 Subject: [PATCH 011/210] Fix temperature and cellvoltage mapping --- Software/src/inverter/KOSTAL-RS485.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Software/src/inverter/KOSTAL-RS485.cpp b/Software/src/inverter/KOSTAL-RS485.cpp index aa00ccb0..9367cbdf 100644 --- a/Software/src/inverter/KOSTAL-RS485.cpp +++ b/Software/src/inverter/KOSTAL-RS485.cpp @@ -226,7 +226,7 @@ void update_RS485_registers_inverter() { float2frameMSB(frame2, (float)(datalayer.battery.info.max_design_voltage_dV / 10), 12); - frame2[16] = (uint8_t)(average_temperature_dC / 10); + float2frameMSB(frame2, (float)average_temperature_dC, 16); float2frameMSB(frame2, (float)datalayer.battery.status.current_dA / 10, 20); // Peak discharge? current (2 byte float) @@ -238,8 +238,8 @@ void update_RS485_registers_inverter() { float2frameMSB(frame2, (float)(datalayer.battery.status.temperature_max_dC / 10), 40); float2frameMSB(frame2, (float)(datalayer.battery.status.temperature_min_dC / 10), 44); - float2frame(frame2, (float)(datalayer.battery.status.cell_max_voltage_mV / 1000), 46); - float2frame(frame2, (float)(datalayer.battery.status.cell_min_voltage_mV / 1000), 50); + float2frameMSB(frame2, (float)(datalayer.battery.status.cell_max_voltage_mV / 1000), 46); + float2frameMSB(frame2, (float)(datalayer.battery.status.cell_min_voltage_mV / 1000), 50); frame2[58] = (byte)(datalayer.battery.status.reported_soc / 100); // Confirmed OK mapping From 2fbcacbb3c9bba01af40f4dd500d5619c69ca5bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Tue, 17 Sep 2024 22:01:52 +0300 Subject: [PATCH 012/210] Add back /10 --- Software/src/inverter/KOSTAL-RS485.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Software/src/inverter/KOSTAL-RS485.cpp b/Software/src/inverter/KOSTAL-RS485.cpp index 9367cbdf..6f8990b9 100644 --- a/Software/src/inverter/KOSTAL-RS485.cpp +++ b/Software/src/inverter/KOSTAL-RS485.cpp @@ -226,7 +226,7 @@ void update_RS485_registers_inverter() { float2frameMSB(frame2, (float)(datalayer.battery.info.max_design_voltage_dV / 10), 12); - float2frameMSB(frame2, (float)average_temperature_dC, 16); + float2frameMSB(frame2, (float)(average_temperature_dC / 10), 16); float2frameMSB(frame2, (float)datalayer.battery.status.current_dA / 10, 20); // Peak discharge? current (2 byte float) From 0bee82372e10848de93637587107623e59462fc6 Mon Sep 17 00:00:00 2001 From: rha Date: Tue, 17 Sep 2024 22:13:13 +0300 Subject: [PATCH 013/210] More temperature fixes --- Software/src/inverter/KOSTAL-RS485.cpp | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/Software/src/inverter/KOSTAL-RS485.cpp b/Software/src/inverter/KOSTAL-RS485.cpp index 6f8990b9..fc56ae3e 100644 --- a/Software/src/inverter/KOSTAL-RS485.cpp +++ b/Software/src/inverter/KOSTAL-RS485.cpp @@ -37,16 +37,13 @@ uint8_t frame2[64] = {0x0A, 0xE2, 0xFF, 0x02, 0xFF, 0x29, // frame Header 0x01, 0x01, // Unknown 0x01, 0x05, // Max charge? (2 byte float) Bit 36-37 - 0xCD, 0xCC, // Unknown - 0xB4, 0x41, // MaxCellTemp (2 byte float) Bit 40-41 + 0xCD, 0xCC, 0xB4, 0x41, // MaxCellTemp (4 byte float) Bit 38-41 - 0x01, 0x0C, // Maybe cell information? - 0xA4, 0x41, // MinCellTemp (2 byte float) Bit 44-45 + 0x01, 0x0C, 0xA4, 0x41, // MinCellTemp (4 byte float) Bit 42-45 0xA4, 0x70, 0x55, 0x40, // MaxCellVolt (float), Bit 46-49 - // 0xA470 = 4.2096V + 0x7D, 0x3F, 0x55, 0x40, // MinCellVolt (float), Bit 50-53 - // 0x7D3F = 3.2063V 0xFE, // Cylce count , Bit 54 0x04, // Cycle count? , Bit 55 @@ -228,18 +225,17 @@ void update_RS485_registers_inverter() { float2frameMSB(frame2, (float)(average_temperature_dC / 10), 16); - float2frameMSB(frame2, (float)datalayer.battery.status.current_dA / 10, - 20); // Peak discharge? current (2 byte float) + float2frameMSB(frame2, (float)datalayer.battery.status.current_dA / 10, 20); // Peak discharge? current (2 byte float) float2frameMSB(frame2, (float)datalayer.battery.status.current_dA / 10, 24); float2frameMSB(frame2, (float)(discharge_current_dA / 10), 28); // Nominal discharge? I (2 byte float) float2frameMSB(frame2, (float)(discharge_current_dA / 10), 32); - float2frameMSB(frame2, (float)(datalayer.battery.status.temperature_max_dC / 10), 40); - float2frameMSB(frame2, (float)(datalayer.battery.status.temperature_min_dC / 10), 44); + float2frame(frame2, (float)(datalayer.battery.status.temperature_max_dC / 10), 40); + float2frame(frame2, (float)(datalayer.battery.status.temperature_min_dC / 10), 44); - float2frameMSB(frame2, (float)(datalayer.battery.status.cell_max_voltage_mV / 1000), 46); - float2frameMSB(frame2, (float)(datalayer.battery.status.cell_min_voltage_mV / 1000), 50); + float2frame(frame2, (float)(datalayer.battery.status.cell_max_voltage_mV / 1000), 46); + float2frame(frame2, (float)(datalayer.battery.status.cell_min_voltage_mV / 1000), 50); frame2[58] = (byte)(datalayer.battery.status.reported_soc / 100); // Confirmed OK mapping From ea2d0f7e458e130a8f1863baf26b61fc907cb357 Mon Sep 17 00:00:00 2001 From: rha Date: Wed, 18 Sep 2024 17:25:20 +0300 Subject: [PATCH 014/210] temp & current fix --- Software/src/inverter/KOSTAL-RS485.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Software/src/inverter/KOSTAL-RS485.cpp b/Software/src/inverter/KOSTAL-RS485.cpp index fc56ae3e..f5607737 100644 --- a/Software/src/inverter/KOSTAL-RS485.cpp +++ b/Software/src/inverter/KOSTAL-RS485.cpp @@ -34,8 +34,8 @@ uint8_t frame2[64] = {0x0A, 0xE2, 0xFF, 0x02, 0xFF, 0x29, // frame Header 0x01, 0x03, 0xC8, 0x41, // Nominal discharge I (2 byte float) , Bit 30-33 - 0x01, 0x01, // Unknown - 0x01, 0x05, // Max charge? (2 byte float) Bit 36-37 + 0x01, 0x16, // Unknown + 0xA0, 0x41, // Max charge? (2 byte float) Bit 36-37 0xCD, 0xCC, 0xB4, 0x41, // MaxCellTemp (4 byte float) Bit 38-41 @@ -230,9 +230,10 @@ void update_RS485_registers_inverter() { float2frameMSB(frame2, (float)(discharge_current_dA / 10), 28); // Nominal discharge? I (2 byte float) float2frameMSB(frame2, (float)(discharge_current_dA / 10), 32); + float2frameMSB(frame2, (float)(charge_current_dA / 10), 36); - float2frame(frame2, (float)(datalayer.battery.status.temperature_max_dC / 10), 40); - float2frame(frame2, (float)(datalayer.battery.status.temperature_min_dC / 10), 44); + float2frame(frame2, (float)(datalayer.battery.status.temperature_max_dC / 10), 38); + float2frame(frame2, (float)(datalayer.battery.status.temperature_min_dC / 10), 42); float2frame(frame2, (float)(datalayer.battery.status.cell_max_voltage_mV / 1000), 46); float2frame(frame2, (float)(datalayer.battery.status.cell_min_voltage_mV / 1000), 50); From 91231983610fdcac75560a7ee6b84b2d853b7f65 Mon Sep 17 00:00:00 2001 From: rha Date: Sat, 21 Sep 2024 07:47:19 +0300 Subject: [PATCH 015/210] frame 1 reverse engineering.. --- Software/src/inverter/KOSTAL-RS485.cpp | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/Software/src/inverter/KOSTAL-RS485.cpp b/Software/src/inverter/KOSTAL-RS485.cpp index f5607737..6e68a404 100644 --- a/Software/src/inverter/KOSTAL-RS485.cpp +++ b/Software/src/inverter/KOSTAL-RS485.cpp @@ -15,10 +15,26 @@ union f32b { byte b[4]; }; + + + uint8_t frame1[40] = { - 0x06, 0xE2, 0xFF, 0x02, 0xFF, 0x29, 0x01, 0x08, 0x80, 0x43, // 256.063 Max Charge? first byte 0x01 or 0x04 - 0xE4, 0x70, 0x8A, 0x5C, 0xB5, 0x02, 0xD3, 0x01, 0x01, 0x05, 0xC8, 0x41, // 25.0024 TEMP?? - 0xC2, 0x18, 0x01, 0x03, 0x59, 0x42, 0x01, 0x01, 0x01, 0x02, 0x05, 0x02, 0xA0, 0x01, 0x01, 0x02, 0x4D, 0x00}; + 0x06, 0xE2, 0xFF, 0x02, 0xFF, 0x29, // Frame header + 0x01, 0x08, 0x80, 0x43, // 256.063 Nominal voltage / 5*51.2=256 first byte 0x01 or 0x04 + 0xE4, 0x70, 0x8A, 0x5C, // 266.74 + 0xB5, 0x02, 0xD3, 0x01, // Battery Serial number? Modbus register 527 + 0x01, 0x05, 0xC8, 0x41, // 25.0024 Battery capacity?? + 0xC2, 0x18, // Battery Firmware, modbus register 586 + 0x01, 0x03, 0x59, 0x42, // 0x00005942 = 54.25 + 0x01, 0x01, 0x01, 0x02, + 0x05, 0x02, 0xA0, 0x01, 0x01, 0x02, + 0x4D, // CRC + 0x00}; // + // We have also modbus registers: + // 512: Battery gross capacity, value, U32 0x00000019 = 25 + // 588: battery type, value 0x0004, that's from kostal settings. + // + // values in frame2 will be overwritten at update_modbus_registers_inverter() uint8_t frame2[64] = {0x0A, 0xE2, 0xFF, 0x02, 0xFF, 0x29, // frame Header From 52f140861f5393e95ff0d6c85967c899a0d90872 Mon Sep 17 00:00:00 2001 From: rha Date: Sat, 21 Sep 2024 21:45:00 +0300 Subject: [PATCH 016/210] kostal registers... --- Software/src/inverter/KOSTAL-RS485.cpp | 77 ++++++++++++++------------ 1 file changed, 43 insertions(+), 34 deletions(-) diff --git a/Software/src/inverter/KOSTAL-RS485.cpp b/Software/src/inverter/KOSTAL-RS485.cpp index 6e68a404..6b0343d4 100644 --- a/Software/src/inverter/KOSTAL-RS485.cpp +++ b/Software/src/inverter/KOSTAL-RS485.cpp @@ -23,53 +23,49 @@ uint8_t frame1[40] = { 0x01, 0x08, 0x80, 0x43, // 256.063 Nominal voltage / 5*51.2=256 first byte 0x01 or 0x04 0xE4, 0x70, 0x8A, 0x5C, // 266.74 0xB5, 0x02, 0xD3, 0x01, // Battery Serial number? Modbus register 527 - 0x01, 0x05, 0xC8, 0x41, // 25.0024 Battery capacity?? + 0x01, 0x05, 0xC8, 0x41, // 25.0024 ? 0xC2, 0x18, // Battery Firmware, modbus register 586 - 0x01, 0x03, 0x59, 0x42, // 0x00005942 = 54.25 + 0x01, 0x03, 0x59, 0x42, // 0x00005942 = 54.25 ?? 0x01, 0x01, 0x01, 0x02, 0x05, 0x02, 0xA0, 0x01, 0x01, 0x02, 0x4D, // CRC 0x00}; // - // We have also modbus registers: - // 512: Battery gross capacity, value, U32 0x00000019 = 25 - // 588: battery type, value 0x0004, that's from kostal settings. - // // values in frame2 will be overwritten at update_modbus_registers_inverter() uint8_t frame2[64] = {0x0A, 0xE2, 0xFF, 0x02, 0xFF, 0x29, // frame Header - 0x1D, 0x5A, 0x85, 0x43, // Voltage (float) Modbus register 216, Bit 6-9 - - 0x01, 0x03, 0x8D, 0x43, // Max Voltage (2 byte float), Bit 10-13 - // 0x8D43 = 36163 (361.63) DALA: Is this nominal voltage? - 0x01, 0x03, 0xAC, 0x41, // Temp (2 byte float) Modbus register 214, Bit 14-17 - 0x01, 0x01, 0x01, 0x01, // Current, Bit 18-21 - 0x01, 0x01, 0x01, 0x01, // Current, Bit 22-25 - - 0x01, 0x03, 0x48, 0x42, // Peak discharge current (2 byte float), Bit 26-29 - - 0x01, 0x03, 0xC8, 0x41, // Nominal discharge I (2 byte float) , Bit 30-33 - - 0x01, 0x16, // Unknown - 0xA0, 0x41, // Max charge? (2 byte float) Bit 36-37 - - 0xCD, 0xCC, 0xB4, 0x41, // MaxCellTemp (4 byte float) Bit 38-41 + 0x1D, 0x5A, 0x85, 0x43, // Cyrrent Voltage (float) Modbus register 216, Bit 6-9 + 0x01, 0x03, 0x8D, 0x43, // Max Voltage (2 byte float), Bit 10-13 + // 0x8D43 = 36163 (361.63) DALA: Is this nominal voltage? + 0x01, 0x03, 0xAC, 0x41, // Temp (2 byte float) Modbus register 214, Bit 14-17 + 0x01, 0x01, 0x01, 0x01, // Current, Bit 18-21 + 0x01, 0x01, 0x01, 0x01, // Current, Bit 22-25 + 0x01, 0x03, 0x48, 0x42, // Max discharge current (2 byte float), Bit 26-29, + // Sunspec: ADisChaMax + 0x01, 0x03, // Unknown + 0xC8, 0x41, // Battery gross capacity, Ah (2 byte float) , Bytes 30-33, Modbus 512 + 0x01, 0x16, // Unknown + 0xA0, 0x41, // Max charge current (2 byte float) Bit 36-37 + // Sunspec: AChaMax + 0xCD, 0xCC, 0xB4, 0x41, // MaxCellTemp (4 byte float) Bit 38-41 0x01, 0x0C, 0xA4, 0x41, // MinCellTemp (4 byte float) Bit 42-45 + 0xA4, 0x70, 0x55, 0x40, // MaxCellVolt (float), Bit 46-49 + 0x7D, 0x3F, 0x55, 0x40, // MinCellVolt (float), Bit 50-53 + 0xFE, // Cylce count , Bit 54 + 0x04, // Cycle count? , Bit 55 + 0x01, 0x40, // ?? , Bit 56, 57 + 0x64, // SOC , Bit 58 + 0x01, // Unknown, Mostly 0x01, seen also 0x02 + 0x01, // Unknown, Seen only 0x01 + 0x02, // Unknown, Mostly 0x02. seen also 0x01 + 0x00, // CRC (inverted sum of bytes 1-62 + 0xC0), Bit 62 + 0x00}; - 0xA4, 0x70, 0x55, 0x40, // MaxCellVolt (float), Bit 46-49 - 0x7D, 0x3F, 0x55, 0x40, // MinCellVolt (float), Bit 50-53 +// FE 04 01 40 xx 01 01 02 yy (fully charged) +// FE 02 01 02 xx 01 01 02 yy (charging or discharging) - 0xFE, // Cylce count , Bit 54 - 0x04, // Cycle count? , Bit 55 - 0x01, 0x40, // ?? , Bit 56, 57 - 0x64, // SOC , Bit 58 - 0x01, // Unknown, Mostly 0x01, seen also 0x02 - 0x01, // Unknown, Seen only 0x01 - 0x02, // Unknown, Mostly 0x02. seen also 0x01 - 0x00, // CRC (inverted sum of bytes 1-62 + 0xC0), Bit 62 - 0x00}; uint8_t frame3[9] = { 0x08, 0xE2, 0xFF, 0x02, 0xFF, 0x29, //header @@ -124,6 +120,14 @@ byte calculate_longframe_crc(byte* lfc, int lastbyte) { return ((byte) ~(sum + 0xc0) & 0xff); } +byte calculate_frame1_crc(byte* lfc, int lastbyte) { + unsigned int sum = 0; + for (int i = 0; i < lastbyte; ++i) { + sum += lfc[i]; + } + return ((byte) ~(sum + 0x28) & 0xff); +} + bool check_kostal_frame_crc() { unsigned int sum = 0; for (int i = 1; i < 8; ++i) { @@ -237,6 +241,8 @@ void update_RS485_registers_inverter() { float2frame(frame2, (float)(datalayer.battery.status.voltage_dV / 10), 6); // Confirmed OK mapping + float2frame(frame1, (float)(datalayer.battery.status.voltage_dV / 10), 6); // This shall be nominal voltage, but not available + float2frameMSB(frame2, (float)(datalayer.battery.info.max_design_voltage_dV / 10), 12); float2frameMSB(frame2, (float)(average_temperature_dC / 10), 16); @@ -244,7 +250,8 @@ void update_RS485_registers_inverter() { float2frameMSB(frame2, (float)datalayer.battery.status.current_dA / 10, 20); // Peak discharge? current (2 byte float) float2frameMSB(frame2, (float)datalayer.battery.status.current_dA / 10, 24); - float2frameMSB(frame2, (float)(discharge_current_dA / 10), 28); // Nominal discharge? I (2 byte float) + float2frameMSB(frame2, (float)(discharge_current_dA / 10), 28); // BAttery capacity Ah + float2frameMSB(frame2, (float)(discharge_current_dA / 10), 32); float2frameMSB(frame2, (float)(charge_current_dA / 10), 36); @@ -257,5 +264,7 @@ void update_RS485_registers_inverter() { frame2[58] = (byte)(datalayer.battery.status.reported_soc / 100); // Confirmed OK mapping register_content_ok = true; + + frame1[38] = calculate_frame1_crc(frame1, 38); } #endif From c37bd3fcc1cdfccef7e30baf76000f2e8eb30682 Mon Sep 17 00:00:00 2001 From: rha Date: Sun, 22 Sep 2024 09:20:16 +0300 Subject: [PATCH 017/210] 100% SOC voltage definition and current limitation --- Software/src/inverter/KOSTAL-RS485.cpp | 32 +++++++++++++++++++------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/Software/src/inverter/KOSTAL-RS485.cpp b/Software/src/inverter/KOSTAL-RS485.cpp index 6b0343d4..d8bcecaf 100644 --- a/Software/src/inverter/KOSTAL-RS485.cpp +++ b/Software/src/inverter/KOSTAL-RS485.cpp @@ -21,7 +21,7 @@ union f32b { uint8_t frame1[40] = { 0x06, 0xE2, 0xFF, 0x02, 0xFF, 0x29, // Frame header 0x01, 0x08, 0x80, 0x43, // 256.063 Nominal voltage / 5*51.2=256 first byte 0x01 or 0x04 - 0xE4, 0x70, 0x8A, 0x5C, // 266.74 + 0xE4, 0x70, 0x8A, 0x5C, // 266.74 => Voltage at 100% SOC 0xB5, 0x02, 0xD3, 0x01, // Battery Serial number? Modbus register 527 0x01, 0x05, 0xC8, 0x41, // 25.0024 ? 0xC2, 0x18, // Battery Firmware, modbus register 586 @@ -39,22 +39,24 @@ uint8_t frame2[64] = {0x0A, 0xE2, 0xFF, 0x02, 0xFF, 0x29, // frame Header 0x01, 0x03, 0x8D, 0x43, // Max Voltage (2 byte float), Bit 10-13 // 0x8D43 = 36163 (361.63) DALA: Is this nominal voltage? 0x01, 0x03, 0xAC, 0x41, // Temp (2 byte float) Modbus register 214, Bit 14-17 - 0x01, 0x01, 0x01, 0x01, // Current, Bit 18-21 - 0x01, 0x01, 0x01, 0x01, // Current, Bit 22-25 + 0x01, 0x01, 0x01, 0x01, // Peak Current (1s period), Bytes 18-21 + 0x01, 0x01, 0x01, 0x01, // Avg current (1s period), Bytes 22-25 0x01, 0x03, 0x48, 0x42, // Max discharge current (2 byte float), Bit 26-29, // Sunspec: ADisChaMax 0x01, 0x03, // Unknown 0xC8, 0x41, // Battery gross capacity, Ah (2 byte float) , Bytes 30-33, Modbus 512 - 0x01, 0x16, // Unknown - 0xA0, 0x41, // Max charge current (2 byte float) Bit 36-37 - // Sunspec: AChaMax + 0x01, // Unknown + 0x16, // This seems to have something to do with cell temperatures + 0xA0, 0x41, // Max charge current (2 byte float) Bit 36-37, ZERO WHEN SOC=100 + // Sunspec: AChaMax 0xCD, 0xCC, 0xB4, 0x41, // MaxCellTemp (4 byte float) Bit 38-41 0x01, 0x0C, 0xA4, 0x41, // MinCellTemp (4 byte float) Bit 42-45 0xA4, 0x70, 0x55, 0x40, // MaxCellVolt (float), Bit 46-49 0x7D, 0x3F, 0x55, 0x40, // MinCellVolt (float), Bit 50-53 0xFE, // Cylce count , Bit 54 0x04, // Cycle count? , Bit 55 - 0x01, 0x40, // ?? , Bit 56, 57 + 0x01, // Byte 56 + 0x40, // When SOC=100 Byte57=0x40, otherwise 0x02 or 0x03 0x64, // SOC , Bit 58 0x01, // Unknown, Mostly 0x01, seen also 0x02 0x01, // Unknown, Seen only 0x01 @@ -221,6 +223,7 @@ void update_RS485_registers_inverter() { discharge_current_dA = (discharge_current_dA * 10); //Value needs a decimal before getting sent to inverter (81.0A) } + if (charge_current_dA > datalayer.battery.info.max_charge_amp_dA) { charge_current_dA = datalayer.battery.info @@ -253,7 +256,20 @@ void update_RS485_registers_inverter() { float2frameMSB(frame2, (float)(discharge_current_dA / 10), 28); // BAttery capacity Ah float2frameMSB(frame2, (float)(discharge_current_dA / 10), 32); - float2frameMSB(frame2, (float)(charge_current_dA / 10), 36); + + + // When SOC = 100%, drop down allowed charge current down. + + if((datalayer.battery.status.reported_soc / 100)<100) + { + float2frameMSB(frame2, (float)(charge_current_dA / 10), 36); + frame2[57]=0x02; + } + else + { + float2frameMSB(frame2, 0.0 , 36); + frame2[57]=0x40; + } float2frame(frame2, (float)(datalayer.battery.status.temperature_max_dC / 10), 38); float2frame(frame2, (float)(datalayer.battery.status.temperature_min_dC / 10), 42); From ff98551d38c79b382e4d44f44d492124efeb6641 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Sun, 22 Sep 2024 19:53:07 +0300 Subject: [PATCH 018/210] Add Event for missing RS485 comm --- Software/src/inverter/KOSTAL-RS485.cpp | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/Software/src/inverter/KOSTAL-RS485.cpp b/Software/src/inverter/KOSTAL-RS485.cpp index f5607737..19231836 100644 --- a/Software/src/inverter/KOSTAL-RS485.cpp +++ b/Software/src/inverter/KOSTAL-RS485.cpp @@ -4,11 +4,15 @@ #include "../devboard/utils/events.h" #include "KOSTAL-RS485.h" +#define RS485_HEALTHY \ + 12 // How many value updates we can go without inverter gets reported as missing \ + // e.g. value set to 12, 12*5sec=60seconds without comm before event is raised static const uint8_t KOSTAL_FRAMEHEADER[5] = {0x62, 0xFF, 0x02, 0xFF, 0x29}; static const uint8_t KOSTAL_FRAMEHEADER2[5] = {0x63, 0xFF, 0x02, 0xFF, 0x29}; static uint16_t discharge_current_dA = 0; static uint16_t charge_current_dA = 0; static int16_t average_temperature_dC = 0; +static uint8_t incoming_message_counter = RS485_HEALTHY; union f32b { float f; @@ -39,7 +43,7 @@ uint8_t frame2[64] = {0x0A, 0xE2, 0xFF, 0x02, 0xFF, 0x29, // frame Header 0xCD, 0xCC, 0xB4, 0x41, // MaxCellTemp (4 byte float) Bit 38-41 - 0x01, 0x0C, 0xA4, 0x41, // MinCellTemp (4 byte float) Bit 42-45 + 0x01, 0x0C, 0xA4, 0x41, // MinCellTemp (4 byte float) Bit 42-45 0xA4, 0x70, 0x55, 0x40, // MaxCellVolt (float), Bit 46-49 @@ -139,6 +143,7 @@ void receive_RS485() // Runs as fast as possible to handle the serial stream #endif rx_index = 0; if (check_kostal_frame_crc()) { + incoming_message_counter = RS485_HEALTHY; bool headerA = true; bool headerB = true; for (uint8_t i = 0; i < 5; i++) { @@ -225,7 +230,8 @@ void update_RS485_registers_inverter() { float2frameMSB(frame2, (float)(average_temperature_dC / 10), 16); - float2frameMSB(frame2, (float)datalayer.battery.status.current_dA / 10, 20); // Peak discharge? current (2 byte float) + float2frameMSB(frame2, (float)datalayer.battery.status.current_dA / 10, + 20); // Peak discharge? current (2 byte float) float2frameMSB(frame2, (float)datalayer.battery.status.current_dA / 10, 24); float2frameMSB(frame2, (float)(discharge_current_dA / 10), 28); // Nominal discharge? I (2 byte float) @@ -241,5 +247,15 @@ void update_RS485_registers_inverter() { frame2[58] = (byte)(datalayer.battery.status.reported_soc / 100); // Confirmed OK mapping register_content_ok = true; + + if (incoming_message_counter > 0) { + incoming_message_counter--; + } + + if (incoming_message_counter == 0) { + set_event(EVENT_MODBUS_INVERTER_MISSING, 0); + } else { + clear_event(EVENT_MODBUS_INVERTER_MISSING); + } } #endif From 9ea98275cf6d0ce8e4c476cfe540f1ee43db0c8b Mon Sep 17 00:00:00 2001 From: rha Date: Tue, 24 Sep 2024 21:37:06 +0300 Subject: [PATCH 019/210] frame 1 crc not working, will be fixed later --- Software/src/inverter/KOSTAL-RS485.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Software/src/inverter/KOSTAL-RS485.cpp b/Software/src/inverter/KOSTAL-RS485.cpp index 1d50ca6a..41c3ca0a 100644 --- a/Software/src/inverter/KOSTAL-RS485.cpp +++ b/Software/src/inverter/KOSTAL-RS485.cpp @@ -248,7 +248,7 @@ void update_RS485_registers_inverter() { float2frame(frame2, (float)(datalayer.battery.status.voltage_dV / 10), 6); // Confirmed OK mapping - float2frame(frame1, (float)(datalayer.battery.status.voltage_dV / 10), 6); // This shall be nominal voltage, but not available +# float2frame(frame1, (float)(datalayer.battery.status.voltage_dV / 10), 6); // This shall be nominal voltage, but not available float2frameMSB(frame2, (float)(datalayer.battery.info.max_design_voltage_dV / 10), 12); @@ -286,7 +286,7 @@ void update_RS485_registers_inverter() { register_content_ok = true; - frame1[38] = calculate_frame1_crc(frame1, 38); +# frame1[38] = calculate_frame1_crc(frame1, 38); if (incoming_message_counter > 0) { incoming_message_counter--; From a61f9040c84766474e50a846c50d53092c15ce1f Mon Sep 17 00:00:00 2001 From: rha Date: Wed, 25 Sep 2024 20:32:37 +0300 Subject: [PATCH 020/210] Frame 1 CRC fix --- Software/src/inverter/KOSTAL-RS485.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Software/src/inverter/KOSTAL-RS485.cpp b/Software/src/inverter/KOSTAL-RS485.cpp index 41c3ca0a..49144855 100644 --- a/Software/src/inverter/KOSTAL-RS485.cpp +++ b/Software/src/inverter/KOSTAL-RS485.cpp @@ -130,7 +130,7 @@ byte calculate_frame1_crc(byte* lfc, int lastbyte) { for (int i = 0; i < lastbyte; ++i) { sum += lfc[i]; } - return ((byte) ~(sum + 0x28) & 0xff); + return ((byte) ~(sum - 0x28) & 0xff); } bool check_kostal_frame_crc() { @@ -248,7 +248,7 @@ void update_RS485_registers_inverter() { float2frame(frame2, (float)(datalayer.battery.status.voltage_dV / 10), 6); // Confirmed OK mapping -# float2frame(frame1, (float)(datalayer.battery.status.voltage_dV / 10), 6); // This shall be nominal voltage, but not available + float2frameMSB(frame1, (float)(datalayer.battery.status.voltage_dV / 10), 8); // This shall be nominal voltage, but not available float2frameMSB(frame2, (float)(datalayer.battery.info.max_design_voltage_dV / 10), 12); @@ -286,7 +286,7 @@ void update_RS485_registers_inverter() { register_content_ok = true; -# frame1[38] = calculate_frame1_crc(frame1, 38); + frame1[38] = calculate_frame1_crc(frame1, 38); if (incoming_message_counter > 0) { incoming_message_counter--; From 35d7b35737e8722e661221c3e91bab694feae9e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Wed, 25 Sep 2024 22:58:25 +0300 Subject: [PATCH 021/210] Speedup and voltage limits added --- Software/src/battery/KIA-E-GMP-BATTERY.cpp | 269 +++------------------ 1 file changed, 40 insertions(+), 229 deletions(-) diff --git a/Software/src/battery/KIA-E-GMP-BATTERY.cpp b/Software/src/battery/KIA-E-GMP-BATTERY.cpp index 2c4204f2..95ad2ef4 100644 --- a/Software/src/battery/KIA-E-GMP-BATTERY.cpp +++ b/Software/src/battery/KIA-E-GMP-BATTERY.cpp @@ -6,13 +6,7 @@ #include "KIA-E-GMP-BATTERY.h" /* Do not change code below unless you are sure what you are doing */ -static unsigned long previousMillis10ms = 0; // will store last time a 10ms CAN Message was send -static unsigned long previousMillis20ms = 0; // will store last time a 20ms CAN Message was send -static unsigned long previousMillis30ms = 0; // will store last time a 30ms CAN Message was send -static unsigned long previousMillis100ms = 0; // will store last time a 100ms CAN Message was send static unsigned long previousMillis200ms = 0; // will store last time a 200ms CAN Message was send -static unsigned long previousMillis500ms = 0; // will store last time a 500ms CAN Message was send -static unsigned long previousMillis1s = 0; // will store last time a 1s CAN Message was send static unsigned long previousMillis10s = 0; // will store last time a 10s CAN Message was send #define MAX_CELL_VOLTAGE 4250 //Battery is put into emergency stop if one cell goes over this value @@ -585,208 +579,7 @@ CAN_frame* messages[] = {&message_1, &message_2, &message_3, &message_4, &me &message_43, &message_44, &message_45, &message_46, &message_47, &message_48, &message_49, &message_50, &message_51, &message_52, &message_53, &message_54, &message_55, &message_56, &message_57, &message_58, &message_59, &message_60, &message_61, &message_62, &message_63}; - -/* These messages are rest of the vehicle messages, to reduce number of active fault codes */ -CAN_frame EGMP_1CF = {.FD = true, - .ext_ID = false, - .DLC = 8, - .ID = 0x1CF, - .data = {0x56, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00}}; -CAN_frame EGMP_3AA = {.FD = true, - .ext_ID = false, - .DLC = 8, - .ID = 0x3AA, - .data = {0xFF, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00}}; -CAN_frame EGMP_3E0 = {.FD = true, - .ext_ID = false, - .DLC = 8, - .ID = 0x3E0, - .data = {0xC3, 0x10, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00}}; -CAN_frame EGMP_3E1 = {.FD = true, - .ext_ID = false, - .DLC = 8, - .ID = 0x3E1, - .data = {0x59, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; -CAN_frame EGMP_36F = {.FD = true, - .ext_ID = false, - .DLC = 8, - .ID = 0x36F, - .data = {0x28, 0x31, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00}}; -CAN_frame EGMP_37F = {.FD = true, - .ext_ID = false, - .DLC = 8, - .ID = 0x37F, - .data = {0x9B, 0x30, 0x52, 0x24, 0x41, 0x02, 0x00, 0x00}}; -CAN_frame EGMP_4B4 = {.FD = true, - .ext_ID = false, - .DLC = 8, - .ID = 0x4B4, - .data = {0x00, 0x00, 0xC0, 0x3F, 0x00, 0x00, 0x00, 0x00}}; -CAN_frame EGMP_4B5 = {.FD = true, - .ext_ID = false, - .DLC = 8, - .ID = 0x4B5, - .data = {0x08, 0x00, 0xF0, 0x07, 0x00, 0x00, 0x00, 0x00}}; -CAN_frame EGMP_4B7 = {.FD = true, - .ext_ID = false, - .DLC = 8, - .ID = 0x4B7, - .data = {0x08, 0x00, 0xF0, 0x07, 0x00, 0x00, 0x00, 0x00}}; -CAN_frame EGMP_4CC = {.FD = true, - .ext_ID = false, - .DLC = 8, - .ID = 0x4CC, - .data = {0x08, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x00}}; -CAN_frame EGMP_4CE = {.FD = true, - .ext_ID = false, - .DLC = 8, - .ID = 0x4CE, - .data = {0x16, 0xCF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}}; -CAN_frame EGMP_4D8 = {.FD = true, - .ext_ID = false, - .DLC = 8, - .ID = 0x4D8, - .data = {0x40, 0x10, 0xF0, 0xF0, 0x40, 0xF2, 0x1E, 0xCC}}; -CAN_frame EGMP_4DD = {.FD = true, - .ext_ID = false, - .DLC = 8, - .ID = 0x4DD, - .data = {0x3F, 0xFC, 0xFF, 0x00, 0x38, 0x00, 0x00, 0x00}}; -CAN_frame EGMP_4E7 = {.FD = true, - .ext_ID = false, - .DLC = 8, - .ID = 0x4E7, - .data = {0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00}}; -CAN_frame EGMP_4E9 = {.FD = true, - .ext_ID = false, - .DLC = 8, - .ID = 0x4E9, - .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; -CAN_frame EGMP_4EA = {.FD = true, - .ext_ID = false, - .DLC = 8, - .ID = 0x4EA, - .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; -CAN_frame EGMP_4EB = {.FD = true, - .ext_ID = false, - .DLC = 8, - .ID = 0x4EB, - .data = {0x01, 0x50, 0x0B, 0x26, 0x00, 0x00, 0x00, 0x00}}; -CAN_frame EGMP_4EC = {.FD = true, - .ext_ID = false, - .DLC = 8, - .ID = 0x4EC, - .data = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F}}; -CAN_frame EGMP_4ED = {.FD = true, - .ext_ID = false, - .DLC = 8, - .ID = 0x4ED, - .data = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x1F}}; -CAN_frame EGMP_4EE = {.FD = true, - .ext_ID = false, - .DLC = 8, - .ID = 0x4EE, - .data = {0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; -CAN_frame EGMP_4EF = {.FD = true, - .ext_ID = false, - .DLC = 8, - .ID = 0x4EF, - .data = {0x2B, 0xFE, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00}}; -CAN_frame EGMP_405 = {.FD = true, - .ext_ID = false, - .DLC = 8, - .ID = 0x405, - .data = {0xE4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; -CAN_frame EGMP_410 = {.FD = true, - .ext_ID = false, - .DLC = 8, - .ID = 0x410, - .data = {0xA6, 0x10, 0xFF, 0x3C, 0xFF, 0x7F, 0xFF, 0xFF}}; -CAN_frame EGMP_411 = {.FD = true, - .ext_ID = false, - .DLC = 8, - .ID = 0x411, - .data = {0xEA, 0x22, 0x50, 0x51, 0x00, 0x00, 0x00, 0x40}}; -CAN_frame EGMP_412 = {.FD = true, - .ext_ID = false, - .DLC = 8, - .ID = 0x412, - .data = {0xDC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; -CAN_frame EGMP_413 = {.FD = true, - .ext_ID = false, - .DLC = 8, - .ID = 0x413, - .data = {0xB9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; -CAN_frame EGMP_414 = {.FD = true, - .ext_ID = false, - .DLC = 8, - .ID = 0x414, - .data = {0xF0, 0x10, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00}}; -CAN_frame EGMP_416 = {.FD = true, - .ext_ID = false, - .DLC = 8, - .ID = 0x416, - .data = {0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; -CAN_frame EGMP_417 = {.FD = true, - .ext_ID = false, - .DLC = 8, - .ID = 0x417, - .data = {0xC7, 0x10, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00}}; -CAN_frame EGMP_418 = {.FD = true, - .ext_ID = false, - .DLC = 8, - .ID = 0x418, - .data = {0x17, 0x20, 0x00, 0x00, 0x14, 0x0C, 0x00, 0x00}}; -CAN_frame EGMP_3C1 = {.FD = true, - .ext_ID = false, - .DLC = 8, - .ID = 0x3C1, - .data = {0x59, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00}}; -CAN_frame EGMP_3C2 = {.FD = true, - .ext_ID = false, - .DLC = 8, - .ID = 0x3C2, - .data = {0x07, 0x00, 0x11, 0x40, 0x00, 0x00, 0x00, 0x00}}; -CAN_frame EGMP_4F0 = {.FD = true, - .ext_ID = false, - .DLC = 8, - .ID = 0x4F0, - .data = {0x8A, 0x0A, 0x0D, 0x34, 0x60, 0x18, 0x12, 0xFC}}; -CAN_frame EGMP_4F2 = {.FD = true, - .ext_ID = false, - .DLC = 8, - .ID = 0x4F2, - .data = {0x0A, 0xC3, 0xD5, 0xFF, 0x0F, 0x21, 0x80, 0x2B}}; -CAN_frame EGMP_4FE = {.FD = true, - .ext_ID = false, - .DLC = 8, - .ID = 0x4FE, - .data = {0x69, 0x3F, 0x00, 0x04, 0xDF, 0x01, 0x4C, 0xA8}}; -CAN_frame EGMP_48F = {.FD = true, - .ext_ID = false, - .DLC = 8, - .ID = 0x48F, - .data = {0xAD, 0x10, 0x41, 0x00, 0x05, 0x00, 0x00, 0x00}}; -CAN_frame EGMP_419 = {.FD = true, - .ext_ID = false, - .DLC = 8, - .ID = 0x419, - .data = {0xC7, 0x90, 0xB9, 0xD2, 0x0D, 0x62, 0x7A, 0x00}}; -CAN_frame EGMP_422 = {.FD = true, - .ext_ID = false, - .DLC = 8, - .ID = 0x422, - .data = {0x15, 0x10, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00}}; -CAN_frame EGMP_444 = {.FD = true, - .ext_ID = false, - .DLC = 8, - .ID = 0x444, - .data = {0x96, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; -CAN_frame EGMP_641 = {.FD = true, - .ext_ID = false, - .DLC = 8, - .ID = 0x641, - .data = {0xFF, 0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF}}; +/* PID polling messages */ CAN_frame EGMP_7E4 = {.FD = true, .ext_ID = false, .DLC = 8, @@ -807,6 +600,31 @@ void set_cell_voltages(CAN_frame rx_frame, int start, int length, int startCell) } } +void set_voltage_minmax_limits() { + + uint8_t valid_cell_count = 0; + for (int i = 0; i < MAX_AMOUNT_CELLS; ++i) { + if (datalayer.battery.status.cell_voltages_mV[i] > 0) { + ++valid_cell_count; + } + } + if (valid_cell_count == 144) { + datalayer.battery.info.number_of_cells = valid_cell_count; + datalayer.battery.info.max_design_voltage_dV = 6048; + datalayer.battery.info.min_design_voltage_dV = 4320; + } else if (valid_cell_count == 180) { + datalayer.battery.info.number_of_cells = valid_cell_count; + datalayer.battery.info.max_design_voltage_dV = 7560; + datalayer.battery.info.min_design_voltage_dV = 5400; + } else if (valid_cell_count == 192) { + datalayer.battery.info.number_of_cells = valid_cell_count; + datalayer.battery.info.max_design_voltage_dV = 8064; + datalayer.battery.info.min_design_voltage_dV = 5760; + } else { + // We are still starting up? Not all cells available. + } +} + static uint8_t calculateCRC(CAN_frame rx_frame, uint8_t length, uint8_t initial_value) { uint8_t crc = initial_value; for (uint8_t j = 1; j < length; j++) { //start at 1, since 0 is the CRC @@ -848,6 +666,10 @@ void update_values_battery() { //This function maps all the values fetched via datalayer.battery.status.cell_min_voltage_mV = CellVoltMin_mV; + if (millis() > INTERVAL_60_S) { + set_voltage_minmax_limits(); // Count cells, and set voltage limits accordingly + } + /* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/ if (!datalayer.battery.status.CAN_battery_still_alive) { set_event(EVENT_CANFD_RX_FAILURE, 0); @@ -1189,16 +1011,16 @@ void send_can_battery() { messageIndex = 0; } - //Send 500ms CANFD message - if (currentMillis - previousMillis500ms >= INTERVAL_500_MS) { - previousMillis500ms = currentMillis; + //Send 200ms CANFD message + if (currentMillis - previousMillis200ms >= INTERVAL_200_MS) { + previousMillis200ms = currentMillis; // Check if sending of CAN messages has been delayed too much. - if ((currentMillis - previousMillis500ms >= INTERVAL_500_MS_DELAYED) && (currentMillis > BOOTUP_TIME)) { - set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis500ms)); + if ((currentMillis - previousMillis200ms >= INTERVAL_200_MS_DELAYED) && (currentMillis > BOOTUP_TIME)) { + set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis200ms)); } else { clear_event(EVENT_CAN_OVERRUN); } - previousMillis500ms = currentMillis; + previousMillis200ms = currentMillis; EGMP_7E4.data.u8[3] = KIA_7E4_COUNTER; @@ -1211,19 +1033,10 @@ void send_can_battery() { KIA_7E4_COUNTER = 0x01; } } - //Send 1s CANFD message - if (currentMillis - previousMillis1s >= INTERVAL_1_S) { - previousMillis1s = currentMillis; - /* COMMENTED OUT WHILE CONTACTOR CLOSING TESTING - transmit_can(&EGMP_48F, can_config.battery); - */ - } //Send 10s CANFD message if (currentMillis - previousMillis10s >= INTERVAL_10_S) { previousMillis10s = currentMillis; - /* COMMENTED OUT WHILE CONTACTOR CLOSING TESTING - transmit_can(&EGMP_4FE, can_config.battery); - */ + ok_start_polling_battery = true; } } @@ -1238,12 +1051,10 @@ void setup_battery(void) { // Performs one time setup at startup datalayer.system.status.battery_allows_contactor_closing = true; - datalayer.battery.info.number_of_cells = 192; // TODO: will vary depending on battery + datalayer.battery.info.number_of_cells = 192; // Startup in 192S mode - datalayer.battery.info.max_design_voltage_dV = - 8064; // TODO: define when battery is known, charging is not possible (goes into forced discharge) - datalayer.battery.info.min_design_voltage_dV = - 4320; // TODO: define when battery is known. discharging further is disabled + datalayer.battery.info.max_design_voltage_dV = 8064; // 192S max value + datalayer.battery.info.min_design_voltage_dV = 4320; // 144S min value } #endif From eb2b5387242d51b52463a802834bb137d1096116 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Thu, 26 Sep 2024 19:46:37 +0300 Subject: [PATCH 022/210] Restore 500ms, and fix setting limits --- Software/src/battery/KIA-E-GMP-BATTERY.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/Software/src/battery/KIA-E-GMP-BATTERY.cpp b/Software/src/battery/KIA-E-GMP-BATTERY.cpp index 95ad2ef4..d3788ffb 100644 --- a/Software/src/battery/KIA-E-GMP-BATTERY.cpp +++ b/Software/src/battery/KIA-E-GMP-BATTERY.cpp @@ -6,7 +6,7 @@ #include "KIA-E-GMP-BATTERY.h" /* Do not change code below unless you are sure what you are doing */ -static unsigned long previousMillis200ms = 0; // will store last time a 200ms CAN Message was send +static unsigned long previousMillis500ms = 0; // will store last time a 500ms CAN Message was send static unsigned long previousMillis10s = 0; // will store last time a 10s CAN Message was send #define MAX_CELL_VOLTAGE 4250 //Battery is put into emergency stop if one cell goes over this value @@ -60,7 +60,7 @@ static uint8_t KIA_7E4_COUNTER = 0x01; static int8_t temperature_water_inlet = 0; static int8_t powerRelayTemperature = 0; static int8_t heatertemp = 0; - +static bool set_voltage_limits = false; static uint8_t ticks_200ms_counter = 0; static uint8_t EGMP_1CF_counter = 0; static uint8_t EGMP_3XF_counter = 0; @@ -622,6 +622,7 @@ void set_voltage_minmax_limits() { datalayer.battery.info.min_design_voltage_dV = 5760; } else { // We are still starting up? Not all cells available. + set_voltage_limits = false; } } @@ -666,7 +667,8 @@ void update_values_battery() { //This function maps all the values fetched via datalayer.battery.status.cell_min_voltage_mV = CellVoltMin_mV; - if (millis() > INTERVAL_60_S) { + if ((millis() > INTERVAL_60_S) && !set_voltage_limits) { + set_voltage_limits = true; set_voltage_minmax_limits(); // Count cells, and set voltage limits accordingly } @@ -1011,16 +1013,16 @@ void send_can_battery() { messageIndex = 0; } - //Send 200ms CANFD message - if (currentMillis - previousMillis200ms >= INTERVAL_200_MS) { - previousMillis200ms = currentMillis; + //Send 500ms CANFD message + if (currentMillis - previousMillis500ms >= INTERVAL_500_MS) { + previousMillis500ms = currentMillis; // Check if sending of CAN messages has been delayed too much. - if ((currentMillis - previousMillis200ms >= INTERVAL_200_MS_DELAYED) && (currentMillis > BOOTUP_TIME)) { + if ((currentMillis - previousMillis500ms >= INTERVAL_500_MS_DELAYED) && (currentMillis > BOOTUP_TIME)) { set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis200ms)); } else { clear_event(EVENT_CAN_OVERRUN); } - previousMillis200ms = currentMillis; + previousMillis500ms = currentMillis; EGMP_7E4.data.u8[3] = KIA_7E4_COUNTER; From 8e8fffe51aa6ee6058510102cc258212405e546e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Thu, 26 Sep 2024 19:47:50 +0300 Subject: [PATCH 023/210] Compile error fix --- Software/src/battery/KIA-E-GMP-BATTERY.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Software/src/battery/KIA-E-GMP-BATTERY.cpp b/Software/src/battery/KIA-E-GMP-BATTERY.cpp index d3788ffb..76812626 100644 --- a/Software/src/battery/KIA-E-GMP-BATTERY.cpp +++ b/Software/src/battery/KIA-E-GMP-BATTERY.cpp @@ -1018,7 +1018,7 @@ void send_can_battery() { previousMillis500ms = currentMillis; // Check if sending of CAN messages has been delayed too much. if ((currentMillis - previousMillis500ms >= INTERVAL_500_MS_DELAYED) && (currentMillis > BOOTUP_TIME)) { - set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis200ms)); + set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis500ms)); } else { clear_event(EVENT_CAN_OVERRUN); } From 658a837cd60c3f1038b4dcf33e7a665c46784e6d Mon Sep 17 00:00:00 2001 From: rha Date: Fri, 27 Sep 2024 21:32:35 +0300 Subject: [PATCH 024/210] data conversion fix --- Software/src/inverter/KOSTAL-RS485.cpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Software/src/inverter/KOSTAL-RS485.cpp b/Software/src/inverter/KOSTAL-RS485.cpp index 49144855..766aa7ef 100644 --- a/Software/src/inverter/KOSTAL-RS485.cpp +++ b/Software/src/inverter/KOSTAL-RS485.cpp @@ -246,28 +246,28 @@ void update_RS485_registers_inverter() { average_temperature_dC = 0; } - float2frame(frame2, (float)(datalayer.battery.status.voltage_dV / 10), 6); // Confirmed OK mapping + float2frame(frame2, (float)datalayer.battery.status.voltage_dV / 10, 6); // Confirmed OK mapping - float2frameMSB(frame1, (float)(datalayer.battery.status.voltage_dV / 10), 8); // This shall be nominal voltage, but not available + float2frameMSB(frame1, (float)datalayer.battery.status.voltage_dV / 10, 8); // This shall be nominal voltage, but not available - float2frameMSB(frame2, (float)(datalayer.battery.info.max_design_voltage_dV / 10), 12); + float2frameMSB(frame2, (float)datalayer.battery.info.max_design_voltage_dV / 10, 12); - float2frameMSB(frame2, (float)(average_temperature_dC / 10), 16); + float2frameMSB(frame2, (float)average_temperature_dC / 10, 16); float2frameMSB(frame2, (float)datalayer.battery.status.current_dA / 10, 20); // Peak discharge? current (2 byte float) float2frameMSB(frame2, (float)datalayer.battery.status.current_dA / 10, 24); - float2frameMSB(frame2, (float)(discharge_current_dA / 10), 28); // BAttery capacity Ah + float2frameMSB(frame2, (float)discharge_current_dA / 10, 28); // BAttery capacity Ah - float2frameMSB(frame2, (float)(discharge_current_dA / 10), 32); + float2frameMSB(frame2, (float)discharge_current_dA / 10, 32); // When SOC = 100%, drop down allowed charge current down. if((datalayer.battery.status.reported_soc / 100)<100) { - float2frameMSB(frame2, (float)(charge_current_dA / 10), 36); + float2frameMSB(frame2, (float)charge_current_dA / 10, 36); frame2[57]=0x02; } else @@ -276,11 +276,11 @@ void update_RS485_registers_inverter() { frame2[57]=0x40; } - float2frame(frame2, (float)(datalayer.battery.status.temperature_max_dC / 10), 38); - float2frame(frame2, (float)(datalayer.battery.status.temperature_min_dC / 10), 42); + float2frame(frame2, (float)datalayer.battery.status.temperature_max_dC / 10, 38); + float2frame(frame2, (float)datalayer.battery.status.temperature_min_dC / 10, 42); - float2frame(frame2, (float)(datalayer.battery.status.cell_max_voltage_mV / 1000), 46); - float2frame(frame2, (float)(datalayer.battery.status.cell_min_voltage_mV / 1000), 50); + float2frame(frame2, (float)datalayer.battery.status.cell_max_voltage_mV / 1000, 46); + float2frame(frame2, (float)datalayer.battery.status.cell_min_voltage_mV / 1000, 50); frame2[58] = (byte)(datalayer.battery.status.reported_soc / 100); // Confirmed OK mapping From 7adb63e1bfb6cf558dc3c04c1da401e76946962b Mon Sep 17 00:00:00 2001 From: rha Date: Fri, 27 Sep 2024 22:11:43 +0300 Subject: [PATCH 025/210] dynamic battery update testing --- Software/src/inverter/KOSTAL-RS485.cpp | 137 +++++++++++++------------ 1 file changed, 73 insertions(+), 64 deletions(-) diff --git a/Software/src/inverter/KOSTAL-RS485.cpp b/Software/src/inverter/KOSTAL-RS485.cpp index 766aa7ef..44f72fbf 100644 --- a/Software/src/inverter/KOSTAL-RS485.cpp +++ b/Software/src/inverter/KOSTAL-RS485.cpp @@ -145,71 +145,7 @@ bool check_kostal_frame_crc() { } } -static uint8_t rx_index = 0; - -void receive_RS485() // Runs as fast as possible to handle the serial stream -{ - if (Serial2.available()) { - RS485_RXFRAME[rx_index] = Serial2.read(); - rx_index++; - if (RS485_RXFRAME[rx_index - 1] == 0x00) { - if ((rx_index == 10) && (RS485_RXFRAME[0] == 0x09) && register_content_ok) { -#ifdef DEBUG_KOSTAL_RS485_DATA - Serial.print("RX: "); - for (uint8_t i = 0; i < 10; i++) { - Serial.print(RS485_RXFRAME[i], HEX); - Serial.print(" "); - } - Serial.println(""); -#endif - rx_index = 0; - if (check_kostal_frame_crc()) { - incoming_message_counter = RS485_HEALTHY; - bool headerA = true; - bool headerB = true; - for (uint8_t i = 0; i < 5; i++) { - if (RS485_RXFRAME[i + 1] != KOSTAL_FRAMEHEADER[i]) { - headerA = false; - } - if (RS485_RXFRAME[i + 1] != KOSTAL_FRAMEHEADER2[i]) { - headerB = false; - } - } - - // "frame B1", maybe reset request, seen after battery power on/partial data - if (headerB && (RS485_RXFRAME[6] == 0x5E) && (RS485_RXFRAME[7] == 0xFF)) { - send_kostal(frameB1, 10); - Serial2.flush(); - delay(1); - send_kostal(frameB1b, 10); - } - if (headerA && (RS485_RXFRAME[6] == 0x4A) && (RS485_RXFRAME[7] == 0x08)) { // "frame 1" - send_kostal(frame1, 40); - } - if (headerA && (RS485_RXFRAME[6] == 0x4A) && (RS485_RXFRAME[7] == 0x04)) { // "frame 2" - byte tmpframe[64]; //copy values to prevent data manipulation during rewrite/crc calculation - memcpy(tmpframe, frame2, 64); - for (int i = 1; i < 63; i++) { - if (tmpframe[i] == 0x00) { - tmpframe[i] = 0x01; - } - } - tmpframe[62] = calculate_longframe_crc(tmpframe, 62); - send_kostal(tmpframe, 64); - } - if (headerA && (RS485_RXFRAME[6] == 0x53) && (RS485_RXFRAME[7] == 0x03)) { // "frame 3" - send_kostal(frame3, 9); - } - } - } - rx_index = 0; - } - if (rx_index >= 10) { - rx_index = 0; - } - } -} void update_RS485_registers_inverter() { @@ -298,4 +234,77 @@ void update_RS485_registers_inverter() { clear_event(EVENT_MODBUS_INVERTER_MISSING); } } + +static uint8_t rx_index = 0; + +void receive_RS485() // Runs as fast as possible to handle the serial stream +{ + if (Serial2.available()) { + RS485_RXFRAME[rx_index] = Serial2.read(); + rx_index++; + if (RS485_RXFRAME[rx_index - 1] == 0x00) { + + + if ((rx_index == 10) && (RS485_RXFRAME[0] == 0x09) && register_content_ok) { +#ifdef DEBUG_KOSTAL_RS485_DATA + Serial.print("RX: "); + for (uint8_t i = 0; i < 10; i++) { + Serial.print(RS485_RXFRAME[i], HEX); + Serial.print(" "); + } + Serial.println(""); +#endif + rx_index = 0; + if (check_kostal_frame_crc()) { + incoming_message_counter = RS485_HEALTHY; + bool headerA = true; + bool headerB = true; + for (uint8_t i = 0; i < 5; i++) { + if (RS485_RXFRAME[i + 1] != KOSTAL_FRAMEHEADER[i]) { + headerA = false; + } + if (RS485_RXFRAME[i + 1] != KOSTAL_FRAMEHEADER2[i]) { + headerB = false; + } + } + + // "frame B1", maybe reset request, seen after battery power on/partial data + if (headerB && (RS485_RXFRAME[6] == 0x5E) && (RS485_RXFRAME[7] == 0xFF)) { + send_kostal(frameB1, 10); + Serial2.flush(); + delay(1); + send_kostal(frameB1b, 10); + } + + if (headerA && (RS485_RXFRAME[6] == 0x4A) && (RS485_RXFRAME[7] == 0x08)) { // "frame 1" + send_kostal(frame1, 40); + } + if (headerA && (RS485_RXFRAME[6] == 0x4A) && (RS485_RXFRAME[7] == 0x04)) { // "frame 2" + + update_values_battery(); + update_RS485_registers_inverter(); + + byte tmpframe[64]; //copy values to prevent data manipulation during rewrite/crc calculation + memcpy(tmpframe, frame2, 64); + for (int i = 1; i < 63; i++) { + if (tmpframe[i] == 0x00) { + tmpframe[i] = 0x01; + } + } + tmpframe[62] = calculate_longframe_crc(tmpframe, 62); + send_kostal(tmpframe, 64); + } + if (headerA && (RS485_RXFRAME[6] == 0x53) && (RS485_RXFRAME[7] == 0x03)) { // "frame 3" + send_kostal(frame3, 9); + } + } + } + rx_index = 0; + } + if (rx_index >= 10) { + rx_index = 0; + } + } +} + #endif From 2d306ae301361bde4305dd313b4b4f710cad5983 Mon Sep 17 00:00:00 2001 From: rha Date: Sat, 28 Sep 2024 21:42:17 +0300 Subject: [PATCH 026/210] frame4 --- Software/src/inverter/KOSTAL-RS485.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/Software/src/inverter/KOSTAL-RS485.cpp b/Software/src/inverter/KOSTAL-RS485.cpp index 44f72fbf..e5d276ee 100644 --- a/Software/src/inverter/KOSTAL-RS485.cpp +++ b/Software/src/inverter/KOSTAL-RS485.cpp @@ -14,14 +14,13 @@ static uint16_t charge_current_dA = 0; static int16_t average_temperature_dC = 0; static uint8_t incoming_message_counter = RS485_HEALTHY; + union f32b { float f; byte b[4]; }; - - uint8_t frame1[40] = { 0x06, 0xE2, 0xFF, 0x02, 0xFF, 0x29, // Frame header 0x01, 0x08, 0x80, 0x43, // 256.063 Nominal voltage / 5*51.2=256 first byte 0x01 or 0x04 @@ -79,6 +78,12 @@ uint8_t frame3[9] = { 0x00 //endbyte }; +uint8_t frame4[8] = { + 0x07, 0xE3, 0xFF, 0x02, 0xFF,0x29, + 0xF4, + 0x00 +} + uint8_t frameB1[10] = {0x07, 0x63, 0xFF, 0x02, 0xFF, 0x29, 0x5E, 0x02, 0x16, 0x00}; uint8_t frameB1b[10] = {0x07, 0xE3, 0xFF, 0x02, 0xFF, 0x29, 0xF4, 0x00}; @@ -276,6 +281,12 @@ void receive_RS485() // Runs as fast as possible to handle the serial stream send_kostal(frameB1b, 10); } + // "frame B1", maybe reset request, seen after battery power on/partial data + if (headerB && (RS485_RXFRAME[6] == 0x5E) && (RS485_RXFRAME[7] == 0x04)) { + send_kostal(frame4, 8); + Serial2.flush(); + } + if (headerA && (RS485_RXFRAME[6] == 0x4A) && (RS485_RXFRAME[7] == 0x08)) { // "frame 1" send_kostal(frame1, 40); } From 68fa723a84ee711a336f9034542bfbce8451bfee Mon Sep 17 00:00:00 2001 From: rha Date: Sat, 28 Sep 2024 21:44:30 +0300 Subject: [PATCH 027/210] frame4 --- Software/src/inverter/KOSTAL-RS485.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Software/src/inverter/KOSTAL-RS485.cpp b/Software/src/inverter/KOSTAL-RS485.cpp index e5d276ee..e34e601c 100644 --- a/Software/src/inverter/KOSTAL-RS485.cpp +++ b/Software/src/inverter/KOSTAL-RS485.cpp @@ -82,7 +82,7 @@ uint8_t frame4[8] = { 0x07, 0xE3, 0xFF, 0x02, 0xFF,0x29, 0xF4, 0x00 -} +}; uint8_t frameB1[10] = {0x07, 0x63, 0xFF, 0x02, 0xFF, 0x29, 0x5E, 0x02, 0x16, 0x00}; uint8_t frameB1b[10] = {0x07, 0xE3, 0xFF, 0x02, 0xFF, 0x29, 0xF4, 0x00}; From 0651ced73c333451cc785059f117d11e36cf57fb Mon Sep 17 00:00:00 2001 From: rha Date: Fri, 4 Oct 2024 07:04:43 +0300 Subject: [PATCH 028/210] added byte 57 startup value & fixed FrameB1b length --- Software/src/inverter/KOSTAL-RS485.cpp | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/Software/src/inverter/KOSTAL-RS485.cpp b/Software/src/inverter/KOSTAL-RS485.cpp index e34e601c..29a7d637 100644 --- a/Software/src/inverter/KOSTAL-RS485.cpp +++ b/Software/src/inverter/KOSTAL-RS485.cpp @@ -13,6 +13,7 @@ static uint16_t discharge_current_dA = 0; static uint16_t charge_current_dA = 0; static int16_t average_temperature_dC = 0; static uint8_t incoming_message_counter = RS485_HEALTHY; +static int8_t f2_startup_count = 0; union f32b { @@ -85,10 +86,11 @@ uint8_t frame4[8] = { }; uint8_t frameB1[10] = {0x07, 0x63, 0xFF, 0x02, 0xFF, 0x29, 0x5E, 0x02, 0x16, 0x00}; -uint8_t frameB1b[10] = {0x07, 0xE3, 0xFF, 0x02, 0xFF, 0x29, 0xF4, 0x00}; +uint8_t frameB1b[8] = {0x07, 0xE3, 0xFF, 0x02, 0xFF, 0x29, 0xF4, 0x00}; uint8_t RS485_RXFRAME[10]; + bool register_content_ok = false; void float2frame(byte* arr, float value, byte framepointer) { @@ -217,6 +219,12 @@ void update_RS485_registers_inverter() { frame2[57]=0x40; } + // On startup, byte 57 seems to be always 0x03 couple of frames, mostly 5 frames. + if (f2_startup_count<7) + { + frame2[57]=0x03; + } + float2frame(frame2, (float)datalayer.battery.status.temperature_max_dC / 10, 38); float2frame(frame2, (float)datalayer.battery.status.temperature_min_dC / 10, 42); @@ -278,7 +286,7 @@ void receive_RS485() // Runs as fast as possible to handle the serial stream send_kostal(frameB1, 10); Serial2.flush(); delay(1); - send_kostal(frameB1b, 10); + send_kostal(frameB1b, 8); } // "frame B1", maybe reset request, seen after battery power on/partial data @@ -294,6 +302,11 @@ void receive_RS485() // Runs as fast as possible to handle the serial stream update_values_battery(); update_RS485_registers_inverter(); + if (f2_startup_count<7) + { + f2_startup_count++; + } + byte tmpframe[64]; //copy values to prevent data manipulation during rewrite/crc calculation memcpy(tmpframe, frame2, 64); From a3058b3fa5db7ec0d9c6cc002fef2450a6c529b1 Mon Sep 17 00:00:00 2001 From: rha Date: Sat, 5 Oct 2024 09:24:28 +0300 Subject: [PATCH 029/210] battery startup sequence & relay control test --- Software/src/inverter/KOSTAL-RS485.cpp | 35 +++++++++++++++++++------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/Software/src/inverter/KOSTAL-RS485.cpp b/Software/src/inverter/KOSTAL-RS485.cpp index 29a7d637..ed393bd5 100644 --- a/Software/src/inverter/KOSTAL-RS485.cpp +++ b/Software/src/inverter/KOSTAL-RS485.cpp @@ -37,7 +37,9 @@ uint8_t frame1[40] = { // values in frame2 will be overwritten at update_modbus_registers_inverter() -uint8_t frame2[64] = {0x0A, 0xE2, 0xFF, 0x02, 0xFF, 0x29, // frame Header + +uint8_t frame2[64] = {0x0A, // This may also been 0x06, seen at startup when live values not valid, but also occasionally single frames. + 0xE2, 0xFF, 0x02, 0xFF, 0x29, // frame Header 0x1D, 0x5A, 0x85, 0x43, // Cyrrent Voltage (float) Modbus register 216, Bit 6-9 0x01, 0x03, 0x8D, 0x43, // Max Voltage (2 byte float), Bit 10-13 @@ -60,9 +62,9 @@ uint8_t frame2[64] = {0x0A, 0xE2, 0xFF, 0x02, 0xFF, 0x29, // frame Header 0xFE, // Cylce count , Bit 54 0x04, // Cycle count? , Bit 55 0x01, // Byte 56 - 0x40, // When SOC=100 Byte57=0x40, otherwise 0x02 or 0x03 + 0x40, // When SOC=100 Byte57=0x40, at startup 0x03 (about 7 times), otherwise 0x02 0x64, // SOC , Bit 58 - 0x01, // Unknown, Mostly 0x01, seen also 0x02 + 0x01, // Unknown, when byte 57 = 0x03, this 0x02, otherwise 0x01 0x01, // Unknown, Seen only 0x01 0x02, // Unknown, Mostly 0x02. seen also 0x01 0x00, // CRC (inverted sum of bytes 1-62 + 0xC0), Bit 62 @@ -189,8 +191,19 @@ void update_RS485_registers_inverter() { average_temperature_dC = 0; } - float2frame(frame2, (float)datalayer.battery.status.voltage_dV / 10, 6); // Confirmed OK mapping + + if (f2_startup_count>8) + { + float2frame(frame2, (float)datalayer.battery.status.voltage_dV / 10, 6); // Confirmed OK mapping + datalayer.system.status.inverter_allows_contactor_closing = true; + frame2[0]=0x0A; + } + else + { + frame2[0]=0x06; + float2frame(frame2, 0.0, 6); + } float2frameMSB(frame1, (float)datalayer.battery.status.voltage_dV / 10, 8); // This shall be nominal voltage, but not available float2frameMSB(frame2, (float)datalayer.battery.info.max_design_voltage_dV / 10, 12); @@ -211,18 +224,21 @@ void update_RS485_registers_inverter() { if((datalayer.battery.status.reported_soc / 100)<100) { float2frameMSB(frame2, (float)charge_current_dA / 10, 36); - frame2[57]=0x02; + frame2[57]=0x02; + frame2[59]=0x01; } else { float2frameMSB(frame2, 0.0 , 36); - frame2[57]=0x40; + frame2[57]=0x40; + frame2[59]=0x01; } - // On startup, byte 57 seems to be always 0x03 couple of frames, mostly 5 frames. - if (f2_startup_count<7) + // On startup, byte 57 seems to be always 0x03 couple of frames,. + if (f2_startup_count<14) { frame2[57]=0x03; + frame2[59]=0x02; } float2frame(frame2, (float)datalayer.battery.status.temperature_max_dC / 10, 38); @@ -292,6 +308,7 @@ void receive_RS485() // Runs as fast as possible to handle the serial stream // "frame B1", maybe reset request, seen after battery power on/partial data if (headerB && (RS485_RXFRAME[6] == 0x5E) && (RS485_RXFRAME[7] == 0x04)) { send_kostal(frame4, 8); + datalayer.system.status.inverter_allows_contactor_closing = false; Serial2.flush(); } @@ -302,7 +319,7 @@ void receive_RS485() // Runs as fast as possible to handle the serial stream update_values_battery(); update_RS485_registers_inverter(); - if (f2_startup_count<7) + if (f2_startup_count<15) { f2_startup_count++; } From 0dd374b5d1e0e4fa9355dc5a835aafe4c0c44bf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Sat, 5 Oct 2024 12:46:38 +0300 Subject: [PATCH 030/210] Add ramping of power value --- Software/src/battery/KIA-E-GMP-BATTERY.cpp | 12 ++++++++++-- Software/src/battery/KIA-E-GMP-BATTERY.h | 2 ++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/Software/src/battery/KIA-E-GMP-BATTERY.cpp b/Software/src/battery/KIA-E-GMP-BATTERY.cpp index 036f2451..bb0c174d 100644 --- a/Software/src/battery/KIA-E-GMP-BATTERY.cpp +++ b/Software/src/battery/KIA-E-GMP-BATTERY.cpp @@ -645,8 +645,16 @@ void update_values_battery() { //This function maps all the values fetched via (static_cast(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh); //datalayer.battery.status.max_charge_power_W = (uint16_t)allowedChargePower * 10; //From kW*100 to Watts - //The allowed charge power is not available. We hardcode this value for now - datalayer.battery.status.max_charge_power_W = MAXCHARGEPOWERALLOWED; + //The allowed charge power is not available. We estimate this value for now + if (datalayer.battery.status.real_soc > 9900) { + datalayer.battery.status.max_charge_power_W = 0; + } else if (datalayer.battery.status.real_soc > + RAMPDOWN_SOC) { // When real SOC is between 90-99%, ramp the value between Max<->0 + datalayer.battery.status.max_charge_power_W = + RAMPDOWNPOWERALLOWED * (1 - (datalayer.battery.status.real_soc - RAMPDOWN_SOC) / (10000.0 - RAMPDOWN_SOC)); + } else { // No limits, max charging power allowed + datalayer.battery.status.max_charge_power_W = MAXCHARGEPOWERALLOWED; + } //datalayer.battery.status.max_discharge_power_W = (uint16_t)allowedDischargePower * 10; //From kW*100 to Watts //The allowed discharge power is not available. We hardcode this value for now diff --git a/Software/src/battery/KIA-E-GMP-BATTERY.h b/Software/src/battery/KIA-E-GMP-BATTERY.h index 8000b05b..8c9f0db7 100644 --- a/Software/src/battery/KIA-E-GMP-BATTERY.h +++ b/Software/src/battery/KIA-E-GMP-BATTERY.h @@ -14,6 +14,8 @@ extern ACAN2517FD canfd; #define MIN_CELL_VOLTAGE_MV 2950 //Battery is put into emergency stop if one cell goes below this value #define MAXCHARGEPOWERALLOWED 10000 #define MAXDISCHARGEPOWERALLOWED 10000 +#define RAMPDOWN_SOC 9000 // 90.00 SOC% to start ramping down from max charge power towards 0 at 100.00% +#define RAMPDOWNPOWERALLOWED 10000 // What power we ramp down from towards top balancing void setup_battery(void); void transmit_can(CAN_frame* tx_frame, int interface); From a2f5e33eaff72831efaf3da119af7f6b3f8472db Mon Sep 17 00:00:00 2001 From: rha Date: Tue, 8 Oct 2024 22:48:11 +0300 Subject: [PATCH 031/210] fixed blocking delay() and added battery nominal voltage --- Software/USER_SETTINGS.h | 2 ++ Software/src/datalayer/datalayer.h | 2 ++ Software/src/inverter/KOSTAL-RS485.cpp | 34 +++++++++++++------------- 3 files changed, 21 insertions(+), 17 deletions(-) diff --git a/Software/USER_SETTINGS.h b/Software/USER_SETTINGS.h index 57153f02..c8a4d5c7 100644 --- a/Software/USER_SETTINGS.h +++ b/Software/USER_SETTINGS.h @@ -88,6 +88,8 @@ #define BATTERY_MAX_CHARGE_AMP 300 // 300 = 30.0A , BYD CAN specific setting, Max discharge in Amp (Some inverters needs to be limited) #define BATTERY_MAX_DISCHARGE_AMP 300 +// Battery nominal voltage dV +#define BATTERY_NOMINAL_VOLTAGE 3600 /* Do not change any code below this line unless you are sure what you are doing */ /* Only change battery specific settings in "USER_SETTINGS.h" */ diff --git a/Software/src/datalayer/datalayer.h b/Software/src/datalayer/datalayer.h index f64a190e..90337dba 100644 --- a/Software/src/datalayer/datalayer.h +++ b/Software/src/datalayer/datalayer.h @@ -17,6 +17,8 @@ typedef struct { 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; + /** Kostal specific setting, */ + uint16_t nominal_dV = BATTERY_NOMINAL_VOLTAGE ; /** uint8_t */ /** Total number of cells in the pack */ diff --git a/Software/src/inverter/KOSTAL-RS485.cpp b/Software/src/inverter/KOSTAL-RS485.cpp index ed393bd5..956ece03 100644 --- a/Software/src/inverter/KOSTAL-RS485.cpp +++ b/Software/src/inverter/KOSTAL-RS485.cpp @@ -15,6 +15,8 @@ static int16_t average_temperature_dC = 0; static uint8_t incoming_message_counter = RS485_HEALTHY; static int8_t f2_startup_count = 0; +static boolean B1_delay = false; +static unsigned long B1_last_millis = 0; union f32b { float f; @@ -154,8 +156,6 @@ bool check_kostal_frame_crc() { } } - - void update_RS485_registers_inverter() { if (datalayer.battery.status.voltage_dV > 10) { // Only update value when we have voltage available to avoid div0 @@ -172,7 +172,6 @@ void update_RS485_registers_inverter() { discharge_current_dA = (discharge_current_dA * 10); //Value needs a decimal before getting sent to inverter (81.0A) } - if (charge_current_dA > datalayer.battery.info.max_charge_amp_dA) { charge_current_dA = datalayer.battery.info @@ -191,8 +190,6 @@ void update_RS485_registers_inverter() { average_temperature_dC = 0; } - - if (f2_startup_count>8) { float2frame(frame2, (float)datalayer.battery.status.voltage_dV / 10, 6); // Confirmed OK mapping @@ -204,7 +201,7 @@ void update_RS485_registers_inverter() { frame2[0]=0x06; float2frame(frame2, 0.0, 6); } - float2frameMSB(frame1, (float)datalayer.battery.status.voltage_dV / 10, 8); // This shall be nominal voltage, but not available + float2frameMSB(frame1, (float)datalayer.battery.info.nominal_dV / 10, 8); float2frameMSB(frame2, (float)datalayer.battery.info.max_design_voltage_dV / 10, 12); @@ -268,12 +265,19 @@ static uint8_t rx_index = 0; void receive_RS485() // Runs as fast as possible to handle the serial stream { - if (Serial2.available()) { + unsigned long currentMillis = millis(); + if(B1_delay) + { + if ((currentMillis - B1_last_millis) >1000) + { + send_kostal(frameB1b, 8); + B1_delay=false; + } + } + else if (Serial2.available()) { RS485_RXFRAME[rx_index] = Serial2.read(); rx_index++; if (RS485_RXFRAME[rx_index - 1] == 0x00) { - - if ((rx_index == 10) && (RS485_RXFRAME[0] == 0x09) && register_content_ok) { #ifdef DEBUG_KOSTAL_RS485_DATA Serial.print("RX: "); @@ -300,31 +304,27 @@ void receive_RS485() // Runs as fast as possible to handle the serial stream // "frame B1", maybe reset request, seen after battery power on/partial data if (headerB && (RS485_RXFRAME[6] == 0x5E) && (RS485_RXFRAME[7] == 0xFF)) { send_kostal(frameB1, 10); - Serial2.flush(); - delay(1); - send_kostal(frameB1b, 8); + B1_delay=true; + B1_last_millis=currentMillis; } // "frame B1", maybe reset request, seen after battery power on/partial data if (headerB && (RS485_RXFRAME[6] == 0x5E) && (RS485_RXFRAME[7] == 0x04)) { send_kostal(frame4, 8); - datalayer.system.status.inverter_allows_contactor_closing = false; - Serial2.flush(); + // This needs more reverse engineering, disabled... + // datalayer.system.status.inverter_allows_contactor_closing = false; } if (headerA && (RS485_RXFRAME[6] == 0x4A) && (RS485_RXFRAME[7] == 0x08)) { // "frame 1" send_kostal(frame1, 40); } if (headerA && (RS485_RXFRAME[6] == 0x4A) && (RS485_RXFRAME[7] == 0x04)) { // "frame 2" - update_values_battery(); update_RS485_registers_inverter(); if (f2_startup_count<15) { f2_startup_count++; } - - byte tmpframe[64]; //copy values to prevent data manipulation during rewrite/crc calculation memcpy(tmpframe, frame2, 64); for (int i = 1; i < 63; i++) { From 45698b40e908b3257dea7ee6b22f60e7c1bcaa4b Mon Sep 17 00:00:00 2001 From: rha Date: Sat, 12 Oct 2024 18:16:42 +0300 Subject: [PATCH 032/210] removed avg/peak current values --- Software/src/inverter/KOSTAL-RS485.cpp | 34 ++++++++++++++++---------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/Software/src/inverter/KOSTAL-RS485.cpp b/Software/src/inverter/KOSTAL-RS485.cpp index 956ece03..7ca4e98f 100644 --- a/Software/src/inverter/KOSTAL-RS485.cpp +++ b/Software/src/inverter/KOSTAL-RS485.cpp @@ -17,17 +17,17 @@ static int8_t f2_startup_count = 0; static boolean B1_delay = false; static unsigned long B1_last_millis = 0; +static unsigned long currentMillis; union f32b { float f; byte b[4]; }; - uint8_t frame1[40] = { 0x06, 0xE2, 0xFF, 0x02, 0xFF, 0x29, // Frame header 0x01, 0x08, 0x80, 0x43, // 256.063 Nominal voltage / 5*51.2=256 first byte 0x01 or 0x04 - 0xE4, 0x70, 0x8A, 0x5C, // 266.74 => Voltage at 100% SOC + 0xE4, 0x70, 0x8A, 0x5C, // These might be Umin & Unax, Uint16 0xB5, 0x02, 0xD3, 0x01, // Battery Serial number? Modbus register 527 0x01, 0x05, 0xC8, 0x41, // 25.0024 ? 0xC2, 0x18, // Battery Firmware, modbus register 586 @@ -43,24 +43,32 @@ uint8_t frame1[40] = { uint8_t frame2[64] = {0x0A, // This may also been 0x06, seen at startup when live values not valid, but also occasionally single frames. 0xE2, 0xFF, 0x02, 0xFF, 0x29, // frame Header - 0x1D, 0x5A, 0x85, 0x43, // Cyrrent Voltage (float) Modbus register 216, Bit 6-9 - 0x01, 0x03, 0x8D, 0x43, // Max Voltage (2 byte float), Bit 10-13 - // 0x8D43 = 36163 (361.63) DALA: Is this nominal voltage? - 0x01, 0x03, 0xAC, 0x41, // Temp (2 byte float) Modbus register 214, Bit 14-17 - 0x01, 0x01, 0x01, 0x01, // Peak Current (1s period), Bytes 18-21 - 0x01, 0x01, 0x01, 0x01, // Avg current (1s period), Bytes 22-25 + 0x1D, 0x5A, 0x85, 0x43, // Current Voltage (float) Modbus register 216, Bytes 6-9 + 0x01, 0x03, 0x8D, 0x43, // Max Voltage (2 byte float), Bytes 10-13 + + 0x01, 0x03, // Unknown, 0x03 seen also 0x0F, 0x07, might hava something to do with current + 0xAC, 0x41, // BAttery Temperature (2 byte float) Modbus register 214, Bytes 16-17 + 0x01, 0x01, 0x01, 0x01, // Peak Current (1s period?), Bytes 18-21 - Communication fault seen with some values (>10A?) + 0x01, 0x01, 0x01, 0x01, // Avg current (1s period?), Bytes 22-25 - Communication fault seen with some values (>10A?) + 0x01, 0x03, 0x48, 0x42, // Max discharge current (2 byte float), Bit 26-29, // Sunspec: ADisChaMax + 0x01, 0x03, // Unknown 0xC8, 0x41, // Battery gross capacity, Ah (2 byte float) , Bytes 30-33, Modbus 512 + 0x01, // Unknown 0x16, // This seems to have something to do with cell temperatures + 0xA0, 0x41, // Max charge current (2 byte float) Bit 36-37, ZERO WHEN SOC=100 // Sunspec: AChaMax + 0xCD, 0xCC, 0xB4, 0x41, // MaxCellTemp (4 byte float) Bit 38-41 - 0x01, 0x0C, 0xA4, 0x41, // MinCellTemp (4 byte float) Bit 42-45 + 0x01, 0x0C, 0xA4, 0x41, // MinCellTemp (4 byte float) Bit 42-45 + 0xA4, 0x70, 0x55, 0x40, // MaxCellVolt (float), Bit 46-49 0x7D, 0x3F, 0x55, 0x40, // MinCellVolt (float), Bit 50-53 + 0xFE, // Cylce count , Bit 54 0x04, // Cycle count? , Bit 55 0x01, // Byte 56 @@ -207,9 +215,9 @@ void update_RS485_registers_inverter() { float2frameMSB(frame2, (float)average_temperature_dC / 10, 16); - float2frameMSB(frame2, (float)datalayer.battery.status.current_dA / 10, - 20); // Peak discharge? current (2 byte float) - float2frameMSB(frame2, (float)datalayer.battery.status.current_dA / 10, 24); +// Some current values causes communication error, must be resolved, why. +// float2frameMSB(frame2, (float)datalayer.battery.status.current_dA / 10, 20); // Peak discharge? current (2 byte float) +// float2frameMSB(frame2, (float)datalayer.battery.status.current_dA / 10, 24); float2frameMSB(frame2, (float)discharge_current_dA / 10, 28); // BAttery capacity Ah @@ -265,7 +273,7 @@ static uint8_t rx_index = 0; void receive_RS485() // Runs as fast as possible to handle the serial stream { - unsigned long currentMillis = millis(); + currentMillis = millis(); if(B1_delay) { if ((currentMillis - B1_last_millis) >1000) From c8b2c427efb59e5c180ef3fc37e0d874658b03e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakob=20L=C3=B6w?= Date: Tue, 15 Oct 2024 09:00:49 +0200 Subject: [PATCH 033/210] :sparkles: add support for the pylontech LV battery protocol when talking to inverters (very common to be supported by various manufacturers) --- Software/src/inverter/PYLON-LV-CAN.cpp | 145 +++++++++++++++++++++++++ Software/src/inverter/PYLON-LV-CAN.h | 17 +++ 2 files changed, 162 insertions(+) create mode 100644 Software/src/inverter/PYLON-LV-CAN.cpp create mode 100644 Software/src/inverter/PYLON-LV-CAN.h diff --git a/Software/src/inverter/PYLON-LV-CAN.cpp b/Software/src/inverter/PYLON-LV-CAN.cpp new file mode 100644 index 00000000..15efe829 --- /dev/null +++ b/Software/src/inverter/PYLON-LV-CAN.cpp @@ -0,0 +1,145 @@ +#include "../include.h" +#ifdef PYLON_LV_CAN +#include "../datalayer/datalayer.h" +#include "PYLON-LV-CAN.h" + +/* Do not change code below unless you are sure what you are doing */ + +static unsigned long previousMillis1000ms = 0; + +CAN_frame PYLON_351 = {.FD = false, + .ext_ID = false, + .DLC = 6, + .ID = 0x351, + .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame PYLON_355 = {.FD = false, + .ext_ID = false, + .DLC = 4, + .ID = 0x355, + .data = {0x00, 0x00, 0x00, 0x00}}; +CAN_frame PYLON_356 = {.FD = false, + .ext_ID = false, + .DLC = 6, + .ID = 0x356, + .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame PYLON_359 = {.FD = false, + .ext_ID = false, + .DLC = 7, + .ID = 0x359, + .data = {0x00, 0x00, 0x00, 0x00, PACK_NUMBER, 'P', 'N'}}; +CAN_frame PYLON_35C = {.FD = false, + .ext_ID = false, + .DLC = 2, + .ID = 0x35C, + .data = {0x00, 0x00}}; +CAN_frame PYLON_35E = {.FD = false, + .ext_ID = false, + .DLC = 8, + .ID = 0x35E, + .data = { + MANUFACTURER_NAME[0], + MANUFACTURER_NAME[1], + MANUFACTURER_NAME[2], + MANUFACTURER_NAME[3], + MANUFACTURER_NAME[4], + MANUFACTURER_NAME[5], + MANUFACTURER_NAME[6], + MANUFACTURER_NAME[7], + }}; + +void update_values_can_inverter() { + // This function maps all the values fetched from battery CAN to the correct CAN messages + + // 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[1] = datalayer.battery.status.voltage_dV >> 8; + int16_t maxChargeCurrent = datalayer.battery.status.max_charge_power_W * 10 / datalayer.battery.status.voltage_dV; + PYLON_351.data.u8[2] = maxChargeCurrent & 0xff; + PYLON_351.data.u8[3] = maxChargeCurrent >> 8; + int16_t maxDischargeCurrent = datalayer.battery.status.max_discharge_power_W * 10 / 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[1] = (datalayer.battery.status.reported_soc / 10) >> 8; + PYLON_355.data.u8[2] = (datalayer.battery.status.soh_pptt / 10) & 0xff; + PYLON_355.data.u8[3] = (datalayer.battery.status.soh_pptt / 10) >> 8; + + PYLON_356.data.u8[0] = datalayer.battery.status.voltage_dV & 0xff; + PYLON_356.data.u8[1] = datalayer.battery.status.voltage_dV >> 8; + PYLON_356.data.u8[2] = datalayer.battery.status.current_dA & 0xff; + PYLON_356.data.u8[3] = datalayer.battery.status.current_dA >> 8; + PYLON_356.data.u8[4] = datalayer.battery.status.temperature_max_dC & 0xff; + PYLON_356.data.u8[5] = datalayer.battery.status.temperature_max_dC >> 8; + + // initialize all errors and warnings to 0 + PYLON_359.data.u8[0] = 0x00; + PYLON_359.data.u8[1] = 0x00; + PYLON_359.data.u8[2] = 0x00; + PYLON_359.data.u8[3] = 0x00; + PYLON_359.data.u8[4] = PACK_NUMBER; + PYLON_359.data.u8[5] = 'P'; + PYLON_359.data.u8[6] = 'N'; + + // ERRORS + if(datalayer.battery.status.current_dA >= maxDischargeCurrent) + PYLON_359.data.u8[0] |= 0x80; + if(datalayer.battery.status.temperature_min_dC <= MIN_TEMPERATURE) + PYLON_359.data.u8[0] |= 0x10; + if(datalayer.battery.status.temperature_max_dC >= 500) + PYLON_359.data.u8[0] |= 0x0C; + if(datalayer.battery.status.voltage_dV * 100 <= datalayer.battery.info.min_cell_voltage_mV) + PYLON_359.data.u8[0] |= 0x04; + // we never set PYLON_359.data.u8[1] |= 0x80 called "BMS internal" + if(datalayer.battery.status.current_dA <= -1 * maxChargeCurrent) + PYLON_359.data.u8[1] |= 0x01; + + // WARNINGS (using same rules as errors but reporting earlier) + if(datalayer.battery.status.current_dA >= maxDischargeCurrent * WARNINGS_PERCENT / 100) + PYLON_359.data.u8[2] |= 0x80; + if(datalayer.battery.status.temperature_min_dC <= MIN_TEMPERATURE * WARNINGS_PERCENT / 100) + PYLON_359.data.u8[2] |= 0x10; + if(datalayer.battery.status.temperature_max_dC >= MAX_TEMPERATURE * WARNINGS_PERCENT / 100) + PYLON_359.data.u8[2] |= 0x0C; + if(datalayer.battery.status.voltage_dV * 100 <= datalayer.battery.info.min_cell_voltage_mV + 100) + PYLON_359.data.u8[2] |= 0x04; + // we never set PYLON_359.data.u8[3] |= 0x80 called "BMS internal" + if(datalayer.battery.status.current_dA <= -1 * maxChargeCurrent * WARNINGS_PERCENT / 100) + PYLON_359.data.u8[3] |= 0x01; + + PYLON_35C.data.u8[0] = 0xC0; // enable charging and discharging + PYLON_35C.data.u8[1] = 0x00; + if(datalayer.battery.status.real_soc <= datalayer.battery.settings.min_percentage) + PYLON_35C.data.u8[0] = 0xA0; // enable charing, set charge immediately + if(datalayer.battery.status.real_soc >= datalayer.battery.settings.max_percentage) + PYLON_35C.data.u8[0] = 0x40; // enable discharging only + + // PYLON_35E is pre-filled with the manufacturer name +} + +void receive_can_inverter(CAN_frame rx_frame) { + switch (rx_frame.ID) { + case 0x305: //Message originating from inverter. + // TODO: according to the spec, this message includes only 0-bytes + // we could however use this to check if the inverter is still alive + break; + default: + break; + } +} + +void send_can_inverter() { + unsigned long currentMillis = millis(); + + if (currentMillis - previousMillis1000ms >= 1000) { + previousMillis1000ms = currentMillis; + + transmit_can(&PYLON_351, can_config.inverter); + transmit_can(&PYLON_355, can_config.inverter); + transmit_can(&PYLON_356, can_config.inverter); + transmit_can(&PYLON_359, can_config.inverter); + transmit_can(&PYLON_35C, can_config.inverter); + transmit_can(&PYLON_35E, can_config.inverter); + } +} +#endif diff --git a/Software/src/inverter/PYLON-LV-CAN.h b/Software/src/inverter/PYLON-LV-CAN.h new file mode 100644 index 00000000..8f01b326 --- /dev/null +++ b/Software/src/inverter/PYLON-LV-CAN.h @@ -0,0 +1,17 @@ +#ifndef PYLON_LV_CAN_H +#define PYLON_LV_CAN_H +#include "../include.h" + +#define CAN_INVERTER_SELECTED + +#define MANUFACTURER_NAME "BatEmuLV" +#define PACK_NUMBER 0x01 +#define MIN_TEMPERATURE 40 // 40 for 4.0 °C +#define MAX_TEMPERATURE 500 // 500 for 50.0 °C +#define WARNINGS_PERCENT 80 // 80 means after reaching 80% of a nominal value a warning is produced (e.g. 80% of max current) + +void send_system_data(); +void send_setup_info(); +void transmit_can(CAN_frame* tx_frame, int interface); + +#endif From 0047a346bcdda770dfa6bf6b52212c0c15e0a0a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakob=20L=C3=B6w?= Date: Tue, 15 Oct 2024 09:07:35 +0200 Subject: [PATCH 034/210] :sparkles: track pylon LV compatible inverter responses for checking if inverter is dead --- Software/src/inverter/PYLON-LV-CAN.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Software/src/inverter/PYLON-LV-CAN.cpp b/Software/src/inverter/PYLON-LV-CAN.cpp index 15efe829..401153b1 100644 --- a/Software/src/inverter/PYLON-LV-CAN.cpp +++ b/Software/src/inverter/PYLON-LV-CAN.cpp @@ -5,6 +5,7 @@ /* Do not change code below unless you are sure what you are doing */ +static unsigned long previousInverterPacketMillis = 0; static unsigned long previousMillis1000ms = 0; CAN_frame PYLON_351 = {.FD = false, @@ -120,8 +121,9 @@ void update_values_can_inverter() { void receive_can_inverter(CAN_frame rx_frame) { switch (rx_frame.ID) { case 0x305: //Message originating from inverter. - // TODO: according to the spec, this message includes only 0-bytes - // we could however use this to check if the inverter is still alive + // according to the spec, this message includes only 0-bytes + datalayer.system.status.CAN_inverter_still_alive = true; + previousInverterPacketMillis = millis(); break; default: break; @@ -131,6 +133,10 @@ void receive_can_inverter(CAN_frame rx_frame) { void send_can_inverter() { unsigned long currentMillis = millis(); + if(currentMillis - previousInverterPacketMillis >= 3000) { + datalayer.system.status.CAN_inverter_still_alive = false; + } + if (currentMillis - previousMillis1000ms >= 1000) { previousMillis1000ms = currentMillis; From 22e846570d9fc3989722703be8aea7f09ee468f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakob=20L=C3=B6w?= Date: Tue, 15 Oct 2024 09:15:11 +0200 Subject: [PATCH 035/210] :art: move min/max temperature definitions to the user settings --- Software/USER_SETTINGS.h | 7 ++++++- Software/src/devboard/safety/safety.cpp | 4 ++-- Software/src/inverter/PYLON-LV-CAN.cpp | 8 ++++---- Software/src/inverter/PYLON-LV-CAN.h | 2 -- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/Software/USER_SETTINGS.h b/Software/USER_SETTINGS.h index 236bbc21..97c74e1d 100644 --- a/Software/USER_SETTINGS.h +++ b/Software/USER_SETTINGS.h @@ -36,7 +36,8 @@ //#define BYD_SMA //Enable this line to emulate a SMA compatible "BYD Battery-Box HVS 10.2KW battery" over CAN bus //#define BYD_MODBUS //Enable this line to emulate a "BYD 11kWh HVM battery" over Modbus RTU //#define FOXESS_CAN //Enable this line to emulate a "HV2600/ECS4100 battery" over CAN bus -//#define PYLON_CAN //Enable this line to emulate a "Pylontech battery" over CAN bus +//#define PYLON_LV_CAN //Enable this line to emulate a "48V Pylontech battery" over CAN bus +//#define PYLON_CAN //Enable this line to emulate a "High Voltage Pylontech battery" over CAN bus //#define SMA_CAN //Enable this line to emulate a "BYD Battery-Box H 8.9kWh, 7 mod" over CAN bus //#define SMA_TRIPOWER_CAN //Enable this line to emulate a "SMA Home Storage battery" over CAN bus //#define SOFAR_CAN //Enable this line to emulate a "Sofar Energy Storage Inverter High Voltage BMS General Protocol (Extended Frame)" over CAN bus @@ -93,6 +94,10 @@ #define BATTERY_MAXPERCENTAGE 8000 // 2000 = 20.0% , Min percentage the battery will discharge to (Inverter gets 0% when reached) #define BATTERY_MINPERCENTAGE 2000 +// 500 = 50.0 °C , Max temperature (Will produce a battery overheat event if above) +#define BATTERY_MAXTEMPERATURE 500 +// -250 = -25.0 °C , Min temperature (Will produce a battery frozen event if below) +#define BATTERY_MINTEMPERATURE -250 // 300 = 30.0A , BYD CAN specific setting, Max charge in Amp (Some inverters needs to be limited) #define BATTERY_MAX_CHARGE_AMP 300 // 300 = 30.0A , BYD CAN specific setting, Max discharge in Amp (Some inverters needs to be limited) diff --git a/Software/src/devboard/safety/safety.cpp b/Software/src/devboard/safety/safety.cpp index a94702d3..15823a34 100644 --- a/Software/src/devboard/safety/safety.cpp +++ b/Software/src/devboard/safety/safety.cpp @@ -27,14 +27,14 @@ void update_machineryprotection() { } // Battery is overheated! - if (datalayer.battery.status.temperature_max_dC > 500) { + if (datalayer.battery.status.temperature_max_dC > BATTERY_MAXTEMPERATURE) { set_event(EVENT_BATTERY_OVERHEAT, datalayer.battery.status.temperature_max_dC); } else { clear_event(EVENT_BATTERY_OVERHEAT); } // Battery is frozen! - if (datalayer.battery.status.temperature_min_dC < -250) { + if (datalayer.battery.status.temperature_min_dC < BATTERY_MINTEMPERATURE) { set_event(EVENT_BATTERY_FROZEN, datalayer.battery.status.temperature_min_dC); } else { clear_event(EVENT_BATTERY_FROZEN); diff --git a/Software/src/inverter/PYLON-LV-CAN.cpp b/Software/src/inverter/PYLON-LV-CAN.cpp index 401153b1..5fd02fd4 100644 --- a/Software/src/inverter/PYLON-LV-CAN.cpp +++ b/Software/src/inverter/PYLON-LV-CAN.cpp @@ -85,9 +85,9 @@ void update_values_can_inverter() { // ERRORS if(datalayer.battery.status.current_dA >= maxDischargeCurrent) PYLON_359.data.u8[0] |= 0x80; - if(datalayer.battery.status.temperature_min_dC <= MIN_TEMPERATURE) + if(datalayer.battery.status.temperature_min_dC <= BATTERY_MINTEMPERATURE) PYLON_359.data.u8[0] |= 0x10; - if(datalayer.battery.status.temperature_max_dC >= 500) + if(datalayer.battery.status.temperature_max_dC >= BATTERY_MAXTEMPERATURE) PYLON_359.data.u8[0] |= 0x0C; if(datalayer.battery.status.voltage_dV * 100 <= datalayer.battery.info.min_cell_voltage_mV) PYLON_359.data.u8[0] |= 0x04; @@ -98,9 +98,9 @@ void update_values_can_inverter() { // WARNINGS (using same rules as errors but reporting earlier) if(datalayer.battery.status.current_dA >= maxDischargeCurrent * WARNINGS_PERCENT / 100) PYLON_359.data.u8[2] |= 0x80; - if(datalayer.battery.status.temperature_min_dC <= MIN_TEMPERATURE * WARNINGS_PERCENT / 100) + if(datalayer.battery.status.temperature_min_dC <= BATTERY_MINTEMPERATURE * WARNINGS_PERCENT / 100) PYLON_359.data.u8[2] |= 0x10; - if(datalayer.battery.status.temperature_max_dC >= MAX_TEMPERATURE * WARNINGS_PERCENT / 100) + if(datalayer.battery.status.temperature_max_dC >= BATTERY_MAXTEMPERATURE * WARNINGS_PERCENT / 100) PYLON_359.data.u8[2] |= 0x0C; if(datalayer.battery.status.voltage_dV * 100 <= datalayer.battery.info.min_cell_voltage_mV + 100) PYLON_359.data.u8[2] |= 0x04; diff --git a/Software/src/inverter/PYLON-LV-CAN.h b/Software/src/inverter/PYLON-LV-CAN.h index 8f01b326..d2c66677 100644 --- a/Software/src/inverter/PYLON-LV-CAN.h +++ b/Software/src/inverter/PYLON-LV-CAN.h @@ -6,8 +6,6 @@ #define MANUFACTURER_NAME "BatEmuLV" #define PACK_NUMBER 0x01 -#define MIN_TEMPERATURE 40 // 40 for 4.0 °C -#define MAX_TEMPERATURE 500 // 500 for 50.0 °C #define WARNINGS_PERCENT 80 // 80 means after reaching 80% of a nominal value a warning is produced (e.g. 80% of max current) void send_system_data(); From ed4161ee0be6232289dac9496c5d5e67ece1cdce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakob=20L=C3=B6w?= Date: Tue, 15 Oct 2024 09:49:47 +0200 Subject: [PATCH 036/210] :art: run clang using pre-commit for code formatting --- Software/src/inverter/PYLON-LV-CAN.cpp | 69 ++++++++++++-------------- Software/src/inverter/PYLON-LV-CAN.h | 3 +- 2 files changed, 33 insertions(+), 39 deletions(-) diff --git a/Software/src/inverter/PYLON-LV-CAN.cpp b/Software/src/inverter/PYLON-LV-CAN.cpp index 5fd02fd4..4cec04b1 100644 --- a/Software/src/inverter/PYLON-LV-CAN.cpp +++ b/Software/src/inverter/PYLON-LV-CAN.cpp @@ -13,11 +13,7 @@ CAN_frame PYLON_351 = {.FD = false, .DLC = 6, .ID = 0x351, .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; -CAN_frame PYLON_355 = {.FD = false, - .ext_ID = false, - .DLC = 4, - .ID = 0x355, - .data = {0x00, 0x00, 0x00, 0x00}}; +CAN_frame PYLON_355 = {.FD = false, .ext_ID = false, .DLC = 4, .ID = 0x355, .data = {0x00, 0x00, 0x00, 0x00}}; CAN_frame PYLON_356 = {.FD = false, .ext_ID = false, .DLC = 6, @@ -28,27 +24,23 @@ CAN_frame PYLON_359 = {.FD = false, .DLC = 7, .ID = 0x359, .data = {0x00, 0x00, 0x00, 0x00, PACK_NUMBER, 'P', 'N'}}; -CAN_frame PYLON_35C = {.FD = false, - .ext_ID = false, - .DLC = 2, - .ID = 0x35C, - .data = {0x00, 0x00}}; +CAN_frame PYLON_35C = {.FD = false, .ext_ID = false, .DLC = 2, .ID = 0x35C, .data = {0x00, 0x00}}; CAN_frame PYLON_35E = {.FD = false, .ext_ID = false, .DLC = 8, .ID = 0x35E, .data = { - MANUFACTURER_NAME[0], - MANUFACTURER_NAME[1], - MANUFACTURER_NAME[2], - MANUFACTURER_NAME[3], - MANUFACTURER_NAME[4], - MANUFACTURER_NAME[5], - MANUFACTURER_NAME[6], - MANUFACTURER_NAME[7], - }}; - -void update_values_can_inverter() { + MANUFACTURER_NAME[0], + MANUFACTURER_NAME[1], + MANUFACTURER_NAME[2], + MANUFACTURER_NAME[3], + MANUFACTURER_NAME[4], + MANUFACTURER_NAME[5], + MANUFACTURER_NAME[6], + MANUFACTURER_NAME[7], + }}; + +void update_values_can_inverter() { // This function maps all the values fetched from battery CAN to the correct CAN messages // TODO: officially this value is "battery charge voltage". Do we need to add something here to the actual voltage? @@ -57,7 +49,8 @@ void update_values_can_inverter() { int16_t maxChargeCurrent = datalayer.battery.status.max_charge_power_W * 10 / datalayer.battery.status.voltage_dV; PYLON_351.data.u8[2] = maxChargeCurrent & 0xff; PYLON_351.data.u8[3] = maxChargeCurrent >> 8; - int16_t maxDischargeCurrent = datalayer.battery.status.max_discharge_power_W * 10 / datalayer.battery.status.voltage_dV; + int16_t maxDischargeCurrent = + datalayer.battery.status.max_discharge_power_W * 10 / datalayer.battery.status.voltage_dV; PYLON_351.data.u8[4] = maxDischargeCurrent & 0xff; PYLON_351.data.u8[5] = maxDischargeCurrent >> 8; @@ -83,37 +76,37 @@ void update_values_can_inverter() { PYLON_359.data.u8[6] = 'N'; // ERRORS - if(datalayer.battery.status.current_dA >= maxDischargeCurrent) + if (datalayer.battery.status.current_dA >= maxDischargeCurrent) 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; - if(datalayer.battery.status.temperature_max_dC >= BATTERY_MAXTEMPERATURE) + if (datalayer.battery.status.temperature_max_dC >= BATTERY_MAXTEMPERATURE) PYLON_359.data.u8[0] |= 0x0C; - 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; // 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 * maxChargeCurrent) PYLON_359.data.u8[1] |= 0x01; // 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 >= maxDischargeCurrent * WARNINGS_PERCENT / 100) 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; - if(datalayer.battery.status.temperature_max_dC >= BATTERY_MAXTEMPERATURE * WARNINGS_PERCENT / 100) + if (datalayer.battery.status.temperature_max_dC >= BATTERY_MAXTEMPERATURE * WARNINGS_PERCENT / 100) PYLON_359.data.u8[2] |= 0x0C; - 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; // 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 * maxChargeCurrent * WARNINGS_PERCENT / 100) 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 PYLON_35C.data.u8[1] = 0x00; - if(datalayer.battery.status.real_soc <= datalayer.battery.settings.min_percentage) - PYLON_35C.data.u8[0] = 0xA0; // enable charing, set charge immediately - if(datalayer.battery.status.real_soc >= datalayer.battery.settings.max_percentage) - PYLON_35C.data.u8[0] = 0x40; // enable discharging only + if (datalayer.battery.status.real_soc <= datalayer.battery.settings.min_percentage) + PYLON_35C.data.u8[0] = 0xA0; // enable charing, set charge immediately + if (datalayer.battery.status.real_soc >= datalayer.battery.settings.max_percentage) + PYLON_35C.data.u8[0] = 0x40; // enable discharging only // PYLON_35E is pre-filled with the manufacturer name } @@ -133,7 +126,7 @@ void receive_can_inverter(CAN_frame rx_frame) { void send_can_inverter() { unsigned long currentMillis = millis(); - if(currentMillis - previousInverterPacketMillis >= 3000) { + if (currentMillis - previousInverterPacketMillis >= 3000) { datalayer.system.status.CAN_inverter_still_alive = false; } diff --git a/Software/src/inverter/PYLON-LV-CAN.h b/Software/src/inverter/PYLON-LV-CAN.h index d2c66677..45376d2b 100644 --- a/Software/src/inverter/PYLON-LV-CAN.h +++ b/Software/src/inverter/PYLON-LV-CAN.h @@ -6,7 +6,8 @@ #define MANUFACTURER_NAME "BatEmuLV" #define PACK_NUMBER 0x01 -#define WARNINGS_PERCENT 80 // 80 means after reaching 80% of a nominal value a warning is produced (e.g. 80% of max current) +// 80 means after reaching 80% of a nominal value a warning is produced (e.g. 80% of max current) +#define WARNINGS_PERCENT 80 void send_system_data(); void send_setup_info(); From f872344332cc3b96a4fae6b0ca12dec9b7e6a5cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Tue, 15 Oct 2024 15:53:35 +0300 Subject: [PATCH 037/210] Add Afore CAN inverter protocol --- Software/USER_SETTINGS.h | 1 + Software/src/inverter/AFORE-CAN.cpp | 257 ++++++++++++++++++++++++++++ Software/src/inverter/AFORE-CAN.h | 11 ++ Software/src/inverter/INVERTERS.h | 4 + 4 files changed, 273 insertions(+) create mode 100644 Software/src/inverter/AFORE-CAN.cpp create mode 100644 Software/src/inverter/AFORE-CAN.h diff --git a/Software/USER_SETTINGS.h b/Software/USER_SETTINGS.h index 236bbc21..c8cdd8ec 100644 --- a/Software/USER_SETTINGS.h +++ b/Software/USER_SETTINGS.h @@ -32,6 +32,7 @@ //#define DOUBLE_BATTERY //Enable this line if you use two identical batteries at the same time (requires DUAL_CAN setup) /* Select inverter communication protocol. See Wiki for which to use with your inverter: https://github.com/dalathegreat/BYD-Battery-Emulator-For-Gen24/wiki */ +//#define AFORE_CAN //Enable this line to emulate an "Afore battery" over CAN bus //#define BYD_CAN //Enable this line to emulate a "BYD Battery-Box Premium HVS" over CAN Bus //#define BYD_SMA //Enable this line to emulate a SMA compatible "BYD Battery-Box HVS 10.2KW battery" over CAN bus //#define BYD_MODBUS //Enable this line to emulate a "BYD 11kWh HVM battery" over Modbus RTU diff --git a/Software/src/inverter/AFORE-CAN.cpp b/Software/src/inverter/AFORE-CAN.cpp new file mode 100644 index 00000000..6ac88873 --- /dev/null +++ b/Software/src/inverter/AFORE-CAN.cpp @@ -0,0 +1,257 @@ +#include "../include.h" +#ifdef AFORE_CAN +#include "../datalayer/datalayer.h" +#include "AFORE-CAN.h" + +#define SOCMAX 100 +#define SOCMIN 1 + +/* Do not change code below unless you are sure what you are doing */ +/* The code is following the Afore 2.3 CAN standard, little-endian, 500kbps, from 2023.08.07 */ +static uint8_t inverter_status = + 0; //0 = init, 1 = standby, 2 = starting, 3 = grid connected, 4 off-grid, 5 diesel generator, 6 grid connected, but disconnected, 7off grid and disconnected, 8 = power failure processing, 9 = power off, 10 = Failure +static bool time_to_send_info = false; +static uint8_t char0 = 0; +static uint8_t char1 = 0; +static uint8_t char2 = 0; +static uint8_t char3 = 0; +static uint8_t char4 = 0; +//Actual content messages +CAN_frame AFORE_350 = {.FD = false, // Operation information + .ext_ID = false, + .DLC = 8, + .ID = 0x350, + .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame AFORE_351 = {.FD = false, + .ext_ID = false, + .DLC = 8, + .ID = 0x351, + .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame AFORE_352 = {.FD = false, + .ext_ID = false, + .DLC = 8, + .ID = 0x352, + .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame AFORE_353 = {.FD = false, + .ext_ID = false, + .DLC = 8, + .ID = 0x353, + .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame AFORE_354 = {.FD = false, + .ext_ID = false, + .DLC = 8, + .ID = 0x354, + .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame AFORE_355 = {.FD = false, + .ext_ID = false, + .DLC = 8, + .ID = 0x355, + .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame AFORE_356 = {.FD = false, + .ext_ID = false, + .DLC = 8, + .ID = 0x356, + .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame AFORE_357 = {.FD = false, + .ext_ID = false, + .DLC = 8, + .ID = 0x357, + .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame AFORE_358 = {.FD = false, + .ext_ID = false, + .DLC = 8, + .ID = 0x358, + .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame AFORE_359 = {.FD = false, // Serial number 0-7 + .ext_ID = false, + .DLC = 8, + .ID = 0x359, + .data = {0x62, 0x61, 0x74, 0x74, 0x65, 0x72, 0x79, 0x2D}}; // Battery- +CAN_frame AFORE_35A = {.FD = false, // Serial number 8-15 + .ext_ID = false, + .DLC = 8, + .ID = 0x35A, + .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 + //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*/ + AFORE_350.data.u8[0] = (datalayer.battery.status.voltage_dV & 0x00FF); + AFORE_350.data.u8[1] = (datalayer.battery.status.voltage_dV >> 8); + //Total battery current unit: 0.1A offset 5000; positive is charging, + //Negative means discharge; for example: 1A = (5010-5000)/10 + AFORE_350.data.u8[2] = ((datalayer.battery.status.current_dA + 5000) & 0x00FF); + AFORE_350.data.u8[3] = ((datalayer.battery.status.current_dA + 5000) >> 8); + //Battery temperature unit: 0.1C offset 1000; for example: 20C + //= (1200 -1000)/10; when all temperatures are greater than or equal to 0 + AFORE_350.data.u8[4] = ((datalayer.battery.status.temperature_max_dC + 1000) & 0x00FF); + AFORE_350.data.u8[5] = ((datalayer.battery.status.temperature_max_dC + 1000) >> 8); + + /*0x351 - Battery information*/ + AFORE_351.data.u8[0] = (datalayer.battery.status.reported_soc / 100); //Remove decimals, 0-100 + AFORE_351.data.u8[1] = (datalayer.battery.status.soh_pptt / 100); //Remove decimals, 0-100 + AFORE_351.data.u8[2] = SOCMAX; + AFORE_351.data.u8[3] = SOCMIN; + AFORE_351.data.u8[4] = 0x03; //Bit0 and Bit1 set + if ((max_charge_current_dA == 0) || (datalayer.battery.status.reported_soc == 10000) || + (datalayer.battery.status.bms_status == FAULT)) { + AFORE_351.data.u8[4] &= ~0x01; // Remove Bit0 (clear) Charge enable flag + } + if ((max_discharge_current_dA == 0) || (datalayer.battery.status.reported_soc == 0) || + (datalayer.battery.status.bms_status == FAULT)) { + AFORE_351.data.u8[4] &= ~0x02; // Remove Bit1 (clear) Discharge enable flag + } + // Bit5-7 is BMS working status. + //A value of 0 here is INIT, 1 = Normal operation, 2 = standby/sleep, 3 = warning, 4 = fault, rest is reserved + AFORE_351.data.u8[4] &= ~(0xE0); // Clear bits 5, 6, and 7 (11100000) + if (datalayer.battery.status.bms_status == FAULT) { + AFORE_351.data.u8[4] |= 0x80; // Set bits 5-7 to 0b100 (Fault = 4) + } else { // Normal mode + AFORE_351.data.u8[4] |= 0x20; // Set Bit5 to 1 (Normal operation) + } + AFORE_351.data.u8[6] = (datalayer.battery.info.number_of_cells & 0x00FF); + AFORE_351.data.u8[7] = (datalayer.battery.info.number_of_cells >> 8); + + /*0x352 - Protection parameters*/ + AFORE_352.data.u8[0] = (max_charge_current_dA & 0x00FF); + AFORE_352.data.u8[1] = (max_charge_current_dA >> 8); + AFORE_352.data.u8[2] = (max_discharge_current_dA & 0x00FF); + AFORE_352.data.u8[3] = (max_discharge_current_dA >> 8); + 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[6] = (datalayer.battery.info.min_design_voltage_dV & 0x00FF); + AFORE_352.data.u8[7] = (datalayer.battery.info.min_design_voltage_dV >> 8); + + /*0x353 - Fault information*/ + /* Fault H, bit, definitions + 0 ErrCellOverVolt + 1 ErrCellUnderVolt + 2 ErrOverVolt + 3 ErrUnderVolt + 4 ErrOverTemperature + 5 ErrUnderTemperature + 6 ErrBatIsolation + 7 ErrChargeCurrentOver + 8 ErrDischargeCurrentOver + 9 ErrSOCtooLow + 10 ErrBatteryInternalCommunicationFailure + 11 ErrBMSVoltageSupplyTooHigh + 12 ErrBMSVoltageSupplyTooLow + 13 ErrRelayFailure + 14 ErrPreChargerFailure + 15 InterlockOpen + AFORE_353.data.u8[0] = Fault H table & 0x00FF + AFORE_353.data.u8[1] = Fault H table >> 8); + /* Fault L, bit, definitions + 8 VoltageInterlockShortCircuit + 9 SystemFailure + 10 ErrorChargeReferenceOvervoltage + 11 ChargingMOSdamaged + 12 DischargeMOSdamaged + 13 CellOverTemperature + 14 CellUnderTemperature + 15 CellUnbalance + AFORE_353.data.u8[2] = Fault L table & 0x00FF + AFORE_353.data.u8[3] = Fault L table >> 8); + */ + + /*0x354 - Single cell voltage parameters*/ + AFORE_354.data.u8[0] = (datalayer.battery.status.cell_max_voltage_mV & 0x00FF); + AFORE_354.data.u8[1] = (datalayer.battery.status.cell_max_voltage_mV >> 8); + AFORE_354.data.u8[2] = (datalayer.battery.status.cell_min_voltage_mV & 0x00FF); + AFORE_354.data.u8[3] = (datalayer.battery.status.cell_min_voltage_mV >> 8); + AFORE_354.data.u8[4] = (1 & 0x00FF); //Maximum single cell voltage number, not used on emulator + AFORE_354.data.u8[5] = (1 >> 8); + AFORE_354.data.u8[6] = (2 & 0x00FF); //Minimum single cell voltage number, not used on emulator + AFORE_354.data.u8[7] = (2 >> 8); + + /*0x355 - Single cell temperature parameters*/ + AFORE_355.data.u8[0] = ((datalayer.battery.status.temperature_max_dC + 1000) & 0x00FF); + AFORE_355.data.u8[1] = ((datalayer.battery.status.temperature_max_dC + 1000) >> 8); + AFORE_355.data.u8[2] = ((datalayer.battery.status.temperature_min_dC + 1000) & 0x00FF); + AFORE_355.data.u8[3] = ((datalayer.battery.status.temperature_min_dC + 1000) >> 8); + AFORE_355.data.u8[4] = (1 & 0x00FF); //Maximum single cell temperature number, not used on emulator + AFORE_355.data.u8[5] = (1 >> 8); + AFORE_355.data.u8[6] = (2 & 0x00FF); //Minimum single cell temperature number, not used on emulator + AFORE_355.data.u8[7] = (2 >> 8); + + /*0x356 - Single cell protection parameters*/ + AFORE_356.data.u8[0] = (datalayer.battery.info.max_cell_voltage_mV & 0x00FF); + AFORE_356.data.u8[1] = (datalayer.battery.info.max_cell_voltage_mV >> 8); + AFORE_356.data.u8[2] = (datalayer.battery.info.min_cell_voltage_mV & 0x00FF); + AFORE_356.data.u8[3] = (datalayer.battery.info.min_cell_voltage_mV >> 8); + + /*0x357 - Warning information*/ + /* Warning, bit, definitions + 0 CellOverVolt + 1 CellUnderVolt + 2 OverVolt + 3 UnderVolt + 4 CellOverTemperature + 5 CellUnderTemperature + 6 TempOver + 7 TempUnder + 8 ChgCurrOver + 9 ErrSOCtooLow + 10 SocLow + AFORE_357.data.u8[0] = Warning table & 0x00FF + AFORE_357.data.u8[1] = Warning table >> 8); + */ +} + +void receive_can_inverter(CAN_frame rx_frame) { + switch (rx_frame.ID) { + case 0x305: // Every 1s from inverter + datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE; + char0 = rx_frame.data.u8[0]; // A + char1 = rx_frame.data.u8[0]; // F + char2 = rx_frame.data.u8[0]; // O + char3 = rx_frame.data.u8[0]; // R + char4 = rx_frame.data.u8[0]; // E + inverter_status = rx_frame.data.u8[7]; + time_to_send_info = true; + break; + default: + break; + } +} + +void send_can_inverter() { + if (time_to_send_info) { // Set every 1s if we get message from inverter + transmit_can(&AFORE_350, can_config.inverter); + transmit_can(&AFORE_351, can_config.inverter); + transmit_can(&AFORE_352, can_config.inverter); + transmit_can(&AFORE_353, can_config.inverter); + transmit_can(&AFORE_354, can_config.inverter); + transmit_can(&AFORE_355, can_config.inverter); + transmit_can(&AFORE_356, can_config.inverter); + transmit_can(&AFORE_357, can_config.inverter); + transmit_can(&AFORE_358, can_config.inverter); + transmit_can(&AFORE_359, can_config.inverter); + transmit_can(&AFORE_35A, can_config.inverter); + time_to_send_info = false; + } +} +#endif diff --git a/Software/src/inverter/AFORE-CAN.h b/Software/src/inverter/AFORE-CAN.h new file mode 100644 index 00000000..cc9f8548 --- /dev/null +++ b/Software/src/inverter/AFORE-CAN.h @@ -0,0 +1,11 @@ +#ifndef AFORE_CAN_H +#define AFORE_CAN_H +#include "../include.h" + +#define CAN_INVERTER_SELECTED + +void send_system_data(); +void send_setup_info(); +void transmit_can(CAN_frame* tx_frame, int interface); + +#endif diff --git a/Software/src/inverter/INVERTERS.h b/Software/src/inverter/INVERTERS.h index 731b6d5c..f2813654 100644 --- a/Software/src/inverter/INVERTERS.h +++ b/Software/src/inverter/INVERTERS.h @@ -3,6 +3,10 @@ #include "../../USER_SETTINGS.h" +#ifdef AFORE_CAN +#include "AFORE-CAN.h" +#endif + #ifdef BYD_CAN #include "BYD-CAN.h" #endif From 7a9057287a38e252ae8e63f92775c125aebc0b42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakob=20L=C3=B6w?= Date: Tue, 15 Oct 2024 15:50:10 +0200 Subject: [PATCH 038/210] :art: add pylon LV to webserver and inverters.h --- Software/src/devboard/webserver/webserver.cpp | 3 +++ Software/src/inverter/INVERTERS.h | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/Software/src/devboard/webserver/webserver.cpp b/Software/src/devboard/webserver/webserver.cpp index 8e60ccb3..30a9d585 100644 --- a/Software/src/devboard/webserver/webserver.cpp +++ b/Software/src/devboard/webserver/webserver.cpp @@ -495,6 +495,9 @@ String processor(const String& var) { #ifdef PYLON_CAN content += "Pylontech battery over CAN bus"; #endif // PYLON_CAN +#ifdef PYLON_LV_CAN + content += "Pylontech LV battery over CAN bus"; +#endif // PYLON_LV_CAN #ifdef SERIAL_LINK_TRANSMITTER content += "Serial link to another LilyGo board"; #endif // SERIAL_LINK_TRANSMITTER diff --git a/Software/src/inverter/INVERTERS.h b/Software/src/inverter/INVERTERS.h index 731b6d5c..42027418 100644 --- a/Software/src/inverter/INVERTERS.h +++ b/Software/src/inverter/INVERTERS.h @@ -23,6 +23,10 @@ #include "PYLON-CAN.h" #endif +#ifdef PYLON_LV_CAN +#include "PYLON-LV-CAN.h" +#endif + #ifdef SMA_CAN #include "SMA-CAN.h" #endif From df9b777734287453790130e449c30b2438cf353b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakob=20L=C3=B6w?= Date: Tue, 15 Oct 2024 16:33:28 +0200 Subject: [PATCH 039/210] :bug: fix crashes due to IntegerDivideByZero exceptions triggered in the pylontech-lv inverter implementation when no data is available yet --- Software/src/inverter/PYLON-LV-CAN.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Software/src/inverter/PYLON-LV-CAN.cpp b/Software/src/inverter/PYLON-LV-CAN.cpp index 4cec04b1..2ec6d4ec 100644 --- a/Software/src/inverter/PYLON-LV-CAN.cpp +++ b/Software/src/inverter/PYLON-LV-CAN.cpp @@ -43,6 +43,10 @@ CAN_frame PYLON_35E = {.FD = false, void update_values_can_inverter() { // 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? PYLON_351.data.u8[0] = datalayer.battery.status.voltage_dV & 0xff; PYLON_351.data.u8[1] = datalayer.battery.status.voltage_dV >> 8; From 2212b8dbaffc1451012447620b09dfb02cefb480 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakob=20L=C3=B6w?= Date: Tue, 15 Oct 2024 16:46:06 +0200 Subject: [PATCH 040/210] :bug: fix incorrect calculation of charge/discharge currents (should be dA instead of A) --- Software/src/inverter/PYLON-LV-CAN.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Software/src/inverter/PYLON-LV-CAN.cpp b/Software/src/inverter/PYLON-LV-CAN.cpp index 2ec6d4ec..84130193 100644 --- a/Software/src/inverter/PYLON-LV-CAN.cpp +++ b/Software/src/inverter/PYLON-LV-CAN.cpp @@ -50,11 +50,11 @@ void update_values_can_inverter() { // 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[1] = datalayer.battery.status.voltage_dV >> 8; - int16_t maxChargeCurrent = datalayer.battery.status.max_charge_power_W * 10 / datalayer.battery.status.voltage_dV; + int16_t maxChargeCurrent = datalayer.battery.status.max_charge_power_W * 100 / datalayer.battery.status.voltage_dV; PYLON_351.data.u8[2] = maxChargeCurrent & 0xff; PYLON_351.data.u8[3] = maxChargeCurrent >> 8; int16_t maxDischargeCurrent = - datalayer.battery.status.max_discharge_power_W * 10 / datalayer.battery.status.voltage_dV; + 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; From 2a1a91ba7574de6c837256f5e420b69c66554319 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakob=20L=C3=B6w?= Date: Tue, 15 Oct 2024 16:58:04 +0200 Subject: [PATCH 041/210] :bug: correctly set CAN_inverter_still_alive in pylon-lv-can --- Software/src/inverter/PYLON-LV-CAN.cpp | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/Software/src/inverter/PYLON-LV-CAN.cpp b/Software/src/inverter/PYLON-LV-CAN.cpp index 84130193..d169146e 100644 --- a/Software/src/inverter/PYLON-LV-CAN.cpp +++ b/Software/src/inverter/PYLON-LV-CAN.cpp @@ -5,7 +5,6 @@ /* Do not change code below unless you are sure what you are doing */ -static unsigned long previousInverterPacketMillis = 0; static unsigned long previousMillis1000ms = 0; CAN_frame PYLON_351 = {.FD = false, @@ -119,8 +118,7 @@ void receive_can_inverter(CAN_frame rx_frame) { switch (rx_frame.ID) { case 0x305: //Message originating from inverter. // according to the spec, this message includes only 0-bytes - datalayer.system.status.CAN_inverter_still_alive = true; - previousInverterPacketMillis = millis(); + datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE; break; default: break; @@ -130,10 +128,6 @@ void receive_can_inverter(CAN_frame rx_frame) { void send_can_inverter() { unsigned long currentMillis = millis(); - if (currentMillis - previousInverterPacketMillis >= 3000) { - datalayer.system.status.CAN_inverter_still_alive = false; - } - if (currentMillis - previousMillis1000ms >= 1000) { previousMillis1000ms = currentMillis; From e537195534c8aac4c781ed8225ad170400339fc1 Mon Sep 17 00:00:00 2001 From: rha Date: Tue, 15 Oct 2024 20:55:57 +0300 Subject: [PATCH 042/210] SOC 100% status byte fix --- Software/src/inverter/KOSTAL-RS485.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Software/src/inverter/KOSTAL-RS485.cpp b/Software/src/inverter/KOSTAL-RS485.cpp index 7ca4e98f..72aad3fa 100644 --- a/Software/src/inverter/KOSTAL-RS485.cpp +++ b/Software/src/inverter/KOSTAL-RS485.cpp @@ -44,9 +44,9 @@ uint8_t frame2[64] = {0x0A, // This may also been 0x06, seen 0xE2, 0xFF, 0x02, 0xFF, 0x29, // frame Header 0x1D, 0x5A, 0x85, 0x43, // Current Voltage (float) Modbus register 216, Bytes 6-9 - 0x01, 0x03, 0x8D, 0x43, // Max Voltage (2 byte float), Bytes 10-13 - 0x01, 0x03, // Unknown, 0x03 seen also 0x0F, 0x07, might hava something to do with current + 0x8D, 0x43, // Max Voltage (2 byte float), Bytes 12-13 + 0x01, 0x03, 0xAC, 0x41, // BAttery Temperature (2 byte float) Modbus register 214, Bytes 16-17 0x01, 0x01, 0x01, 0x01, // Peak Current (1s period?), Bytes 18-21 - Communication fault seen with some values (>10A?) 0x01, 0x01, 0x01, 0x01, // Avg current (1s period?), Bytes 22-25 - Communication fault seen with some values (>10A?) @@ -235,7 +235,8 @@ void update_RS485_registers_inverter() { else { float2frameMSB(frame2, 0.0 , 36); - frame2[57]=0x40; +// frame2[57]=0x40; + frame2[57]=0x02; frame2[59]=0x01; } From 19f4af9023678269f92cded1b0dd8c379e5beb88 Mon Sep 17 00:00:00 2001 From: rha Date: Wed, 16 Oct 2024 19:32:02 +0300 Subject: [PATCH 043/210] relay control --- Software/src/inverter/KOSTAL-RS485.cpp | 48 ++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 7 deletions(-) diff --git a/Software/src/inverter/KOSTAL-RS485.cpp b/Software/src/inverter/KOSTAL-RS485.cpp index 72aad3fa..f7557249 100644 --- a/Software/src/inverter/KOSTAL-RS485.cpp +++ b/Software/src/inverter/KOSTAL-RS485.cpp @@ -17,7 +17,11 @@ static int8_t f2_startup_count = 0; static boolean B1_delay = false; static unsigned long B1_last_millis = 0; -static unsigned long currentMillis; +static unsigned long currentMillis; +static unsigned long startupMillis=0; +static unsigned long contactorMillis=0; + +static boolean RX_allow = false; union f32b { float f; @@ -198,10 +202,9 @@ void update_RS485_registers_inverter() { average_temperature_dC = 0; } - if (f2_startup_count>8) + if (datalayer.system.status.contactor_control_closed ) { float2frame(frame2, (float)datalayer.battery.status.voltage_dV / 10, 6); // Confirmed OK mapping - datalayer.system.status.inverter_allows_contactor_closing = true; frame2[0]=0x0A; } else @@ -274,7 +277,32 @@ static uint8_t rx_index = 0; void receive_RS485() // Runs as fast as possible to handle the serial stream { + currentMillis = millis(); + + + if(datalayer.system.status.contactor_control_closed & !contactorMillis) + { + contactorMillis=currentMillis; + } + if (currentMillis-contactorMillis >= 2000 & !RX_allow) + { + RX_allow=true; + } + + if(((currentMillis-startupMillis) >= 2000 & currentMillis-startupMillis <=7000 )& datalayer.system.status.inverter_allows_contactor_closing) + { + // Disconnect allowed only, when curren zero + if (datalayer.battery.status.current_dA == 0) + { + datalayer.system.status.inverter_allows_contactor_closing = false; + } + } + else if(((currentMillis-startupMillis) >= 7000) & datalayer.system.status.inverter_allows_contactor_closing == false) + { + datalayer.system.status.inverter_allows_contactor_closing = true; + } + if(B1_delay) { if ((currentMillis - B1_last_millis) >1000) @@ -285,9 +313,11 @@ void receive_RS485() // Runs as fast as possible to handle the serial stream } else if (Serial2.available()) { RS485_RXFRAME[rx_index] = Serial2.read(); - rx_index++; - if (RS485_RXFRAME[rx_index - 1] == 0x00) { - if ((rx_index == 10) && (RS485_RXFRAME[0] == 0x09) && register_content_ok) { + if (RX_allow) + { + rx_index++; + if (RS485_RXFRAME[rx_index - 1] == 0x00) { + if ((rx_index == 10) && (RS485_RXFRAME[0] == 0x09) && register_content_ok) { #ifdef DEBUG_KOSTAL_RS485_DATA Serial.print("RX: "); for (uint8_t i = 0; i < 10; i++) { @@ -321,11 +351,14 @@ void receive_RS485() // Runs as fast as possible to handle the serial stream if (headerB && (RS485_RXFRAME[6] == 0x5E) && (RS485_RXFRAME[7] == 0x04)) { send_kostal(frame4, 8); // This needs more reverse engineering, disabled... - // datalayer.system.status.inverter_allows_contactor_closing = false; } if (headerA && (RS485_RXFRAME[6] == 0x4A) && (RS485_RXFRAME[7] == 0x08)) { // "frame 1" send_kostal(frame1, 40); + if (!startupMillis) + { + startupMillis=currentMillis; + } } if (headerA && (RS485_RXFRAME[6] == 0x4A) && (RS485_RXFRAME[7] == 0x04)) { // "frame 2" update_values_battery(); @@ -350,6 +383,7 @@ void receive_RS485() // Runs as fast as possible to handle the serial stream } } rx_index = 0; + } } if (rx_index >= 10) { rx_index = 0; From fe3c80af5dc06d6c9806cd5890365f40607910fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Fri, 18 Oct 2024 23:57:12 +0300 Subject: [PATCH 044/210] Make contactor check control-agnostic --- Software/USER_SETTINGS.h | 4 +- Software/src/datalayer/datalayer.h | 2 - Software/src/inverter/KOSTAL-RS485.cpp | 312 ++++++++++++------------- 3 files changed, 146 insertions(+), 172 deletions(-) diff --git a/Software/USER_SETTINGS.h b/Software/USER_SETTINGS.h index 5bef21c7..c1dd4207 100644 --- a/Software/USER_SETTINGS.h +++ b/Software/USER_SETTINGS.h @@ -35,7 +35,7 @@ //#define BYD_CAN //Enable this line to emulate a "BYD Battery-Box Premium HVS" over CAN Bus //#define BYD_SMA //Enable this line to emulate a SMA compatible "BYD Battery-Box HVS 10.2KW battery" over CAN bus //#define BYD_MODBUS //Enable this line to emulate a "BYD 11kWh HVM battery" over Modbus RTU -//#define BYD_KOSTAL_RS485 //Enable this line to emulate a "BYD 11kWh HVM battery" over Kostal RS485 +//#define BYD_KOSTAL_RS485 //Enable this line to emulate a "BYD 11kWh HVM battery" over Kostal RS485 //#define FOXESS_CAN //Enable this line to emulate a "HV2600/ECS4100 battery" over CAN bus //#define PYLON_CAN //Enable this line to emulate a "Pylontech battery" over CAN bus //#define SMA_CAN //Enable this line to emulate a "BYD Battery-Box H 8.9kWh, 7 mod" over CAN bus @@ -98,8 +98,6 @@ #define BATTERY_MAX_CHARGE_AMP 300 // 300 = 30.0A , BYD CAN specific setting, Max discharge in Amp (Some inverters needs to be limited) #define BATTERY_MAX_DISCHARGE_AMP 300 -// Battery nominal voltage dV -#define BATTERY_NOMINAL_VOLTAGE 3600 /* Do not change any code below this line unless you are sure what you are doing */ /* Only change battery specific settings in "USER_SETTINGS.h" */ diff --git a/Software/src/datalayer/datalayer.h b/Software/src/datalayer/datalayer.h index 9e1a869d..7c30ce93 100644 --- a/Software/src/datalayer/datalayer.h +++ b/Software/src/datalayer/datalayer.h @@ -23,8 +23,6 @@ typedef struct { 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; - /** Kostal specific setting, */ - uint16_t nominal_dV = BATTERY_NOMINAL_VOLTAGE; /** uint8_t */ /** Total number of cells in the pack */ diff --git a/Software/src/inverter/KOSTAL-RS485.cpp b/Software/src/inverter/KOSTAL-RS485.cpp index f7557249..388de6ca 100644 --- a/Software/src/inverter/KOSTAL-RS485.cpp +++ b/Software/src/inverter/KOSTAL-RS485.cpp @@ -9,6 +9,7 @@ // e.g. value set to 12, 12*5sec=60seconds without comm before event is raised static const uint8_t KOSTAL_FRAMEHEADER[5] = {0x62, 0xFF, 0x02, 0xFF, 0x29}; static const uint8_t KOSTAL_FRAMEHEADER2[5] = {0x63, 0xFF, 0x02, 0xFF, 0x29}; +static uint16_t nominal_voltage_dV = 0; static uint16_t discharge_current_dA = 0; static uint16_t charge_current_dA = 0; static int16_t average_temperature_dC = 0; @@ -18,8 +19,8 @@ static int8_t f2_startup_count = 0; static boolean B1_delay = false; static unsigned long B1_last_millis = 0; static unsigned long currentMillis; -static unsigned long startupMillis=0; -static unsigned long contactorMillis=0; +static unsigned long startupMillis = 0; +static unsigned long contactorMillis = 0; static boolean RX_allow = false; @@ -28,66 +29,64 @@ union f32b { byte b[4]; }; -uint8_t frame1[40] = { - 0x06, 0xE2, 0xFF, 0x02, 0xFF, 0x29, // Frame header - 0x01, 0x08, 0x80, 0x43, // 256.063 Nominal voltage / 5*51.2=256 first byte 0x01 or 0x04 - 0xE4, 0x70, 0x8A, 0x5C, // These might be Umin & Unax, Uint16 - 0xB5, 0x02, 0xD3, 0x01, // Battery Serial number? Modbus register 527 - 0x01, 0x05, 0xC8, 0x41, // 25.0024 ? - 0xC2, 0x18, // Battery Firmware, modbus register 586 - 0x01, 0x03, 0x59, 0x42, // 0x00005942 = 54.25 ?? - 0x01, 0x01, 0x01, 0x02, - 0x05, 0x02, 0xA0, 0x01, 0x01, 0x02, - 0x4D, // CRC - 0x00}; // - +uint8_t frame1[40] = {0x06, 0xE2, 0xFF, 0x02, 0xFF, 0x29, // Frame header + 0x01, 0x08, 0x80, 0x43, // 256.063 Nominal voltage / 5*51.2=256 first byte 0x01 or 0x04 + 0xE4, 0x70, 0x8A, 0x5C, // These might be Umin & Unax, Uint16 + 0xB5, 0x02, 0xD3, 0x01, // Battery Serial number? Modbus register 527 + 0x01, 0x05, 0xC8, 0x41, // 25.0024 ? + 0xC2, 0x18, // Battery Firmware, modbus register 586 + 0x01, 0x03, 0x59, 0x42, // 0x00005942 = 54.25 ?? + 0x01, 0x01, 0x01, 0x02, 0x05, 0x02, 0xA0, 0x01, 0x01, 0x02, + 0x4D, // CRC + 0x00}; // // values in frame2 will be overwritten at update_modbus_registers_inverter() -uint8_t frame2[64] = {0x0A, // This may also been 0x06, seen at startup when live values not valid, but also occasionally single frames. - 0xE2, 0xFF, 0x02, 0xFF, 0x29, // frame Header +uint8_t frame2[64] = { + 0x0A, // This may also been 0x06, seen at startup when live values not valid, but also occasionally single frames. + 0xE2, 0xFF, 0x02, 0xFF, 0x29, // frame Header - 0x1D, 0x5A, 0x85, 0x43, // Current Voltage (float) Modbus register 216, Bytes 6-9 - 0x01, 0x03, // Unknown, 0x03 seen also 0x0F, 0x07, might hava something to do with current - 0x8D, 0x43, // Max Voltage (2 byte float), Bytes 12-13 - 0x01, 0x03, - 0xAC, 0x41, // BAttery Temperature (2 byte float) Modbus register 214, Bytes 16-17 - 0x01, 0x01, 0x01, 0x01, // Peak Current (1s period?), Bytes 18-21 - Communication fault seen with some values (>10A?) - 0x01, 0x01, 0x01, 0x01, // Avg current (1s period?), Bytes 22-25 - Communication fault seen with some values (>10A?) + 0x1D, 0x5A, 0x85, 0x43, // Current Voltage (float) Modbus register 216, Bytes 6-9 + 0x01, 0x03, // Unknown, 0x03 seen also 0x0F, 0x07, might hava something to do with current + 0x8D, 0x43, // Max Voltage (2 byte float), Bytes 12-13 + 0x01, 0x03, 0xAC, 0x41, // BAttery Temperature (2 byte float) Modbus register 214, Bytes 16-17 + 0x01, 0x01, 0x01, + 0x01, // Peak Current (1s period?), Bytes 18-21 - Communication fault seen with some values (>10A?) + 0x01, 0x01, 0x01, + 0x01, // Avg current (1s period?), Bytes 22-25 - Communication fault seen with some values (>10A?) - 0x01, 0x03, 0x48, 0x42, // Max discharge current (2 byte float), Bit 26-29, - // Sunspec: ADisChaMax + 0x01, 0x03, 0x48, 0x42, // Max discharge current (2 byte float), Bit 26-29, + // Sunspec: ADisChaMax - 0x01, 0x03, // Unknown - 0xC8, 0x41, // Battery gross capacity, Ah (2 byte float) , Bytes 30-33, Modbus 512 + 0x01, 0x03, // Unknown + 0xC8, 0x41, // Battery gross capacity, Ah (2 byte float) , Bytes 30-33, Modbus 512 - 0x01, // Unknown - 0x16, // This seems to have something to do with cell temperatures + 0x01, // Unknown + 0x16, // This seems to have something to do with cell temperatures - 0xA0, 0x41, // Max charge current (2 byte float) Bit 36-37, ZERO WHEN SOC=100 - // Sunspec: AChaMax + 0xA0, 0x41, // Max charge current (2 byte float) Bit 36-37, ZERO WHEN SOC=100 + // Sunspec: AChaMax - 0xCD, 0xCC, 0xB4, 0x41, // MaxCellTemp (4 byte float) Bit 38-41 - 0x01, 0x0C, 0xA4, 0x41, // MinCellTemp (4 byte float) Bit 42-45 + 0xCD, 0xCC, 0xB4, 0x41, // MaxCellTemp (4 byte float) Bit 38-41 + 0x01, 0x0C, 0xA4, 0x41, // MinCellTemp (4 byte float) Bit 42-45 - 0xA4, 0x70, 0x55, 0x40, // MaxCellVolt (float), Bit 46-49 - 0x7D, 0x3F, 0x55, 0x40, // MinCellVolt (float), Bit 50-53 + 0xA4, 0x70, 0x55, 0x40, // MaxCellVolt (float), Bit 46-49 + 0x7D, 0x3F, 0x55, 0x40, // MinCellVolt (float), Bit 50-53 - 0xFE, // Cylce count , Bit 54 - 0x04, // Cycle count? , Bit 55 - 0x01, // Byte 56 - 0x40, // When SOC=100 Byte57=0x40, at startup 0x03 (about 7 times), otherwise 0x02 - 0x64, // SOC , Bit 58 - 0x01, // Unknown, when byte 57 = 0x03, this 0x02, otherwise 0x01 - 0x01, // Unknown, Seen only 0x01 - 0x02, // Unknown, Mostly 0x02. seen also 0x01 - 0x00, // CRC (inverted sum of bytes 1-62 + 0xC0), Bit 62 - 0x00}; + 0xFE, // Cylce count , Bit 54 + 0x04, // Cycle count? , Bit 55 + 0x01, // Byte 56 + 0x40, // When SOC=100 Byte57=0x40, at startup 0x03 (about 7 times), otherwise 0x02 + 0x64, // SOC , Bit 58 + 0x01, // Unknown, when byte 57 = 0x03, this 0x02, otherwise 0x01 + 0x01, // Unknown, Seen only 0x01 + 0x02, // Unknown, Mostly 0x02. seen also 0x01 + 0x00, // CRC (inverted sum of bytes 1-62 + 0xC0), Bit 62 + 0x00}; // FE 04 01 40 xx 01 01 02 yy (fully charged) // FE 02 01 02 xx 01 01 02 yy (charging or discharging) - uint8_t frame3[9] = { 0x08, 0xE2, 0xFF, 0x02, 0xFF, 0x29, //header 0x06, //Unknown @@ -95,18 +94,13 @@ uint8_t frame3[9] = { 0x00 //endbyte }; -uint8_t frame4[8] = { - 0x07, 0xE3, 0xFF, 0x02, 0xFF,0x29, - 0xF4, - 0x00 -}; +uint8_t frame4[8] = {0x07, 0xE3, 0xFF, 0x02, 0xFF, 0x29, 0xF4, 0x00}; uint8_t frameB1[10] = {0x07, 0x63, 0xFF, 0x02, 0xFF, 0x29, 0x5E, 0x02, 0x16, 0x00}; uint8_t frameB1b[8] = {0x07, 0xE3, 0xFF, 0x02, 0xFF, 0x29, 0xF4, 0x00}; uint8_t RS485_RXFRAME[10]; - bool register_content_ok = false; void float2frame(byte* arr, float value, byte framepointer) { @@ -202,53 +196,49 @@ void update_RS485_registers_inverter() { average_temperature_dC = 0; } - if (datalayer.system.status.contactor_control_closed ) - { - float2frame(frame2, (float)datalayer.battery.status.voltage_dV / 10, 6); // Confirmed OK mapping - frame2[0]=0x0A; - } - else - { - frame2[0]=0x06; - float2frame(frame2, 0.0, 6); - } - float2frameMSB(frame1, (float)datalayer.battery.info.nominal_dV / 10, 8); + if (datalayer.system.status.battery_allows_contactor_closing) { + float2frame(frame2, (float)datalayer.battery.status.voltage_dV / 10, 6); // Confirmed OK mapping + frame2[0] = 0x0A; + } else { + frame2[0] = 0x06; + float2frame(frame2, 0.0, 6); + } + // Set nominal voltage to value between min and max voltage set by battery (Example 400 and 300 results in 350V) + nominal_voltage_dV = + (((datalayer.battery.info.max_design_voltage_dV - datalayer.battery.info.min_design_voltage_dV) / 2) + + datalayer.battery.info.min_design_voltage_dV); + float2frameMSB(frame1, (float)nominal_voltage_dV / 10, 8); float2frameMSB(frame2, (float)datalayer.battery.info.max_design_voltage_dV / 10, 12); float2frameMSB(frame2, (float)average_temperature_dC / 10, 16); -// Some current values causes communication error, must be resolved, why. -// float2frameMSB(frame2, (float)datalayer.battery.status.current_dA / 10, 20); // Peak discharge? current (2 byte float) -// float2frameMSB(frame2, (float)datalayer.battery.status.current_dA / 10, 24); + // Some current values causes communication error, must be resolved, why. + // float2frameMSB(frame2, (float)datalayer.battery.status.current_dA / 10, 20); // Peak discharge? current (2 byte float) + // float2frameMSB(frame2, (float)datalayer.battery.status.current_dA / 10, 24); float2frameMSB(frame2, (float)discharge_current_dA / 10, 28); // BAttery capacity Ah float2frameMSB(frame2, (float)discharge_current_dA / 10, 32); - // When SOC = 100%, drop down allowed charge current down. - if((datalayer.battery.status.reported_soc / 100)<100) - { - float2frameMSB(frame2, (float)charge_current_dA / 10, 36); - frame2[57]=0x02; - frame2[59]=0x01; - } - else - { - float2frameMSB(frame2, 0.0 , 36); -// frame2[57]=0x40; - frame2[57]=0x02; - frame2[59]=0x01; - } + if ((datalayer.battery.status.reported_soc / 100) < 100) { + float2frameMSB(frame2, (float)charge_current_dA / 10, 36); + frame2[57] = 0x02; + frame2[59] = 0x01; + } else { + float2frameMSB(frame2, 0.0, 36); + //frame2[57]=0x40; + frame2[57] = 0x02; + frame2[59] = 0x01; + } // On startup, byte 57 seems to be always 0x03 couple of frames,. - if (f2_startup_count<14) - { - frame2[57]=0x03; - frame2[59]=0x02; - } + if (f2_startup_count < 14) { + frame2[57] = 0x03; + frame2[59] = 0x02; + } float2frame(frame2, (float)datalayer.battery.status.temperature_max_dC / 10, 38); float2frame(frame2, (float)datalayer.battery.status.temperature_min_dC / 10, 42); @@ -277,113 +267,101 @@ static uint8_t rx_index = 0; void receive_RS485() // Runs as fast as possible to handle the serial stream { - currentMillis = millis(); + if (datalayer.system.status.battery_allows_contactor_closing & !contactorMillis) { + contactorMillis = currentMillis; + } + if (currentMillis - contactorMillis >= INTERVAL_2_S & !RX_allow) { + RX_allow = true; + } - if(datalayer.system.status.contactor_control_closed & !contactorMillis) - { - contactorMillis=currentMillis; - } - if (currentMillis-contactorMillis >= 2000 & !RX_allow) - { - RX_allow=true; - } - - if(((currentMillis-startupMillis) >= 2000 & currentMillis-startupMillis <=7000 )& datalayer.system.status.inverter_allows_contactor_closing) - { + if (((currentMillis - startupMillis) >= INTERVAL_2_S & currentMillis - startupMillis <= 7000) & + datalayer.system.status.inverter_allows_contactor_closing) { // Disconnect allowed only, when curren zero - if (datalayer.battery.status.current_dA == 0) - { + if (datalayer.battery.status.current_dA == 0) { datalayer.system.status.inverter_allows_contactor_closing = false; - } - } - else if(((currentMillis-startupMillis) >= 7000) & datalayer.system.status.inverter_allows_contactor_closing == false) - { - datalayer.system.status.inverter_allows_contactor_closing = true; } + } else if (((currentMillis - startupMillis) >= 7000) & + datalayer.system.status.inverter_allows_contactor_closing == false) { + datalayer.system.status.inverter_allows_contactor_closing = true; + } - if(B1_delay) - { - if ((currentMillis - B1_last_millis) >1000) - { + if (B1_delay) { + if ((currentMillis - B1_last_millis) > INTERVAL_1_S) { send_kostal(frameB1b, 8); - B1_delay=false; - } + B1_delay = false; } - else if (Serial2.available()) { + } else if (Serial2.available()) { RS485_RXFRAME[rx_index] = Serial2.read(); - if (RX_allow) - { + if (RX_allow) { rx_index++; if (RS485_RXFRAME[rx_index - 1] == 0x00) { if ((rx_index == 10) && (RS485_RXFRAME[0] == 0x09) && register_content_ok) { #ifdef DEBUG_KOSTAL_RS485_DATA - Serial.print("RX: "); - for (uint8_t i = 0; i < 10; i++) { - Serial.print(RS485_RXFRAME[i], HEX); - Serial.print(" "); - } - Serial.println(""); + Serial.print("RX: "); + for (uint8_t i = 0; i < 10; i++) { + Serial.print(RS485_RXFRAME[i], HEX); + Serial.print(" "); + } + Serial.println(""); #endif - rx_index = 0; - if (check_kostal_frame_crc()) { - incoming_message_counter = RS485_HEALTHY; - bool headerA = true; - bool headerB = true; - for (uint8_t i = 0; i < 5; i++) { - if (RS485_RXFRAME[i + 1] != KOSTAL_FRAMEHEADER[i]) { - headerA = false; - } - if (RS485_RXFRAME[i + 1] != KOSTAL_FRAMEHEADER2[i]) { - headerB = false; + rx_index = 0; + if (check_kostal_frame_crc()) { + incoming_message_counter = RS485_HEALTHY; + bool headerA = true; + bool headerB = true; + for (uint8_t i = 0; i < 5; i++) { + if (RS485_RXFRAME[i + 1] != KOSTAL_FRAMEHEADER[i]) { + headerA = false; + } + if (RS485_RXFRAME[i + 1] != KOSTAL_FRAMEHEADER2[i]) { + headerB = false; + } } - } - // "frame B1", maybe reset request, seen after battery power on/partial data - if (headerB && (RS485_RXFRAME[6] == 0x5E) && (RS485_RXFRAME[7] == 0xFF)) { - send_kostal(frameB1, 10); - B1_delay=true; - B1_last_millis=currentMillis; - } + // "frame B1", maybe reset request, seen after battery power on/partial data + if (headerB && (RS485_RXFRAME[6] == 0x5E) && (RS485_RXFRAME[7] == 0xFF)) { + send_kostal(frameB1, 10); + B1_delay = true; + B1_last_millis = currentMillis; + } - // "frame B1", maybe reset request, seen after battery power on/partial data - if (headerB && (RS485_RXFRAME[6] == 0x5E) && (RS485_RXFRAME[7] == 0x04)) { - send_kostal(frame4, 8); - // This needs more reverse engineering, disabled... - } + // "frame B1", maybe reset request, seen after battery power on/partial data + if (headerB && (RS485_RXFRAME[6] == 0x5E) && (RS485_RXFRAME[7] == 0x04)) { + send_kostal(frame4, 8); + // This needs more reverse engineering, disabled... + } - if (headerA && (RS485_RXFRAME[6] == 0x4A) && (RS485_RXFRAME[7] == 0x08)) { // "frame 1" - send_kostal(frame1, 40); - if (!startupMillis) - { - startupMillis=currentMillis; + if (headerA && (RS485_RXFRAME[6] == 0x4A) && (RS485_RXFRAME[7] == 0x08)) { // "frame 1" + send_kostal(frame1, 40); + if (!startupMillis) { + startupMillis = currentMillis; } - } - if (headerA && (RS485_RXFRAME[6] == 0x4A) && (RS485_RXFRAME[7] == 0x04)) { // "frame 2" - update_values_battery(); - update_RS485_registers_inverter(); - if (f2_startup_count<15) - { - f2_startup_count++; + } + if (headerA && (RS485_RXFRAME[6] == 0x4A) && (RS485_RXFRAME[7] == 0x04)) { // "frame 2" + update_values_battery(); + update_RS485_registers_inverter(); + if (f2_startup_count < 15) { + f2_startup_count++; } - byte tmpframe[64]; //copy values to prevent data manipulation during rewrite/crc calculation - memcpy(tmpframe, frame2, 64); - for (int i = 1; i < 63; i++) { - if (tmpframe[i] == 0x00) { - tmpframe[i] = 0x01; + byte tmpframe[64]; //copy values to prevent data manipulation during rewrite/crc calculation + memcpy(tmpframe, frame2, 64); + for (int i = 1; i < 63; i++) { + if (tmpframe[i] == 0x00) { + tmpframe[i] = 0x01; + } } + tmpframe[62] = calculate_longframe_crc(tmpframe, 62); + send_kostal(tmpframe, 64); + } + if (headerA && (RS485_RXFRAME[6] == 0x53) && (RS485_RXFRAME[7] == 0x03)) { // "frame 3" + send_kostal(frame3, 9); } - tmpframe[62] = calculate_longframe_crc(tmpframe, 62); - send_kostal(tmpframe, 64); - } - if (headerA && (RS485_RXFRAME[6] == 0x53) && (RS485_RXFRAME[7] == 0x03)) { // "frame 3" - send_kostal(frame3, 9); } } + rx_index = 0; } - rx_index = 0; - } } if (rx_index >= 10) { rx_index = 0; From bf78110e858294d5fdd2852758e5d630d656b079 Mon Sep 17 00:00:00 2001 From: rha Date: Sat, 19 Oct 2024 18:28:18 +0300 Subject: [PATCH 045/210] allow contactor closing for testing --- Software/src/battery/TEST-FAKE-BATTERY.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Software/src/battery/TEST-FAKE-BATTERY.cpp b/Software/src/battery/TEST-FAKE-BATTERY.cpp index 82249ad2..f4aaf9bd 100644 --- a/Software/src/battery/TEST-FAKE-BATTERY.cpp +++ b/Software/src/battery/TEST-FAKE-BATTERY.cpp @@ -22,6 +22,8 @@ void print_units(char* header, int value, char* units) { void update_values_battery() { /* This function puts fake values onto the parameters sent towards the inverter */ + datalayer.system.status.battery_allows_contactor_closing = true; + datalayer.battery.status.real_soc = 5000; // 50.00% datalayer.battery.status.soh_pptt = 9900; // 99.00% From 20e16d0b3484bd9e5bef4879efb82be7750d00f5 Mon Sep 17 00:00:00 2001 From: rha Date: Sat, 19 Oct 2024 20:38:37 +0300 Subject: [PATCH 046/210] added relay control frame trigger --- Software/src/inverter/KOSTAL-RS485.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/Software/src/inverter/KOSTAL-RS485.cpp b/Software/src/inverter/KOSTAL-RS485.cpp index 388de6ca..9dc59c55 100644 --- a/Software/src/inverter/KOSTAL-RS485.cpp +++ b/Software/src/inverter/KOSTAL-RS485.cpp @@ -196,7 +196,7 @@ void update_RS485_registers_inverter() { average_temperature_dC = 0; } - if (datalayer.system.status.battery_allows_contactor_closing) { + if (datalayer.system.status.battery_allows_contactor_closing & datalayer.system.status.inverter_allows_contactor_closing ) { float2frame(frame2, (float)datalayer.battery.status.voltage_dV / 10, 6); // Confirmed OK mapping frame2[0] = 0x0A; } else { @@ -276,15 +276,17 @@ void receive_RS485() // Runs as fast as possible to handle the serial stream RX_allow = true; } - if (((currentMillis - startupMillis) >= INTERVAL_2_S & currentMillis - startupMillis <= 7000) & + if (startupMillis) { + if (((currentMillis - startupMillis) >= INTERVAL_2_S & currentMillis - startupMillis <= 7000) & datalayer.system.status.inverter_allows_contactor_closing) { - // Disconnect allowed only, when curren zero - if (datalayer.battery.status.current_dA == 0) { - datalayer.system.status.inverter_allows_contactor_closing = false; + // Disconnect allowed only, when curren zero + if (datalayer.battery.status.current_dA == 0) { + datalayer.system.status.inverter_allows_contactor_closing = false; + } + } else if (((currentMillis - startupMillis) >= 7000) & + datalayer.system.status.inverter_allows_contactor_closing == false) { + datalayer.system.status.inverter_allows_contactor_closing = true; } - } else if (((currentMillis - startupMillis) >= 7000) & - datalayer.system.status.inverter_allows_contactor_closing == false) { - datalayer.system.status.inverter_allows_contactor_closing = true; } if (B1_delay) { From b5eecc82ba43f79960e170cf2016dfb3ceba71c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Sun, 20 Oct 2024 14:40:12 +0300 Subject: [PATCH 047/210] Downgrade soc check to warning --- Software/src/devboard/utils/events.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Software/src/devboard/utils/events.cpp b/Software/src/devboard/utils/events.cpp index ce78042a..58dc61f1 100644 --- a/Software/src/devboard/utils/events.cpp +++ b/Software/src/devboard/utils/events.cpp @@ -154,7 +154,7 @@ void init_events(void) { events.entries[EVENT_CHARGE_LIMIT_EXCEEDED].level = EVENT_LEVEL_INFO; events.entries[EVENT_DISCHARGE_LIMIT_EXCEEDED].level = EVENT_LEVEL_INFO; events.entries[EVENT_12V_LOW].level = EVENT_LEVEL_WARNING; - events.entries[EVENT_SOC_PLAUSIBILITY_ERROR].level = EVENT_LEVEL_ERROR; + 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_BATTERY_EMPTY].level = EVENT_LEVEL_INFO; @@ -289,7 +289,7 @@ const char* get_event_message_string(EVENTS_ENUM_TYPE event) { case EVENT_12V_LOW: return "12V battery source below required voltage to safely close contactors. Inspect the supply/battery!"; case EVENT_SOC_PLAUSIBILITY_ERROR: - return "ERROR: SOC reported by battery not plausible. Restart battery!"; + return "Warning: SOC reported by battery not plausible. Restart battery!"; case EVENT_SOC_UNAVAILABLE: return "Warning: SOC not sent by BMS. Calibrate BMS via app."; case EVENT_KWH_PLAUSIBILITY_ERROR: From 846af0342c880df05fd41dcb7a2354a740216ae1 Mon Sep 17 00:00:00 2001 From: "chasovskiy.vitalii" Date: Mon, 21 Oct 2024 20:49:10 +0300 Subject: [PATCH 048/210] Tesla SX batteries support --- Software/src/battery/TESLA-BATTERY.cpp | 65 ++++++++++++++++++++++++++ Software/src/battery/TESLA-BATTERY.h | 2 + 2 files changed, 67 insertions(+) diff --git a/Software/src/battery/TESLA-BATTERY.cpp b/Software/src/battery/TESLA-BATTERY.cpp index 48b13516..a35751d9 100644 --- a/Software/src/battery/TESLA-BATTERY.cpp +++ b/Software/src/battery/TESLA-BATTERY.cpp @@ -969,6 +969,42 @@ void update_values_battery2() { //This function maps all the values fetched via #endif //DOUBLE_BATTERY +#if defined(TESLA_MODEL_SX_BATTERY) || defined(EXP_TESLA_BMS_DIGITAL_HVIL) +CAN_frame can_msg_1CF[] = { + {.FD = false, .ext_ID = false, .DLC = 8, .ID = 0x1CF, .data = {0x01, 0x00, 0x00, 0x1A, 0x1C, 0x02, 0x60, 0x69}}, + {.FD = false, .ext_ID = false, .DLC = 8, .ID = 0x1CF, .data = {0x01, 0x00, 0x00, 0x1A, 0x1C, 0x02, 0x80, 0x89}}, + {.FD = false, .ext_ID = false, .DLC = 8, .ID = 0x1CF, .data = {0x01, 0x00, 0x00, 0x1A, 0x1C, 0x02, 0xA0, 0xA9}}, + {.FD = false, .ext_ID = false, .DLC = 8, .ID = 0x1CF, .data = {0x01, 0x00, 0x00, 0x1A, 0x1C, 0x02, 0xC0, 0xC9}}, + {.FD = false, .ext_ID = false, .DLC = 8, .ID = 0x1CF, .data = {0x01, 0x00, 0x00, 0x1A, 0x1C, 0x02, 0xE0, 0xE9}}, + {.FD = false, .ext_ID = false, .DLC = 8, .ID = 0x1CF, .data = {0x01, 0x00, 0x00, 0x1A, 0x1C, 0x02, 0x00, 0x09}}, + {.FD = false, .ext_ID = false, .DLC = 8, .ID = 0x1CF, .data = {0x01, 0x00, 0x00, 0x1A, 0x1C, 0x02, 0x20, 0x29}}, + {.FD = false, .ext_ID = false, .DLC = 8, .ID = 0x1CF, .data = {0x01, 0x00, 0x00, 0x1A, 0x1C, 0x02, 0x40, 0x49}}}; + +CAN_frame can_msg_118[] = { + {.FD = false, .ext_ID = false, .DLC = 8, .ID = 0x118, .data = {0x61, 0x80, 0x30, 0x10, 0x00, 0x08, 0x00, 0x80}}, + {.FD = false, .ext_ID = false, .DLC = 8, .ID = 0x118, .data = {0x62, 0x81, 0x30, 0x10, 0x00, 0x08, 0x00, 0x80}}, + {.FD = false, .ext_ID = false, .DLC = 8, .ID = 0x118, .data = {0x63, 0x82, 0x30, 0x10, 0x00, 0x08, 0x00, 0x80}}, + {.FD = false, .ext_ID = false, .DLC = 8, .ID = 0x118, .data = {0x64, 0x83, 0x30, 0x10, 0x00, 0x08, 0x00, 0x80}}, + {.FD = false, .ext_ID = false, .DLC = 8, .ID = 0x118, .data = {0x65, 0x84, 0x30, 0x10, 0x00, 0x08, 0x00, 0x80}}, + {.FD = false, .ext_ID = false, .DLC = 8, .ID = 0x118, .data = {0x66, 0x85, 0x30, 0x10, 0x00, 0x08, 0x00, 0x80}}, + {.FD = false, .ext_ID = false, .DLC = 8, .ID = 0x118, .data = {0x67, 0x86, 0x30, 0x10, 0x00, 0x08, 0x00, 0x80}}, + {.FD = false, .ext_ID = false, .DLC = 8, .ID = 0x118, .data = {0x68, 0x87, 0x30, 0x10, 0x00, 0x08, 0x00, 0x80}}, + {.FD = false, .ext_ID = false, .DLC = 8, .ID = 0x118, .data = {0x69, 0x88, 0x30, 0x10, 0x00, 0x08, 0x00, 0x80}}, + {.FD = false, .ext_ID = false, .DLC = 8, .ID = 0x118, .data = {0x6A, 0x89, 0x30, 0x10, 0x00, 0x08, 0x00, 0x80}}, + {.FD = false, .ext_ID = false, .DLC = 8, .ID = 0x118, .data = {0x6B, 0x8A, 0x30, 0x10, 0x00, 0x08, 0x00, 0x80}}, + {.FD = false, .ext_ID = false, .DLC = 8, .ID = 0x118, .data = {0x6C, 0x8B, 0x30, 0x10, 0x00, 0x08, 0x00, 0x80}}, + {.FD = false, .ext_ID = false, .DLC = 8, .ID = 0x118, .data = {0x6D, 0x8C, 0x30, 0x10, 0x00, 0x08, 0x00, 0x80}}, + {.FD = false, .ext_ID = false, .DLC = 8, .ID = 0x118, .data = {0x6E, 0x8D, 0x30, 0x10, 0x00, 0x08, 0x00, 0x80}}, + {.FD = false, .ext_ID = false, .DLC = 8, .ID = 0x118, .data = {0x6F, 0x8E, 0x30, 0x10, 0x00, 0x08, 0x00, 0x80}}, + {.FD = false, .ext_ID = false, .DLC = 8, .ID = 0x118, .data = {0x70, 0x8F, 0x30, 0x10, 0x00, 0x08, 0x00, 0x80}}}; + +unsigned long lastSend1CF = 0; +unsigned long lastSend118 = 0; + +int index_1CF = 0; +int index_118 = 0; +#endif + void send_can_battery() { /*From bielec: My fist 221 message, to close the contactors is 0x41, 0x11, 0x01, 0x00, 0x00, 0x00, 0x20, 0x96 and then, to cause "hv_up_for_drive" I send an additional 221 message 0x61, 0x15, 0x01, 0x00, 0x00, 0x00, 0x20, 0xBA so @@ -976,6 +1012,35 @@ two 221 messages are being continuously transmitted. When I want to shut down, the first, for a few cycles, then stop all messages which causes the contactor to open. */ unsigned long currentMillis = millis(); + +#if defined(TESLA_MODEL_SX_BATTERY) || defined(EXP_TESLA_BMS_DIGITAL_HVIL) + if (datalayer.system.status.inverter_allows_contactor_closing) { + if (currentMillis - lastSend1CF >= 10) { + transmit_can(&can_msg_1CF[index_1CF], can_config.battery); + +#ifdef DOUBLE_BATTERY + transmit_can(&can_msg_1CF[index_1CF], can_config.battery_double); +#endif + + index_1CF = (index_1CF + 1) % 8; + lastSend1CF = currentMillis; + } + + if (currentMillis - lastSend118 >= 10) { + transmit_can(&can_msg_118[index_118], can_config.battery); +#ifdef DOUBLE_BATTERY + transmit_can(&can_msg_1CF[index_1CF], can_config.battery_double); +#endif + + index_118 = (index_118 + 1) % 16; + lastSend118 = currentMillis; + } + } else { + index_1CF = 0; + index_118 = 0; + } +#endif + //Send 30ms message if (currentMillis - previousMillis30 >= INTERVAL_30_MS) { // Check if sending of CAN messages has been delayed too much. diff --git a/Software/src/battery/TESLA-BATTERY.h b/Software/src/battery/TESLA-BATTERY.h index 22806132..a45da3a9 100644 --- a/Software/src/battery/TESLA-BATTERY.h +++ b/Software/src/battery/TESLA-BATTERY.h @@ -28,6 +28,8 @@ #define MAX_CELL_VOLTAGE_LFP 3550 //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 mode. Enables the sending of additional CAN messages, required for new firmwares + void printFaultCodesIfActive(); void printDebugIfActive(uint8_t symbol, const char* message); void print_int_with_units(char* header, int value, char* units); From 18f24bb25e860d546ddfa54b8f227e8b17dd457c Mon Sep 17 00:00:00 2001 From: chasovskiy_v Date: Tue, 22 Oct 2024 08:31:48 +0300 Subject: [PATCH 049/210] Tesla Batteries: Adding comment to experimental parameter for better understanding --- Software/src/battery/TESLA-BATTERY.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Software/src/battery/TESLA-BATTERY.h b/Software/src/battery/TESLA-BATTERY.h index a45da3a9..f3385617 100644 --- a/Software/src/battery/TESLA-BATTERY.h +++ b/Software/src/battery/TESLA-BATTERY.h @@ -28,7 +28,10 @@ #define MAX_CELL_VOLTAGE_LFP 3550 //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 mode. Enables the sending of additional CAN messages, required for new firmwares +// Forces the transmission of additional CAN frames for experimental purposes. +// Can be used with any Tesla battery configuration, including Model 3/Y, to test potential firmware-related issues. +// Needed to validate the hypothesis that newer BMS firmware versions for Model 3/Y, which will include a 16V battery, may also require these CAN frames. +//#define EXP_TESLA_BMS_DIGITAL_HVIL void printFaultCodesIfActive(); void printDebugIfActive(uint8_t symbol, const char* message); From 816e50345e064e6c5b6f1c95f9c06d54211837c3 Mon Sep 17 00:00:00 2001 From: chasovskiy_v Date: Tue, 22 Oct 2024 08:35:52 +0300 Subject: [PATCH 050/210] Tesla batteries: fix comment again --- Software/src/battery/TESLA-BATTERY.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Software/src/battery/TESLA-BATTERY.h b/Software/src/battery/TESLA-BATTERY.h index f3385617..5a94bd5b 100644 --- a/Software/src/battery/TESLA-BATTERY.h +++ b/Software/src/battery/TESLA-BATTERY.h @@ -28,9 +28,9 @@ #define MAX_CELL_VOLTAGE_LFP 3550 //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 -// Forces the transmission of additional CAN frames for experimental purposes. +// Experimental parameter. Forces the transmission of additional CAN frames for experimental purposes. // Can be used with any Tesla battery configuration, including Model 3/Y, to test potential firmware-related issues. -// Needed to validate the hypothesis that newer BMS firmware versions for Model 3/Y, which will include a 16V battery, may also require these CAN frames. +// Needed to validate the hypothesis that newer BMS firmware versions for Model 3/Y, which work with a 16V li-ion battery, may also require these CAN frames. //#define EXP_TESLA_BMS_DIGITAL_HVIL void printFaultCodesIfActive(); From f446f29cac4bd9b19422a54aebc4eb580c354be0 Mon Sep 17 00:00:00 2001 From: vitalii-ch <95506875+vitalii-ch@users.noreply.github.com> Date: Tue, 22 Oct 2024 16:26:51 +0300 Subject: [PATCH 051/210] Update TESLA-BATTERY.h Tesla Batteries: change explanation for experimental parameter --- Software/src/battery/TESLA-BATTERY.h | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Software/src/battery/TESLA-BATTERY.h b/Software/src/battery/TESLA-BATTERY.h index 5a94bd5b..4f6c1689 100644 --- a/Software/src/battery/TESLA-BATTERY.h +++ b/Software/src/battery/TESLA-BATTERY.h @@ -28,10 +28,7 @@ #define MAX_CELL_VOLTAGE_LFP 3550 //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 -// Experimental parameter. Forces the transmission of additional CAN frames for experimental purposes. -// Can be used with any Tesla battery configuration, including Model 3/Y, to test potential firmware-related issues. -// Needed to validate the hypothesis that newer BMS firmware versions for Model 3/Y, which work with a 16V li-ion battery, may also require these CAN frames. -//#define EXP_TESLA_BMS_DIGITAL_HVIL +//#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. void printFaultCodesIfActive(); void printDebugIfActive(uint8_t symbol, const char* message); From 6667d9d1aa4da07abcb95e84f6fa1b48cc787324 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Tue, 22 Oct 2024 22:23:00 +0300 Subject: [PATCH 052/210] Add more info for BMW i3 --- Software/src/battery/BMW-I3-BATTERY.cpp | 46 ++++------ Software/src/datalayer/datalayer_extended.h | 43 +++++++++ .../webserver/advanced_battery_html.cpp | 87 ++++++++++++++++++- 3 files changed, 147 insertions(+), 29 deletions(-) diff --git a/Software/src/battery/BMW-I3-BATTERY.cpp b/Software/src/battery/BMW-I3-BATTERY.cpp index 1fc75264..2b1f68fa 100644 --- a/Software/src/battery/BMW-I3-BATTERY.cpp +++ b/Software/src/battery/BMW-I3-BATTERY.cpp @@ -1,6 +1,7 @@ #include "../include.h" #ifdef BMW_I3_BATTERY #include "../datalayer/datalayer.h" +#include "../datalayer/datalayer_extended.h" #include "../devboard/utils/events.h" #include "BMW-I3-BATTERY.h" @@ -489,33 +490,20 @@ void update_values_battery() { //This function maps all the values fetched via clear_event(EVENT_PRECHARGE_FAILURE); } -#ifdef DEBUG_VIA_USB - Serial.println(" "); - Serial.print("Battery display SOC%: "); - Serial.print(battery_display_SOC * 50); - Serial.print("Battery display SOC%: "); - Serial.print(battery_HVBatt_SOC * 10); - Serial.print("Battery polled SOC%: "); - Serial.print(battery_soc); - Serial.print(" Battery voltage: "); - Serial.print(datalayer.battery.status.voltage_dV * 0.1); - Serial.print(" Battery current: "); - Serial.print(datalayer.battery.status.current_dA * 0.1); - Serial.print(" Wh when full: "); - Serial.print(datalayer.battery.info.total_capacity_Wh); - Serial.print(" Remaining Wh: "); - Serial.print(datalayer.battery.status.remaining_capacity_Wh); - Serial.print(" Max charge power: "); - Serial.print(datalayer.battery.status.max_charge_power_W); - Serial.print(" Max discharge power: "); - Serial.print(datalayer.battery.status.max_discharge_power_W); - Serial.print(" Active power: "); - Serial.print(datalayer.battery.status.active_power_W); - Serial.print(" Min temp: "); - Serial.print(datalayer.battery.status.temperature_min_dC * 0.1); - Serial.print(" Max temp: "); - Serial.print(datalayer.battery.status.temperature_max_dC * 0.1); -#endif + // Update webserver datalayer + datalayer_extended.bmwi3.SOC_raw = (battery_display_SOC * 50); + datalayer_extended.bmwi3.SOC_dash = (battery_HVBatt_SOC * 10); + datalayer_extended.bmwi3.SOC_OBD2 = battery_soc; + datalayer_extended.bmwi3.ST_iso_ext = battery_status_error_isolation_external_Bordnetz; + datalayer_extended.bmwi3.ST_iso_int = battery_status_error_isolation_internal_Bordnetz; + datalayer_extended.bmwi3.ST_valve_cooling = battery_status_valve_cooling; + datalayer_extended.bmwi3.ST_interlock = battery_status_error_locking; + datalayer_extended.bmwi3.ST_precharge = battery_status_precharge_locked; + datalayer_extended.bmwi3.ST_DCSW = battery_status_disconnecting_switch; + datalayer_extended.bmwi3.ST_EMG = battery_status_emergency_mode; + datalayer_extended.bmwi3.ST_WELD = battery_status_error_disconnecting_switch; + datalayer_extended.bmwi3.ST_isolation = battery_status_warning_isolation; + datalayer_extended.bmwi3.ST_cold_shutoff_valve = battery_status_cold_shutoff_valve; } void receive_can_battery(CAN_frame rx_frame) { @@ -1136,7 +1124,7 @@ void send_can_battery() { void setup_battery(void) { // Performs one time setup at startup #ifdef DEBUG_VIA_USB Serial.println("BMW i3 battery selected"); -#endif +#endif //DEBUG_VIA_USB //Before we have started up and detected which battery is in use, use 60AH values datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_60AH; @@ -1145,7 +1133,9 @@ void setup_battery(void) { // Performs one time setup at startup datalayer.system.status.battery_allows_contactor_closing = true; #ifdef DOUBLE_BATTERY +#ifdef DEBUG_VIA_USB Serial.println("Another BMW i3 battery also selected!"); +#endif //DEBUG_VIA_USB datalayer.battery2.info.max_design_voltage_dV = datalayer.battery.info.max_design_voltage_dV; datalayer.battery2.info.min_design_voltage_dV = datalayer.battery.info.min_design_voltage_dV; datalayer.battery2.info.max_cell_voltage_deviation_mV = datalayer.battery.info.max_cell_voltage_deviation_mV; diff --git a/Software/src/datalayer/datalayer_extended.h b/Software/src/datalayer/datalayer_extended.h index b893f5c7..96bd5e07 100644 --- a/Software/src/datalayer/datalayer_extended.h +++ b/Software/src/datalayer/datalayer_extended.h @@ -3,6 +3,48 @@ #include "../include.h" +typedef struct { + /** uint16_t */ + /** SOC% raw battery value. Might not always reach 100% */ + uint16_t SOC_raw = 0; + /** uint16_t */ + /** SOC% instrumentation cluster value. Will always reach 100% */ + uint16_t SOC_dash = 0; + /** uint16_t */ + /** SOC% OBD2 value, polled actively */ + uint16_t SOC_OBD2 = 0; + /** uint8_t */ + /** Status isolation external, 0 not evaluated, 1 OK, 2 error active, 3 Invalid signal*/ + uint8_t ST_iso_ext = 0; + /** uint8_t */ + /** Status isolation external, 0 not evaluated, 1 OK, 2 error active, 3 Invalid signal*/ + uint8_t ST_iso_int = 0; + /** uint8_t */ + /** Status cooling valve error, 0 not evaluated, 1 OK valve closed, 2 error active valve open, 3 Invalid signal*/ + uint8_t ST_valve_cooling = 0; + /** uint8_t */ + /** Status interlock error, 0 not evaluated, 1 OK, 2 error active, 3 Invalid signal*/ + uint8_t ST_interlock = 0; + /** uint8_t */ + /** Status precharge, 0 no statement, 1 Not active closing not blocked, 2 error precharge blocked, 3 Invalid signal*/ + uint8_t ST_precharge = 0; + /** uint8_t */ + /** Status DC switch, 0 contactors open, 1 precharge ongoing, 2 contactors engaged, 3 Invalid signal*/ + uint8_t ST_DCSW = 0; + /** uint8_t */ + /** Status emergency, 0 not evaluated, 1 OK, 2 error active, 3 Invalid signal*/ + uint8_t ST_EMG = 0; + /** uint8_t */ + /** Status welding detection, 0 Contactors OK, 1 One contactor welded, 2 Two contactors welded, 3 Invalid signal*/ + uint8_t ST_WELD = 0; + /** uint8_t */ + /** Status isolation, 0 not evaluated, 1 OK, 2 error active, 3 Invalid signal*/ + uint8_t ST_isolation = 0; + /** uint8_t */ + /** Status cold shutoff valve, 0 OK, 1 Short circuit to GND, 2 Short circuit to 12V, 3 Line break, 6 Driver error, 12 Stuck, 13 Stuck, 15 Invalid Signal*/ + uint8_t ST_cold_shutoff_valve = 0; +} DATALAYER_INFO_BMWI3; + typedef struct { /** uint8_t */ /** Contactor status */ @@ -76,6 +118,7 @@ typedef struct { class DataLayerExtended { public: + DATALAYER_INFO_BMWI3 bmwi3; DATALAYER_INFO_TESLA tesla; DATALAYER_INFO_NISSAN_LEAF nissanleaf; }; diff --git a/Software/src/devboard/webserver/advanced_battery_html.cpp b/Software/src/devboard/webserver/advanced_battery_html.cpp index 80a69cb4..e8001d0c 100644 --- a/Software/src/devboard/webserver/advanced_battery_html.cpp +++ b/Software/src/devboard/webserver/advanced_battery_html.cpp @@ -16,6 +16,90 @@ String advanced_battery_processor(const String& var) { // Start a new block with a specific background color content += "
"; +#ifdef BMW_I3_BATTERY + content += "

SOC raw: " + String(datalayer_extended.bmwi3.SOC_raw) + "

"; + content += "

SOC dash: " + String(datalayer_extended.bmwi3.SOC_dash) + "

"; + content += "

SOC OBD2: " + String(datalayer_extended.bmwi3.SOC_OBD2) + "

"; + static const char* statusText[16] = { + "Not evaluated", "OK", "Error!", "Invalid signal", "", "", "", "", "", "", "", "", "", "", "", ""}; + content += "

Interlock: " + String(statusText[datalayer_extended.bmwi3.ST_interlock]) + "

"; + content += "

Isolation external: " + String(statusText[datalayer_extended.bmwi3.ST_iso_ext]) + "

"; + content += "

Isolation internal: " + String(statusText[datalayer_extended.bmwi3.ST_iso_int]) + "

"; + content += "

Isolation: " + String(statusText[datalayer_extended.bmwi3.ST_isolation]) + "

"; + content += "

Cooling valve: " + String(statusText[datalayer_extended.bmwi3.ST_valve_cooling]) + "

"; + content += "

Emergency: " + String(statusText[datalayer_extended.bmwi3.ST_EMG]) + "

"; + static const char* prechargeText[16] = {"Not evaluated", + "Not active, closing not blocked", + "Error precharge blocked", + "Invalid signal", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + ""}; + content += "

Precharge: " + String(prechargeText[datalayer_extended.bmwi3.ST_precharge]) + + "

"; //Still unclear of enum + static const char* DCSWText[16] = {"Contactors open", + "Precharge ongoing", + "Contactors engaged", + "Invalid signal", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + ""}; + content += "

Contactor status: " + String(DCSWText[datalayer_extended.bmwi3.ST_DCSW]) + "

"; + static const char* contText[16] = {"Contactors OK", + "One contactor welded!", + "Two contactors welded!", + "Invalid signal", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + ""}; + content += "

Contactor weld: " + String(contText[datalayer_extended.bmwi3.ST_WELD]) + "

"; + static const char* valveText[16] = {"OK", + "Short circuit to GND", + "Short circuit to 12V", + "Line break", + "", + "", + "Driver error", + "", + "", + "", + "", + "", + "Stuck", + "Stuck", + "", + "Invalid Signal"}; + content += "

Cold shutoff valve: " + String(contText[datalayer_extended.bmwi3.ST_cold_shutoff_valve]) + "

"; + +#endif //BMW_I3_BATTERY + #ifdef TESLA_BATTERY static const char* contactorText[] = {"UNKNOWN(0)", "OPEN", "CLOSING", "BLOCKED", "OPENING", "CLOSED", "UNKNOWN(6)", "WELDED", "POS_CL", "NEG_CL", @@ -67,7 +151,8 @@ String advanced_battery_processor(const String& var) { content += "

Heating requested: " + String(datalayer_extended.nissanleaf.HeaterSendRequest) + "

"; #endif -#if !defined(TESLA_BATTERY) && !defined(NISSAN_LEAF_BATTERY) //Only the listed types have extra info +#if !defined(TESLA_BATTERY) && !defined(NISSAN_LEAF_BATTERY) && \ + !defined(BMW_I3_BATTERY) //Only the listed types have extra info content += "No extra information available for this battery type"; #endif From ced16495a06ba0008ca0ddbabfcca9328ed87355 Mon Sep 17 00:00:00 2001 From: amarofarinha <151563493+amarofarinha@users.noreply.github.com> Date: Wed, 23 Oct 2024 01:28:00 +0100 Subject: [PATCH 053/210] Add scaled remaining capacity calculation --- Software/Software.ino | 21 +++++++++++++++++-- Software/src/datalayer/datalayer.h | 6 ++++++ Software/src/devboard/mqtt/mqtt.cpp | 7 +++++-- Software/src/devboard/webserver/webserver.cpp | 4 +++- Software/src/inverter/BYD-MODBUS.cpp | 5 +++-- Software/src/inverter/BYD-SMA.cpp | 5 +++-- Software/src/inverter/FOXESS-CAN.cpp | 4 ++-- Software/src/inverter/SMA-CAN.cpp | 5 +++-- Software/src/inverter/SMA-TRIPOWER-CAN.cpp | 5 +++-- Software/src/inverter/SOLAX-CAN.cpp | 4 ++-- 10 files changed, 49 insertions(+), 17 deletions(-) diff --git a/Software/Software.ino b/Software/Software.ino index a6c664a3..d6670f77 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -291,7 +291,7 @@ void core_loop(void* task_time_us) { #ifdef DOUBLE_BATTERY update_values_battery2(); #endif - update_SOC(); // Check if real or calculated SOC% value should be sent + update_scaled_values(); // Check if real or calculated SOC% value should be sent #ifndef SERIAL_LINK_RECEIVER update_machineryprotection(); // Check safeties (Not on serial link reciever board) #endif @@ -834,7 +834,7 @@ void handle_contactors() { #endif // CONTACTOR_CONTROL } -void update_SOC() { +void update_scaled_values() { if (datalayer.battery.settings.soc_scaling_active) { /** SOC Scaling * @@ -864,23 +864,40 @@ void update_SOC() { calc_soc = 10000 * (calc_soc - datalayer.battery.settings.min_percentage); calc_soc = calc_soc / (datalayer.battery.settings.max_percentage - datalayer.battery.settings.min_percentage); datalayer.battery.status.reported_soc = calc_soc; + + // Calculate the scaled remaining capacity in Wh + if (datalayer.battery.info.total_capacity_Wh > 0) { + uint32_t calc_capacity; + // remove % capacity not used in min_percentage to total_capacity_Wh + calc_capacity = datalayer.battery.settings.min_percentage * datalayer.battery.info.total_capacity_Wh / 10000; + calc_capacity = datalayer.battery.status.remaining_capacity_Wh - calc_capacity; + datalayer.battery.status.reported_remaining_capacity_Wh = calc_capacity; + } else { + datalayer.battery.status.reported_remaining_capacity_Wh = datalayer.battery.status.remaining_capacity_Wh; + } + } else { // No SOC window wanted. Set scaled to same as real. datalayer.battery.status.reported_soc = datalayer.battery.status.real_soc; + datalayer.battery.status.reported_remaining_capacity_Wh = datalayer.battery.status.remaining_capacity_Wh; } #ifdef DOUBLE_BATTERY // Perform extra SOC sanity checks on double battery setups if (datalayer.battery.status.real_soc < 100) { //If this battery is under 1.00%, use this as SOC instead of average datalayer.battery.status.reported_soc = datalayer.battery.status.real_soc; + datalayer.battery.status.reported_remaining_capacity_Wh = datalayer.battery.status.remaining_capacity_Wh; } if (datalayer.battery2.status.real_soc < 100) { //If this battery is under 1.00%, use this as SOC instead of average datalayer.battery.status.reported_soc = datalayer.battery2.status.real_soc; + datalayer.battery.status.reported_remaining_capacity_Wh = datalayer.battery2.status.remaining_capacity_Wh; } if (datalayer.battery.status.real_soc > 9900) { //If this battery is over 99.00%, use this as SOC instead of average datalayer.battery.status.reported_soc = datalayer.battery.status.real_soc; + datalayer.battery.status.reported_remaining_capacity_Wh = datalayer.battery.status.remaining_capacity_Wh; } if (datalayer.battery2.status.real_soc > 9900) { //If this battery is over 99.00%, use this as SOC instead of average datalayer.battery.status.reported_soc = datalayer.battery2.status.real_soc; + datalayer.battery.status.reported_remaining_capacity_Wh = datalayer.battery2.status.remaining_capacity_Wh; } #endif //DOUBLE_BATTERY } diff --git a/Software/src/datalayer/datalayer.h b/Software/src/datalayer/datalayer.h index 7c30ce93..10d49743 100644 --- a/Software/src/datalayer/datalayer.h +++ b/Software/src/datalayer/datalayer.h @@ -43,6 +43,12 @@ typedef struct { /** uint32_t */ /** Remaining energy capacity in Watt-hours */ uint32_t remaining_capacity_Wh; + /** The remaining capacity reported to the inverter based on min percentage setting, in Watt-hours + * This value will either be scaled or not scaled depending on the value of + * battery.settings.soc_scaling_active + */ + uint32_t reported_remaining_capacity_Wh; + /** Maximum allowed battery discharge power in Watts */ uint32_t max_discharge_power_W = 0; /** Maximum allowed battery charge power in Watts */ diff --git a/Software/src/devboard/mqtt/mqtt.cpp b/Software/src/devboard/mqtt/mqtt.cpp index 9de5e0d7..1bede4f1 100644 --- a/Software/src/devboard/mqtt/mqtt.cpp +++ b/Software/src/devboard/mqtt/mqtt.cpp @@ -55,8 +55,10 @@ SensorConfig sensorConfigs[] = { {"cell_min_voltage", "Battery Emulator Cell Min Voltage", "{{ value_json.cell_min_voltage }}", "V", "voltage"}, {"battery_voltage", "Battery Emulator Battery Voltage", "{{ value_json.battery_voltage }}", "V", "voltage"}, {"total_capacity", "Battery Emulator Battery Total Capacity", "{{ value_json.total_capacity }}", "Wh", "energy"}, - {"remaining_capacity", "Battery Emulator Battery Remaining Capacity", "{{ value_json.remaining_capacity }}", "Wh", + {"remaining_capacity", "Battery Emulator Battery Remaining Capacity (scaled)", "{{ value_json.remaining_capacity }}", "Wh", "energy"}, + {"remaining_capacity_real", "Battery Emulator Battery Remaining Capacity (real)", + "{{ value_json.remaining_capacity_real }}", "Wh", "energy"}, {"max_discharge_power", "Battery Emulator Battery Max Discharge Power", "{{ value_json.max_discharge_power }}", "W", "power"}, {"max_charge_power", "Battery Emulator Battery Max Charge Power", "{{ value_json.max_charge_power }}", "W", @@ -141,7 +143,8 @@ static void publish_common_info(void) { doc["cell_min_voltage"] = ((float)datalayer.battery.status.cell_min_voltage_mV) / 1000.0; } doc["total_capacity"] = ((float)datalayer.battery.info.total_capacity_Wh); - doc["remaining_capacity"] = ((float)datalayer.battery.status.remaining_capacity_Wh); + doc["remaining_capacity_real"] = ((float)datalayer.battery.status.remaining_capacity_Wh); + doc["remaining_capacity"] = ((float)datalayer.battery.status.reported_remaining_capacity_Wh); doc["max_discharge_power"] = ((float)datalayer.battery.status.max_discharge_power_W); doc["max_charge_power"] = ((float)datalayer.battery.status.max_charge_power_W); } diff --git a/Software/src/devboard/webserver/webserver.cpp b/Software/src/devboard/webserver/webserver.cpp index 1335c7cd..f1b3230a 100644 --- a/Software/src/devboard/webserver/webserver.cpp +++ b/Software/src/devboard/webserver/webserver.cpp @@ -653,7 +653,9 @@ String processor(const String& var) { content += "

Current: " + String(currentFloat, 1) + " A

"; content += formatPowerValue("Power", powerFloat, "", 1); content += formatPowerValue("Total capacity", datalayer.battery.info.total_capacity_Wh, "h", 0); - content += formatPowerValue("Remaining capacity", datalayer.battery.status.remaining_capacity_Wh, "h", 1); + content += formatPowerValue("Real Remaining capacity", datalayer.battery.status.remaining_capacity_Wh, "h", 1); + content += + formatPowerValue("Scaled Remaining capacity", datalayer.battery.status.reported_remaining_capacity_Wh, "h", 1); if (emulator_pause_status == NORMAL) { content += formatPowerValue("Max discharge power", datalayer.battery.status.max_discharge_power_W, "", 1); diff --git a/Software/src/inverter/BYD-MODBUS.cpp b/Software/src/inverter/BYD-MODBUS.cpp index 498b97a6..c37a5139 100644 --- a/Software/src/inverter/BYD-MODBUS.cpp +++ b/Software/src/inverter/BYD-MODBUS.cpp @@ -83,8 +83,9 @@ void handle_update_data_modbusp301_byd() { mbPV[300] = datalayer.battery.status.bms_status; mbPV[302] = 128 + bms_char_dis_status; mbPV[303] = datalayer.battery.status.reported_soc; - mbPV[304] = std::min(datalayer.battery.info.total_capacity_Wh, static_cast(60000u)); //Cap to 60kWh - mbPV[305] = std::min(datalayer.battery.status.remaining_capacity_Wh, static_cast(60000u)); //Cap to 60kWh + mbPV[304] = std::min(datalayer.battery.info.total_capacity_Wh, static_cast(60000u)); //Cap to 60kWh + mbPV[305] = + std::min(datalayer.battery.status.reported_remaining_capacity_Wh, static_cast(60000u)); //Cap to 60kWh mbPV[306] = std::min(max_discharge_W, static_cast(30000u)); //Cap to 30000 if exceeding mbPV[307] = std::min(max_charge_W, static_cast(30000u)); //Cap to 30000 if exceeding mbPV[310] = datalayer.battery.status.voltage_dV; diff --git a/Software/src/inverter/BYD-SMA.cpp b/Software/src/inverter/BYD-SMA.cpp index 6b778ff5..162cfb3a 100644 --- a/Software/src/inverter/BYD-SMA.cpp +++ b/Software/src/inverter/BYD-SMA.cpp @@ -108,8 +108,9 @@ void update_values_can_inverter() { //This function maps all the values fetched ((datalayer.battery.status.temperature_max_dC + datalayer.battery.status.temperature_min_dC) / 2); if (datalayer.battery.status.voltage_dV > 10) { // Only update value when we have voltage available to avoid div0 - ampere_hours_remaining = ((datalayer.battery.status.remaining_capacity_Wh / datalayer.battery.status.voltage_dV) * - 100); //(WH[10000] * V+1[3600])*100 = 270 (27.0Ah) + ampere_hours_remaining = + ((datalayer.battery.status.reported_remaining_capacity_Wh / datalayer.battery.status.voltage_dV) * + 100); //(WH[10000] * V+1[3600])*100 = 270 (27.0Ah) } //Map values to CAN messages diff --git a/Software/src/inverter/FOXESS-CAN.cpp b/Software/src/inverter/FOXESS-CAN.cpp index 6ca75b4b..8b567946 100644 --- a/Software/src/inverter/FOXESS-CAN.cpp +++ b/Software/src/inverter/FOXESS-CAN.cpp @@ -436,8 +436,8 @@ void update_values_can_inverter() { //This function maps all the CAN values fet FOXESS_1873.data.u8[3] = (datalayer.battery.status.current_dA >> 8); FOXESS_1873.data.u8[4] = (uint8_t)(datalayer.battery.status.reported_soc / 100); //SOC (0-100%) FOXESS_1873.data.u8[5] = 0x00; - FOXESS_1873.data.u8[6] = (uint8_t)(datalayer.battery.status.remaining_capacity_Wh / 10); - FOXESS_1873.data.u8[7] = ((datalayer.battery.status.remaining_capacity_Wh / 10) >> 8); + FOXESS_1873.data.u8[6] = (uint8_t)(datalayer.battery.status.reported_remaining_capacity_Wh / 10); + FOXESS_1873.data.u8[7] = ((datalayer.battery.status.reported_remaining_capacity_Wh / 10) >> 8); //BMS_CellData FOXESS_1874.data.u8[0] = (int8_t)datalayer.battery.status.temperature_max_dC; diff --git a/Software/src/inverter/SMA-CAN.cpp b/Software/src/inverter/SMA-CAN.cpp index 56f01e60..4fabbd16 100644 --- a/Software/src/inverter/SMA-CAN.cpp +++ b/Software/src/inverter/SMA-CAN.cpp @@ -105,8 +105,9 @@ void update_values_can_inverter() { //This function maps all the values fetched ((datalayer.battery.status.temperature_max_dC + datalayer.battery.status.temperature_min_dC) / 2); if (datalayer.battery.status.voltage_dV > 10) { // Only update value when we have voltage available to avoid div0 - ampere_hours_remaining = ((datalayer.battery.status.remaining_capacity_Wh / datalayer.battery.status.voltage_dV) * - 100); //(WH[10000] * V+1[3600])*100 = 270 (27.0Ah) + ampere_hours_remaining = + ((datalayer.battery.status.reported_remaining_capacity_Wh / datalayer.battery.status.voltage_dV) * + 100); //(WH[10000] * V+1[3600])*100 = 270 (27.0Ah) } //Map values to CAN messages diff --git a/Software/src/inverter/SMA-TRIPOWER-CAN.cpp b/Software/src/inverter/SMA-TRIPOWER-CAN.cpp index 4eefaf99..0cf0e0b7 100644 --- a/Software/src/inverter/SMA-TRIPOWER-CAN.cpp +++ b/Software/src/inverter/SMA-TRIPOWER-CAN.cpp @@ -149,8 +149,9 @@ void update_values_can_inverter() { //This function maps all the values fetched temperature_average = ((datalayer.battery.status.temperature_max_dC + datalayer.battery.status.temperature_min_dC) / 2); - ampere_hours_remaining = ((datalayer.battery.status.remaining_capacity_Wh / datalayer.battery.status.voltage_dV) * - 100); //(WH[10000] * V+1[3600])*100 = 270 (27.0Ah) + ampere_hours_remaining = + ((datalayer.battery.status.reported_remaining_capacity_Wh / datalayer.battery.status.voltage_dV) * + 100); //(WH[10000] * V+1[3600])*100 = 270 (27.0Ah) ampere_hours_max = ((datalayer.battery.info.total_capacity_Wh / datalayer.battery.status.voltage_dV) * 100); //(WH[10000] * V+1[3600])*100 = 270 (27.0Ah) diff --git a/Software/src/inverter/SOLAX-CAN.cpp b/Software/src/inverter/SOLAX-CAN.cpp index a1cde078..32c6207b 100644 --- a/Software/src/inverter/SOLAX-CAN.cpp +++ b/Software/src/inverter/SOLAX-CAN.cpp @@ -144,10 +144,10 @@ void update_values_can_inverter() { //This function maps all the values fetched capped_capacity_Wh = datalayer.battery.info.total_capacity_Wh; } // Batteries might be larger than uint16_t value can take - if (datalayer.battery.status.remaining_capacity_Wh > 65000) { + if (datalayer.battery.status.reported_remaining_capacity_Wh > 65000) { capped_remaining_capacity_Wh = 65000; } else { - capped_remaining_capacity_Wh = datalayer.battery.status.remaining_capacity_Wh; + capped_remaining_capacity_Wh = datalayer.battery.status.reported_remaining_capacity_Wh; } //Put the values into the CAN messages From e4bced9873e0d2e89c2da56cf966144d2fdfba68 Mon Sep 17 00:00:00 2001 From: amarofarinha <151563493+amarofarinha@users.noreply.github.com> Date: Wed, 23 Oct 2024 01:28:19 +0100 Subject: [PATCH 054/210] add mqtt --- Software/src/devboard/mqtt/mqtt.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Software/src/devboard/mqtt/mqtt.cpp b/Software/src/devboard/mqtt/mqtt.cpp index 1bede4f1..69a55cc4 100644 --- a/Software/src/devboard/mqtt/mqtt.cpp +++ b/Software/src/devboard/mqtt/mqtt.cpp @@ -55,8 +55,8 @@ SensorConfig sensorConfigs[] = { {"cell_min_voltage", "Battery Emulator Cell Min Voltage", "{{ value_json.cell_min_voltage }}", "V", "voltage"}, {"battery_voltage", "Battery Emulator Battery Voltage", "{{ value_json.battery_voltage }}", "V", "voltage"}, {"total_capacity", "Battery Emulator Battery Total Capacity", "{{ value_json.total_capacity }}", "Wh", "energy"}, - {"remaining_capacity", "Battery Emulator Battery Remaining Capacity (scaled)", "{{ value_json.remaining_capacity }}", "Wh", - "energy"}, + {"remaining_capacity", "Battery Emulator Battery Remaining Capacity (scaled)", + "{{ value_json.remaining_capacity }}", "Wh", "energy"}, {"remaining_capacity_real", "Battery Emulator Battery Remaining Capacity (real)", "{{ value_json.remaining_capacity_real }}", "Wh", "energy"}, {"max_discharge_power", "Battery Emulator Battery Max Discharge Power", "{{ value_json.max_discharge_power }}", "W", From 40aa274b48cf19e4334ec5b60815414b33fe8a0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Thu, 24 Oct 2024 12:52:58 +0300 Subject: [PATCH 055/210] Add event for unavailable value --- Software/src/battery/NISSAN-LEAF-BATTERY.cpp | 12 ++++++++++++ Software/src/devboard/utils/events.cpp | 3 +++ Software/src/devboard/utils/events.h | 3 ++- 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/Software/src/battery/NISSAN-LEAF-BATTERY.cpp b/Software/src/battery/NISSAN-LEAF-BATTERY.cpp index b1406a3e..67bb21f9 100644 --- a/Software/src/battery/NISSAN-LEAF-BATTERY.cpp +++ b/Software/src/battery/NISSAN-LEAF-BATTERY.cpp @@ -253,6 +253,12 @@ void update_values_battery() { /* This function maps all the values fetched via clear_event(EVENT_BATTERY_EMPTY); } + if (battery_Total_Voltage2 == 0x3FF) { //Battery reports critical measurement unavailable + set_event(EVENT_BATTERY_VALUE_UNAVAILABLE, 0); + } else { + clear_event(EVENT_BATTERY_VALUE_UNAVAILABLE); + } + if (battery_Relay_Cut_Request) { //battery_FAIL, BMS requesting shutdown and contactors to be opened //Note, this is sometimes triggered during the night while idle, and the BMS recovers after a while. Removed latching from this scenario datalayer.battery.status.max_discharge_power_W = 0; @@ -420,6 +426,12 @@ void update_values_battery2() { // Handle the values coming in from battery #2 clear_event(EVENT_BATTERY_EMPTY); } + if (battery2_Total_Voltage2 == 0x3FF) { //Battery reports critical measurement unavailable + set_event(EVENT_BATTERY_VALUE_UNAVAILABLE, 0); + } else { + clear_event(EVENT_BATTERY_VALUE_UNAVAILABLE); + } + if (battery2_Relay_Cut_Request) { //battery2_FAIL, BMS requesting shutdown and contactors to be opened //Note, this is sometimes triggered during the night while idle, and the BMS recovers after a while. Removed latching from this scenario datalayer.battery2.status.max_discharge_power_W = 0; diff --git a/Software/src/devboard/utils/events.cpp b/Software/src/devboard/utils/events.cpp index ce78042a..ebe3cfd7 100644 --- a/Software/src/devboard/utils/events.cpp +++ b/Software/src/devboard/utils/events.cpp @@ -167,6 +167,7 @@ void init_events(void) { events.entries[EVENT_BATTERY_OVERHEAT].level = EVENT_LEVEL_ERROR; events.entries[EVENT_BATTERY_OVERVOLTAGE].level = EVENT_LEVEL_WARNING; events.entries[EVENT_BATTERY_UNDERVOLTAGE].level = EVENT_LEVEL_WARNING; + events.entries[EVENT_BATTERY_VALUE_UNAVAILABLE].level = EVENT_LEVEL_WARNING; events.entries[EVENT_BATTERY_ISOLATION].level = EVENT_LEVEL_WARNING; events.entries[EVENT_VOLTAGE_DIFFERENCE].level = EVENT_LEVEL_INFO; events.entries[EVENT_SOH_DIFFERENCE].level = EVENT_LEVEL_WARNING; @@ -318,6 +319,8 @@ const char* get_event_message_string(EVENTS_ENUM_TYPE event) { return "Warning: Battery exceeding maximum design voltage. Discharge battery to prevent damage!"; case EVENT_BATTERY_UNDERVOLTAGE: return "Warning: Battery under minimum design voltage. Charge battery to prevent damage!"; + case EVENT_BATTERY_VALUE_UNAVAILABLE: + return "Warning: Battery measurement unavailable. Check 12V power supply and battery wiring!"; case EVENT_BATTERY_ISOLATION: return "Warning: Battery reports isolation error. High voltage might be leaking to ground. Check battery!"; case EVENT_VOLTAGE_DIFFERENCE: diff --git a/Software/src/devboard/utils/events.h b/Software/src/devboard/utils/events.h index b91860b4..157d0595 100644 --- a/Software/src/devboard/utils/events.h +++ b/Software/src/devboard/utils/events.h @@ -6,7 +6,7 @@ // #define INCLUDE_EVENTS_TEST // Enable to run an event test loop, see events_test_on_target.cpp -#define EE_MAGIC_HEADER_VALUE 0x0015 // 0x0000 to 0xFFFF +#define EE_MAGIC_HEADER_VALUE 0x0016 // 0x0000 to 0xFFFF #define GENERATE_ENUM(ENUM) ENUM, #define GENERATE_STRING(STRING) #STRING, @@ -53,6 +53,7 @@ XX(EVENT_BATTERY_OVERHEAT) \ XX(EVENT_BATTERY_OVERVOLTAGE) \ XX(EVENT_BATTERY_UNDERVOLTAGE) \ + XX(EVENT_BATTERY_VALUE_UNAVAILABLE) \ XX(EVENT_BATTERY_ISOLATION) \ XX(EVENT_BATTERY_REQUESTS_HEAT) \ XX(EVENT_BATTERY_WARMED_UP) \ From 966a759448e468b63e054d747c4f80547e32fc41 Mon Sep 17 00:00:00 2001 From: amarofarinha <151563493+amarofarinha@users.noreply.github.com> Date: Fri, 25 Oct 2024 15:39:32 +0100 Subject: [PATCH 056/210] Calculate scaled remaining capacity using estimated total capacity from SOC --- Software/Software.ino | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/Software/Software.ino b/Software/Software.ino index d6670f77..22dc38be 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -866,12 +866,14 @@ void update_scaled_values() { datalayer.battery.status.reported_soc = calc_soc; // Calculate the scaled remaining capacity in Wh - if (datalayer.battery.info.total_capacity_Wh > 0) { - uint32_t calc_capacity; - // remove % capacity not used in min_percentage to total_capacity_Wh - calc_capacity = datalayer.battery.settings.min_percentage * datalayer.battery.info.total_capacity_Wh / 10000; - calc_capacity = datalayer.battery.status.remaining_capacity_Wh - calc_capacity; - datalayer.battery.status.reported_remaining_capacity_Wh = calc_capacity; + if (datalayer.battery.info.total_capacity_Wh > 0 && datalayer.battery.status.real_soc > 0) { + uint32_t calc_max_capacity; + uint32_t calc_reserved_capacity; + calc_max_capacity = (datalayer.battery.status.remaining_capacity_Wh * 10000 / datalayer.battery.status.real_soc); + calc_reserved_capacity = calc_max_capacity * datalayer.battery.settings.min_percentage / 10000; + // remove % capacity reserved in min_percentage to total_capacity_Wh + datalayer.battery.status.reported_remaining_capacity_Wh = + datalayer.battery.status.remaining_capacity_Wh - calc_reserved_capacity; } else { datalayer.battery.status.reported_remaining_capacity_Wh = datalayer.battery.status.remaining_capacity_Wh; } From 4c9e1a3169e3002818b4d2b9ee7b5dd47a4826f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Fri, 25 Oct 2024 21:56:52 +0300 Subject: [PATCH 057/210] Add iX skeleton --- Software/USER_SETTINGS.h | 1 + Software/src/battery/BATTERIES.h | 4 + Software/src/battery/BMW-IX-BATTERY.cpp | 193 ++++++++++++++++++ Software/src/battery/BMW-IX-BATTERY.h | 17 ++ Software/src/devboard/webserver/webserver.cpp | 3 + 5 files changed, 218 insertions(+) create mode 100644 Software/src/battery/BMW-IX-BATTERY.cpp create mode 100644 Software/src/battery/BMW-IX-BATTERY.h diff --git a/Software/USER_SETTINGS.h b/Software/USER_SETTINGS.h index c5cbd65f..9c127e62 100644 --- a/Software/USER_SETTINGS.h +++ b/Software/USER_SETTINGS.h @@ -10,6 +10,7 @@ /* Select battery used */ //#define BMW_I3_BATTERY +//#define BMW_IX_BATTERY //#define BYD_ATTO_3_BATTERY //#define CHADEMO_BATTERY //NOTE: inherently enables CONTACTOR_CONTROL below //#define IMIEV_CZERO_ION_BATTERY diff --git a/Software/src/battery/BATTERIES.h b/Software/src/battery/BATTERIES.h index 8539c551..d0af768f 100644 --- a/Software/src/battery/BATTERIES.h +++ b/Software/src/battery/BATTERIES.h @@ -6,6 +6,10 @@ #include "BMW-I3-BATTERY.h" #endif +#ifdef BMW_IX_BATTERY +#include "BMW-IX-BATTERY.h" +#endif + #ifdef BYD_ATTO_3_BATTERY #include "BYD-ATTO-3-BATTERY.h" #endif diff --git a/Software/src/battery/BMW-IX-BATTERY.cpp b/Software/src/battery/BMW-IX-BATTERY.cpp new file mode 100644 index 00000000..78559bce --- /dev/null +++ b/Software/src/battery/BMW-IX-BATTERY.cpp @@ -0,0 +1,193 @@ +#include "../include.h" +#ifdef BMW_IX_BATTERY +#include "../datalayer/datalayer.h" +#include "../datalayer/datalayer_extended.h" +#include "../devboard/utils/events.h" +#include "BMW-IX-BATTERY.h" + +/* Do not change code below unless you are sure what you are doing */ +static unsigned long previousMillis20 = 0; // will store last time a 20ms CAN Message was send +static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send +static unsigned long previousMillis200 = 0; // will store last time a 200ms CAN Message was send +static unsigned long previousMillis500 = 0; // will store last time a 500ms CAN Message was send +static unsigned long previousMillis640 = 0; // will store last time a 600ms CAN Message was send +static unsigned long previousMillis1000 = 0; // will store last time a 1000ms CAN Message was send +static unsigned long previousMillis5000 = 0; // will store last time a 5000ms CAN Message was send +static unsigned long previousMillis10000 = 0; // will store last time a 10000ms CAN Message was send + +#define ALIVE_MAX_VALUE 14 // BMW CAN messages contain alive counter, goes from 0...14 + +enum CmdState { SOH, CELL_VOLTAGE_MINMAX, SOC, CELL_VOLTAGE_CELLNO, CELL_VOLTAGE_CELLNO_LAST }; + +static CmdState cmdState = SOC; + +CAN_frame BMW_6F1_CELL = {.FD = false, .ext_ID = false, .DLC = 5, .ID = 0x6F1, .data = {0x07, 0x03, 0x22, 0xDD, 0xBF}}; +CAN_frame BMW_6F1_SOH = {.FD = false, .ext_ID = false, .DLC = 5, .ID = 0x6F1, .data = {0x07, 0x03, 0x22, 0x63, 0x35}}; +CAN_frame BMW_6F1_SOC = {.FD = false, .ext_ID = false, .DLC = 5, .ID = 0x6F1, .data = {0x07, 0x03, 0x22, 0xDD, 0xBC}}; +CAN_frame BMW_6F1_CELL_VOLTAGE_AVG = {.FD = false, + .ext_ID = false, + .DLC = 5, + .ID = 0x6F1, + .data = {0x07, 0x03, 0x22, 0xDF, 0xA0}}; +CAN_frame BMW_6F1_CONTINUE = {.FD = false, .ext_ID = false, .DLC = 4, .ID = 0x6F1, .data = {0x07, 0x30, 0x00, 0x02}}; +CAN_frame BMW_6F4_CELL_VOLTAGE_CELLNO = {.FD = false, + .ext_ID = false, + .DLC = 7, + .ID = 0x6F4, + .data = {0x07, 0x05, 0x31, 0x01, 0xAD, 0x6E, 0x01}}; +CAN_frame BMW_6F4_CELL_CONTINUE = {.FD = false, + .ext_ID = false, + .DLC = 6, + .ID = 0x6F4, + .data = {0x07, 0x04, 0x31, 0x03, 0xAD, 0x6E}}; + +static bool battery_awake = false; + +static uint8_t current_cell_polled = 0; + +static uint8_t increment_alive_counter(uint8_t counter) { + counter++; + if (counter > ALIVE_MAX_VALUE) { + counter = 0; + } + return counter; +} + +void update_values_battery() { //This function maps all the values fetched via CAN to the battery datalayer + + datalayer.battery.status.real_soc; + + datalayer.battery.status.voltage_dV; + + datalayer.battery.status.current_dA; + + datalayer.battery.info.total_capacity_Wh; + + datalayer.battery.status.remaining_capacity_Wh; + + datalayer.battery.status.soh_pptt; + + datalayer.battery.status.max_discharge_power_W; + + datalayer.battery.status.max_charge_power_W; + + datalayer.battery.status.active_power_W; + + datalayer.battery.status.temperature_min_dC; + + datalayer.battery.status.temperature_max_dC; +} + +void receive_can_battery(CAN_frame rx_frame) { + battery_awake = true; + switch (rx_frame.ID) { + case 0x112: + break; + default: + break; + } +} + +void send_can_battery() { + unsigned long currentMillis = millis(); + + if (battery_awake) { + //Send 20ms message + if (currentMillis - previousMillis20 >= INTERVAL_20_MS) { + // Check if sending of CAN messages has been delayed too much. + if ((currentMillis - previousMillis20 >= INTERVAL_20_MS_DELAYED) && (currentMillis > BOOTUP_TIME)) { + set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis20)); + } else { + clear_event(EVENT_CAN_OVERRUN); + } + previousMillis20 = currentMillis; + } + // Send 100ms CAN Message + if (currentMillis - previousMillis100 >= INTERVAL_100_MS) { + previousMillis100 = currentMillis; + } + // Send 200ms CAN Message + if (currentMillis - previousMillis200 >= INTERVAL_200_MS) { + previousMillis200 = currentMillis; + } + // Send 500ms CAN Message + if (currentMillis - previousMillis500 >= INTERVAL_500_MS) { + previousMillis500 = currentMillis; + } + // Send 640ms CAN Message + if (currentMillis - previousMillis640 >= INTERVAL_640_MS) { + previousMillis640 = currentMillis; + } + // Send 1000ms CAN Message + if (currentMillis - previousMillis1000 >= INTERVAL_1_S) { + previousMillis1000 = currentMillis; + + switch (cmdState) { + case SOC: + transmit_can(&BMW_6F1_CELL, can_config.battery); + cmdState = CELL_VOLTAGE_MINMAX; + break; + case CELL_VOLTAGE_MINMAX: + transmit_can(&BMW_6F1_SOH, can_config.battery); + cmdState = SOH; + break; + case SOH: + transmit_can(&BMW_6F1_CELL_VOLTAGE_AVG, can_config.battery); + cmdState = CELL_VOLTAGE_CELLNO; + current_cell_polled = 0; + + break; + case CELL_VOLTAGE_CELLNO: + current_cell_polled++; + if (current_cell_polled > 96) { + datalayer.battery.info.number_of_cells = 97; + cmdState = CELL_VOLTAGE_CELLNO_LAST; + } else { + cmdState = CELL_VOLTAGE_CELLNO; + + BMW_6F4_CELL_VOLTAGE_CELLNO.data.u8[6] = current_cell_polled; + transmit_can(&BMW_6F4_CELL_VOLTAGE_CELLNO, can_config.battery); + } + break; + case CELL_VOLTAGE_CELLNO_LAST: + transmit_can(&BMW_6F1_SOC, can_config.battery); + cmdState = SOC; + break; + } + } + // Send 5000ms CAN Message + if (currentMillis - previousMillis5000 >= INTERVAL_5_S) { + previousMillis5000 = currentMillis; + } + // Send 10000ms CAN Message + if (currentMillis - previousMillis10000 >= INTERVAL_10_S) { + previousMillis10000 = currentMillis; + } + } else { + previousMillis20 = currentMillis; + previousMillis100 = currentMillis; + previousMillis200 = currentMillis; + previousMillis500 = currentMillis; + previousMillis640 = currentMillis; + previousMillis1000 = currentMillis; + previousMillis5000 = currentMillis; + previousMillis10000 = currentMillis; + } +} + +void setup_battery(void) { // Performs one time setup at startup +#ifdef DEBUG_VIA_USB + Serial.println("BMW iX battery selected"); +#endif //DEBUG_VIA_USB + + //Before we have started up and detected which battery is in use, use 60AH values + datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; + datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV; + datalayer.battery.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV; + datalayer.system.status.battery_allows_contactor_closing = true; + + pinMode(WUP_PIN, OUTPUT); + digitalWrite(WUP_PIN, HIGH); // Wake up the battery +} + +#endif diff --git a/Software/src/battery/BMW-IX-BATTERY.h b/Software/src/battery/BMW-IX-BATTERY.h new file mode 100644 index 00000000..8c2e24c9 --- /dev/null +++ b/Software/src/battery/BMW-IX-BATTERY.h @@ -0,0 +1,17 @@ +#ifndef BMW_IX_BATTERY_H +#define BMW_IX_BATTERY_H +#include +#include "../include.h" + +#define BATTERY_SELECTED + +#define WUP_PIN 25 +#define MAX_PACK_VOLTAGE_DV 5000 //5000 = 500.0V +#define MIN_PACK_VOLTAGE_DV 3000 +#define MAX_CELL_DEVIATION_MV 500 +#define MAX_CELL_VOLTAGE_MV 4250 //Battery is put into emergency stop if one cell goes over this value +#define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value +void setup_battery(void); +void transmit_can(CAN_frame* tx_frame, int interface); + +#endif diff --git a/Software/src/devboard/webserver/webserver.cpp b/Software/src/devboard/webserver/webserver.cpp index f1b3230a..ee321d9c 100644 --- a/Software/src/devboard/webserver/webserver.cpp +++ b/Software/src/devboard/webserver/webserver.cpp @@ -513,6 +513,9 @@ String processor(const String& var) { #ifdef BMW_I3_BATTERY content += "BMW i3"; #endif // BMW_I3_BATTERY +#ifdef BMW_IX_BATTERY + content += "BMW iX and i4-7 platform"; +#endif // BMW_IX_BATTERY #ifdef BYD_ATTO_3_BATTERY content += "BYD Atto 3"; #endif // BYD_ATTO_3_BATTERY From 1996c9a850db92dbd1343a5a3c0b5fdc137fd9b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Sat, 26 Oct 2024 21:11:29 +0300 Subject: [PATCH 058/210] Add more info page for Atto3 --- Software/src/battery/BYD-ATTO-3-BATTERY.cpp | 25 +++++++++++++++---- Software/src/datalayer/datalayer_extended.h | 20 +++++++++++++++ .../webserver/advanced_battery_html.cpp | 12 +++++++-- 3 files changed, 50 insertions(+), 7 deletions(-) diff --git a/Software/src/battery/BYD-ATTO-3-BATTERY.cpp b/Software/src/battery/BYD-ATTO-3-BATTERY.cpp index f84571eb..f20f0c1f 100644 --- a/Software/src/battery/BYD-ATTO-3-BATTERY.cpp +++ b/Software/src/battery/BYD-ATTO-3-BATTERY.cpp @@ -1,6 +1,7 @@ #include "../include.h" #ifdef BYD_ATTO_3_BATTERY #include "../datalayer/datalayer.h" +#include "../datalayer/datalayer_extended.h" #include "../devboard/utils/events.h" #include "BYD-ATTO-3-BATTERY.h" @@ -18,13 +19,14 @@ static uint8_t counter_50ms = 0; static uint8_t counter_100ms = 0; static uint8_t frame6_counter = 0xB; static uint8_t frame7_counter = 0x5; - +static uint16_t battery_voltage = 0; static int16_t battery_temperature_ambient = 0; static int16_t battery_daughterboard_temperatures[10]; static int16_t battery_lowest_temperature = 0; static int16_t battery_highest_temperature = 0; static int16_t battery_calc_min_temperature = 0; static int16_t battery_calc_max_temperature = 0; +static uint16_t battery_highprecision_SOC = 0; static uint16_t BMS_SOC = 0; static uint16_t BMS_voltage = 0; static int16_t BMS_current = 0; @@ -40,6 +42,7 @@ static int16_t battery2_lowest_temperature = 0; static int16_t battery2_highest_temperature = 0; static int16_t battery2_calc_min_temperature = 0; static int16_t battery2_calc_max_temperature = 0; +static uint16_t battery2_highprecision_SOC = 0; static uint16_t BMS2_SOC = 0; static uint16_t BMS2_voltage = 0; static int16_t BMS2_current = 0; @@ -142,6 +145,14 @@ void update_values_battery() { //This function maps all the values fetched via datalayer.battery.status.temperature_min_dC = battery_calc_min_temperature * 10; // Add decimals datalayer.battery.status.temperature_max_dC = battery_calc_max_temperature * 10; + + // Update webserver datalayer + datalayer_extended.bydAtto3.SOC_estimated = datalayer.battery.status.real_soc; + //Once we implement switching logic, remember to change from where the estimated is taken + datalayer_extended.bydAtto3.SOC_highprec = battery_highprecision_SOC; + datalayer_extended.bydAtto3.SOC_polled = BMS_SOC; + datalayer_extended.bydAtto3.voltage_periodic = battery_voltage; + datalayer_extended.bydAtto3.voltage_polled = BMS_voltage; } void receive_can_battery(CAN_frame rx_frame) { @@ -220,6 +231,8 @@ void receive_can_battery(CAN_frame rx_frame) { case 0x444: //9E,01,88,13,64,64,98,65 //9A,01,B6,13,64,64,98,3B //407.5V 18deg //9B,01,B8,13,64,64,98,38 //408.5V 14deg + battery_voltage = ((rx_frame.data.u8[1] & 0x0F) << 8) | rx_frame.data.u8[0]; + //battery_temperature_something = rx_frame.data.u8[7] - 40; resides in frame 7 break; case 0x445: //00,98,FF,FF,63,20,4E,98 - Static, values never changes between logs break; @@ -228,8 +241,9 @@ void receive_can_battery(CAN_frame rx_frame) { case 0x447: // Seems to contain more temperatures, highest and lowest? //06,38,01,3B,E0,03,39,69 //06,36,02,36,E0,03,36,72, - battery_lowest_temperature = (rx_frame.data.u8[1] - 40); //Best guess for now - battery_highest_temperature = (rx_frame.data.u8[3] - 40); //Best guess for now + battery_highprecision_SOC = ((rx_frame.data.u8[5] & 0x0F) << 8) | rx_frame.data.u8[4]; // 03 E0 = 992 = 99.2% + battery_lowest_temperature = (rx_frame.data.u8[1] - 40); //Best guess for now + battery_highest_temperature = (rx_frame.data.u8[3] - 40); //Best guess for now break; case 0x47B: //01,FF,FF,FF,FF,FF,FF,FF - Static, values never changes between logs break; @@ -540,8 +554,9 @@ void receive_can_battery2(CAN_frame rx_frame) { case 0x447: // Seems to contain more temperatures, highest and lowest? //06,38,01,3B,E0,03,39,69 //06,36,02,36,E0,03,36,72, - battery2_lowest_temperature = (rx_frame.data.u8[1] - 40); //Best guess for now - battery2_highest_temperature = (rx_frame.data.u8[3] - 40); //Best guess for now + battery2_highprecision_SOC = ((rx_frame.data.u8[5] & 0x0F) << 8) | rx_frame.data.u8[4]; // 03 E0 = 992 = 99.2% + battery2_lowest_temperature = (rx_frame.data.u8[1] - 40); //Best guess for now + battery2_highest_temperature = (rx_frame.data.u8[3] - 40); //Best guess for now break; case 0x47B: //01,FF,FF,FF,FF,FF,FF,FF - Static, values never changes between logs break; diff --git a/Software/src/datalayer/datalayer_extended.h b/Software/src/datalayer/datalayer_extended.h index 96bd5e07..9cae0eb6 100644 --- a/Software/src/datalayer/datalayer_extended.h +++ b/Software/src/datalayer/datalayer_extended.h @@ -45,6 +45,25 @@ typedef struct { uint8_t ST_cold_shutoff_valve = 0; } DATALAYER_INFO_BMWI3; +typedef struct { + /** uint16_t */ + /** SOC% estimate. Estimated from total pack voltage */ + uint16_t SOC_estimated = 0; + /** uint16_t */ + /** SOC% raw battery value. Highprecision. Can be locked if pack is crashed */ + uint16_t SOC_highprec = 0; + /** uint16_t */ + /** SOC% polled OBD2 value. Can be locked if pack is crashed */ + uint16_t SOC_polled = 0; + /** uint16_t */ + /** Voltage raw battery value */ + uint16_t voltage_periodic = 0; + /** uint16_t */ + /** Voltage polled OBD2*/ + uint16_t voltage_polled = 0; + +} DATALAYER_INFO_BYDATTO3; + typedef struct { /** uint8_t */ /** Contactor status */ @@ -119,6 +138,7 @@ typedef struct { class DataLayerExtended { public: DATALAYER_INFO_BMWI3 bmwi3; + DATALAYER_INFO_BYDATTO3 bydAtto3; DATALAYER_INFO_TESLA tesla; DATALAYER_INFO_NISSAN_LEAF nissanleaf; }; diff --git a/Software/src/devboard/webserver/advanced_battery_html.cpp b/Software/src/devboard/webserver/advanced_battery_html.cpp index e8001d0c..223b67a4 100644 --- a/Software/src/devboard/webserver/advanced_battery_html.cpp +++ b/Software/src/devboard/webserver/advanced_battery_html.cpp @@ -100,6 +100,14 @@ String advanced_battery_processor(const String& var) { #endif //BMW_I3_BATTERY +#ifdef BYD_ATTO_3_BATTERY + content += "

SOC estimated: " + String(datalayer_extended.bydAtto3.SOC_estimated) + "

"; + content += "

SOC highprec: " + String(datalayer_extended.bydAtto3.SOC_highprec) + "

"; + content += "

SOC OBD2: " + String(datalayer_extended.bydAtto3.SOC_polled) + "

"; + content += "

Voltage periodic: " + String(datalayer_extended.bydAtto3.voltage_periodic) + "

"; + content += "

Voltage OBD2: " + String(datalayer_extended.bydAtto3.voltage_polled) + "

"; +#endif //BYD_ATTO_3_BATTERY + #ifdef TESLA_BATTERY static const char* contactorText[] = {"UNKNOWN(0)", "OPEN", "CLOSING", "BLOCKED", "OPENING", "CLOSED", "UNKNOWN(6)", "WELDED", "POS_CL", "NEG_CL", @@ -151,8 +159,8 @@ String advanced_battery_processor(const String& var) { content += "

Heating requested: " + String(datalayer_extended.nissanleaf.HeaterSendRequest) + "

"; #endif -#if !defined(TESLA_BATTERY) && !defined(NISSAN_LEAF_BATTERY) && \ - !defined(BMW_I3_BATTERY) //Only the listed types have extra info +#if !defined(TESLA_BATTERY) && !defined(NISSAN_LEAF_BATTERY) && !defined(BMW_I3_BATTERY) && \ + !defined(BYD_ATTO_3_BATTERY) //Only the listed types have extra info content += "No extra information available for this battery type"; #endif From 6b823e2c5b2fe40a41634241d3426c00364500f3 Mon Sep 17 00:00:00 2001 From: wjcloudy <56305354+wjcloudy@users.noreply.github.com> Date: Sat, 26 Oct 2024 22:55:00 +0100 Subject: [PATCH 059/210] Update BMW-IX-BATTERY.cpp First functional version - SME wake line can be held high to use. Notes: - Bat capacity max/remain is temp scaled by 0.1 so fits in uint16 - No max charge/discharge power - No SOH - No contactor control --- Software/src/battery/BMW-IX-BATTERY.cpp | 381 ++++++++++++++++++++++-- 1 file changed, 358 insertions(+), 23 deletions(-) diff --git a/Software/src/battery/BMW-IX-BATTERY.cpp b/Software/src/battery/BMW-IX-BATTERY.cpp index 78559bce..c13d0eb8 100644 --- a/Software/src/battery/BMW-IX-BATTERY.cpp +++ b/Software/src/battery/BMW-IX-BATTERY.cpp @@ -21,21 +21,149 @@ enum CmdState { SOH, CELL_VOLTAGE_MINMAX, SOC, CELL_VOLTAGE_CELLNO, CELL_VOLTAGE static CmdState cmdState = SOC; -CAN_frame BMW_6F1_CELL = {.FD = false, .ext_ID = false, .DLC = 5, .ID = 0x6F1, .data = {0x07, 0x03, 0x22, 0xDD, 0xBF}}; -CAN_frame BMW_6F1_SOH = {.FD = false, .ext_ID = false, .DLC = 5, .ID = 0x6F1, .data = {0x07, 0x03, 0x22, 0x63, 0x35}}; -CAN_frame BMW_6F1_SOC = {.FD = false, .ext_ID = false, .DLC = 5, .ID = 0x6F1, .data = {0x07, 0x03, 0x22, 0xDD, 0xBC}}; -CAN_frame BMW_6F1_CELL_VOLTAGE_AVG = {.FD = false, +/* +Suspected Vehicle comms required: + + 0x06D DLC? 1000ms - counters? + 0x2F1 DLC? 1000ms during run : 0xFF, 0xFF, 0xFF, 0xFF, 0x9B, 0x00, 0xF3, 0xFF - at startup 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xF3, 0xFF. Suspect byte [4] is a counter + 0x439 DLC4 1000ms STATIC + 0x0C0 DLC2 200ms needs counter + 0x510 DLC8 100ms STATIC 40 10 40 00 6F DF 19 00 during run - Startup sends this once: 0x40 0x10 0x02 0x00 0x00 0x00 0x00 0x00 + 0x587 DLC8 appears at startup 0x78 0x07 0x00 0x00 0xFF 0xFF 0xFF 0xFF , 0x01 0x03 0x80 0xFF 0xFF 0xFF 0xFF 0xFF, 0x78 0x07 0x00 0x00 0xFF 0xFF 0xFF 0xFF, 0x06 0x00 0x00 0xFF 0xFF 0xFF 0xFF 0xFF, 0x01 0x03 0x82 0xFF 0xFF 0xFF 0xFF 0xFF, 0x01 0x03 0x80 0xFF 0xFF 0xFF 0xFF 0xFF + +No vehicle log available, SME asks for: + 0x125 (CCU) + 0x16 (CCU) + 0x91 (EME1) + 0xAA (EME2) + +SME Output: + + 0x08F DLC48 10ms - Appears to have analog readings like volt/temp/current + 0x1D2 DLC8 1000ms + 0x20B DLC8 1000ms + 0x2E2 DLC16 1000ms + 0x2F1 DLC8 1000ms + 0x31F DLC16 100ms - 2 downward counters? + 0x453 DLC20 200ms + 0x486 DLC48 1000ms + 0x49C DLC8 1000ms + 0x4A1 DLC8 1000ms + 0x4BB DLC64 200ms - seems multplexed on [0] + 0x4D0 DLC64 1000ms - some slow/flickering values + 0x607 UDS Response + + + +UDS Map: + 69 - service disconnect (1 = closed) + c7 - available energy - available energy charged + ce - min avg max SOC + 61 len12 = current sensor + 53 - min and max cell voltage + 4d- main battery voltage + 4a - after contactor voltage + a4 = charnging contactor temp + A3 - MAIN CONTACTOR VOLTAGE + f4 00 0a 62 DD = main battery temp + A7 - T30 12v voltage (SME input voltage) + B6 - T30C 12v voltage (12v pyro sensor) + 51 = release, switch contactors. 1 = control of switch contactors active + CD = charge contactors + + TX 07 03 22 E5 54 - UDS request cell voltages + RX F4 10 E3 62 E5 54 - 16bit cell voltages batch1 (29 cells) + TX 07 30 00 02 - UDS request continue data + RX F4 21 0F - 16bit cell voltages continue2 (31 cells) + RX F4 22 0F - 16bit cell voltages continue3 (31 cells) + RX F4 23 0F - 16bit cell voltages continue4 (17 cells) + + TX 07 03 22 E5 9A - UDS request cell SOC + RX F4 10 E3 62 E5 9A?? - 16bit cell SOC batch1 (29 cells) + + TX 07 03 22 E5 CA - UDS request cell temps + RX F4 10 81 62 E5 CA - 16bit cell temps batch1 + RX F4 21 81 62 E5 CA - 16bit cell temps continue2 + + TX 07 30 00 02 - UDS request continue data + + +*/ + + + +CAN_frame BMWiX_06D = {.FD = true, + .ext_ID = false, + .DLC = 8, + .ID = 0x06D, + .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0xFF}}; // 1000ms BDC Output - [0] static [1]-[4] counter x2? is needed? [5-7] static + + +CAN_frame BMWiX_2F1 = {.FD = true, + .ext_ID = false, + .DLC = 8, + .ID = 0x2F1, + .data = {0xFF, 0xFF, 0xFF, 0xFF, 0x9B, 0x00, 0xF3, 0xFF}}; // 1000ms BDC Output - Static values - varies at startup + + + +CAN_frame BMWiX_439 = {.FD = true, + .ext_ID = false, + .DLC = 4, + .ID = 0x439, + .data = {0xFF, 0xBF, 0xFF, 0xFF}}; // 1000ms BDC Output - Static values + + + +CAN_frame BMWiX_510 = {.FD = true, + .ext_ID = false, + .DLC = 8, + .ID = 0x510, + .data = {0x40, 0x10, 0x40, 0x00, 0x6F, 0xDF, 0x19, 0x00}}; // 100ms BDC Output - Static values + + + +CAN_frame BMWiX_6F4 = {.FD = true, + .ext_ID = false, + .DLC = 5, + .ID = 0x6F4, + .data = {0x07, 0x03, 0x22, 0xE5, 0xC7}}; // UDS Request data from SME. byte 4 selects requested value + + +CAN_frame BMWiX_0C0 = {.FD = true, + .ext_ID = false, + .DLC = 2, + .ID = 0x0C0, + .data = {0xF0, 0x08}}; // Keep Alive 2 BDC>SME 200ms First byte cycles F0 > FE second byte 08 static + + +CAN_frame BMWiX_6F4_CELL_VOLTAGE = {.FD = true, .ext_ID = false, .DLC = 5, .ID = 0x6F4, .data = {0x07, 0x03, 0x22, 0xE5, 0x54}}; +CAN_frame BMWiX_6F4_CELL_SOC = {.FD = true, .ext_ID = false, .DLC = 5, .ID = 0x6F4, .data = {0x07, 0x03, 0x22, 0xE5, 0x9A}}; +CAN_frame BMWiX_6F4_CELL_TEMP = {.FD = true, .ext_ID = false, .DLC = 5, .ID = 0x6F4, .data = {0x07, 0x03, 0x22, 0xE5, 0xCA}}; + +CAN_frame BMWiX_6F4_CONTINUE_DATA = {.FD = true, .ext_ID = false, .DLC = 4, .ID = 0x6F4, .data = {0x07, 0x30, 0x00, 0x02}}; + +CAN_frame BMW_6F4_CELL = {.FD = true, .ext_ID = false, .DLC = 5, .ID = 0x6F4, .data = {0x07, 0x03, 0x22, 0xDD, 0xBF}};// gives error F4 03 7F 22 31 +CAN_frame BMW_6F4_SOH = {.FD = true, .ext_ID = false, .DLC = 5, .ID = 0x6F4, .data = {0x07, 0x03, 0x22, 0x63, 0x35}}; // gives error F4 03 7F 22 31 +CAN_frame BMW_6F4_SOC = {.FD = true, .ext_ID = false, .DLC = 5, .ID = 0x6F4, .data = {0x07, 0x03, 0x22, 0xDD, 0xBC}}; // gives blanks + +CAN_frame BMW_10B = {.FD = true, + .ext_ID = false, + .DLC = 3, + .ID = 0x10B, + .data = {0xCD, 0x00, 0xFC}}; // Contactor closing command + +CAN_frame BMW_6F4_CELL_VOLTAGE_AVG = {.FD = true, .ext_ID = false, .DLC = 5, - .ID = 0x6F1, + .ID = 0x6F4, .data = {0x07, 0x03, 0x22, 0xDF, 0xA0}}; -CAN_frame BMW_6F1_CONTINUE = {.FD = false, .ext_ID = false, .DLC = 4, .ID = 0x6F1, .data = {0x07, 0x30, 0x00, 0x02}}; CAN_frame BMW_6F4_CELL_VOLTAGE_CELLNO = {.FD = false, .ext_ID = false, .DLC = 7, .ID = 0x6F4, - .data = {0x07, 0x05, 0x31, 0x01, 0xAD, 0x6E, 0x01}}; -CAN_frame BMW_6F4_CELL_CONTINUE = {.FD = false, + .data = {0x07, 0x05, 0x31, 0x01, 0xAD, 0x6E, 0x01}}; // gives error +CAN_frame BMW_6F4_CELL_CONTINUE = {.FD = true, .ext_ID = false, .DLC = 6, .ID = 0x6F4, @@ -43,8 +171,37 @@ CAN_frame BMW_6F4_CELL_CONTINUE = {.FD = false, static bool battery_awake = false; +//iX Intermediate vars +static int32_t battery_current = 0; +static int16_t battery_voltage = 0; +static int16_t battery_voltage_after_contactor = 0; +static int16_t min_soc_state = 0; +static int16_t avg_soc_state = 0; +static int16_t max_soc_state = 0; +static int16_t remaining_capacity = 0; +static int16_t max_capacity = 0; +static int16_t min_battery_temperature = 0; +static int16_t avg_battery_temperature = 0; +static int16_t max_battery_temperature = 0; +static int16_t main_contactor_temperature = 0; +static int16_t min_cell_voltage = 0; +static int16_t max_cell_voltage = 0; +static int16_t battery_power = 0; +static uint8_t uds_req_id_counter = 0; +static byte iX_0C0_counter = 0xF0; // Initialize to 0xF0 + +//End iX Intermediate vars + static uint8_t current_cell_polled = 0; +static uint8_t increment_uds_req_id_counter(uint8_t counter) { + counter++; + if (counter > 7) { + counter = 0; + } + return counter; +} + static uint8_t increment_alive_counter(uint8_t counter) { counter++; if (counter > ALIVE_MAX_VALUE) { @@ -53,29 +210,48 @@ static uint8_t increment_alive_counter(uint8_t counter) { return counter; } +static byte increment_0C0_counter(byte counter) { + counter++; + // Reset to 0xF0 if it exceeds 0xFE + if (counter > 0xFE) { + counter = 0xF0; + } + return counter; +} + void update_values_battery() { //This function maps all the values fetched via CAN to the battery datalayer - datalayer.battery.status.real_soc; + datalayer.battery.status.real_soc = avg_soc_state; + + datalayer.battery.status.voltage_dV = battery_voltage; + + datalayer.battery.status.current_dA = battery_current; + + datalayer.battery.info.total_capacity_Wh = max_capacity; - datalayer.battery.status.voltage_dV; + datalayer.battery.status.remaining_capacity_Wh = remaining_capacity; - datalayer.battery.status.current_dA; + datalayer.battery.status.soh_pptt = 9900; - datalayer.battery.info.total_capacity_Wh; + datalayer.battery.status.max_discharge_power_W = 6000; - datalayer.battery.status.remaining_capacity_Wh; + datalayer.battery.status.max_charge_power_W = 6000; - datalayer.battery.status.soh_pptt; + battery_power = (datalayer.battery.status.current_dA * (datalayer.battery.status.voltage_dV / 100)); - datalayer.battery.status.max_discharge_power_W; + datalayer.battery.status.active_power_W = battery_power; - datalayer.battery.status.max_charge_power_W; + datalayer.battery.status.temperature_min_dC = min_battery_temperature; - datalayer.battery.status.active_power_W; + datalayer.battery.status.temperature_max_dC = max_battery_temperature; - datalayer.battery.status.temperature_min_dC; + datalayer.battery.status.cell_min_voltage_mV = min_cell_voltage; - datalayer.battery.status.temperature_max_dC; + datalayer.battery.status.cell_max_voltage_mV = max_cell_voltage; + + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + + datalayer.battery.info.number_of_cells = 108; //Hardcoded for the SE26 battery until values found } void receive_can_battery(CAN_frame rx_frame) { @@ -83,6 +259,101 @@ void receive_can_battery(CAN_frame rx_frame) { switch (rx_frame.ID) { case 0x112: break; + case 0x607: //SME responds to UDS requests on 0x607 + + if (rx_frame.DLC > 6 && rx_frame.data.u8[0] == 0xF4 && rx_frame.data.u8[1] == 0x10 && rx_frame.data.u8[2] == 0xE3 && rx_frame.data.u8[3] == 0x62 && rx_frame.data.u8[4] == 0xE5){ + //First of multi frame data - Parse the first frame + if (rx_frame.DLC = 64 && rx_frame.data.u8[5] == 0x54) { //Individual Cell Voltages - First Frame + int start_index = 6; //Data starts here + int voltage_index = 0; //Start cell ID + int num_voltages = 29; // number of voltage readings to get + for (int i = start_index; i < (start_index + num_voltages * 2); i += 2) { + uint16_t voltage = (rx_frame.data.u8[i] <<8) | rx_frame.data.u8[i + 1]; + datalayer.battery.status.cell_voltages_mV[voltage_index++] = voltage; + } + } + + //Frame has continued data - so request it + transmit_can(&BMWiX_6F4_CONTINUE_DATA, can_config.battery); + } + + if (rx_frame.DLC = 64 && rx_frame.data.u8[0] == 0xF4 && rx_frame.data.u8[1] == 0x21){ //Individual Cell Voltages - 1st Continue frame + int start_index = 2; //Data starts here + int voltage_index = 29; //Start cell ID + int num_voltages = 31; // number of voltage readings to get + for (int i = start_index; i < (start_index + num_voltages * 2); i += 2) { + uint16_t voltage = (rx_frame.data.u8[i] <<8) | rx_frame.data.u8[i + 1]; + datalayer.battery.status.cell_voltages_mV[voltage_index++] = voltage; + } + } + + if (rx_frame.DLC = 64 && rx_frame.data.u8[0] == 0xF4 && rx_frame.data.u8[1] == 0x22){ //Individual Cell Voltages - 2nd Continue frame + int start_index = 2; //Data starts here + int voltage_index = 60; //Start cell ID + int num_voltages = 31; // number of voltage readings to get + for (int i = start_index; i < (start_index + num_voltages * 2); i += 2) { + uint16_t voltage = (rx_frame.data.u8[i] <<8) | rx_frame.data.u8[i + 1]; + datalayer.battery.status.cell_voltages_mV[voltage_index++] = voltage; + } + } + + if (rx_frame.DLC = 64 && rx_frame.data.u8[0] == 0xF4 && rx_frame.data.u8[1] == 0x23){ //Individual Cell Voltages - 3rd Continue frame + int start_index = 2; //Data starts here + int voltage_index = 91; //Start cell ID + int num_voltages = 17; // number of voltage readings to get + for (int i = start_index; i < (start_index + num_voltages * 2); i += 2) { + uint16_t voltage = (rx_frame.data.u8[i] <<8) | rx_frame.data.u8[i + 1]; + datalayer.battery.status.cell_voltages_mV[voltage_index++] = voltage; + } + } + if (rx_frame.DLC = 7 && rx_frame.data.u8[4] == 0x4D) { //Main Battery Voltage (Pre Contactor) + battery_voltage = (rx_frame.data.u8[5] <<8 | rx_frame.data.u8[6])/10; + } + + if (rx_frame.DLC = 7 && rx_frame.data.u8[4] == 0x4A) { //Main Battery Voltage (After Contactor) + battery_voltage_after_contactor = (rx_frame.data.u8[5] <<8 | rx_frame.data.u8[6]); + } + + + if (rx_frame.DLC = 12 && rx_frame.data.u8[4] == 0xE5 && rx_frame.data.u8[5] == 0x61) { //Current amps 32bit signed MSB. dA . negative is discharge + battery_current = (int32_t)((rx_frame.data.u8[5] << 24) | + (rx_frame.data.u8[6] << 16) | + (rx_frame.data.u8[7] << 8) | + rx_frame.data.u8[8]); + } + + if (rx_frame.DLC = 7 && rx_frame.data.u8[4] == 0xE5 && rx_frame.data.u8[5] == 0xCE) { //Min/Avg/Max SOC% + min_soc_state = (rx_frame.data.u8[8] <<8 | rx_frame.data.u8[9]); + avg_soc_state = (rx_frame.data.u8[6] <<8 | rx_frame.data.u8[7]); + max_soc_state = (rx_frame.data.u8[10] <<8 |rx_frame.data.u8[11]); + } + + if (rx_frame.DLC = 12 && rx_frame.data.u8[4] == 0xE5 && rx_frame.data.u8[5] == 0xC7) { //Current and max capacity kWh. Stored in kWh as 0.01 scale with -50 bias + remaining_capacity = ((rx_frame.data.u8[6] <<8 | rx_frame.data.u8[7]) *1) -5000; + max_capacity = ((rx_frame.data.u8[8] <<8 | rx_frame.data.u8[9]) *1) -5000; //uint16 limits to 65 + #ifdef DEBUG_VIA_USB + Serial.print("Remaining Capacty: "); + Serial.println( remaining_capacity); + Serial.print("Max Capacty: "); + Serial.println(max_capacity); + #endif //DEBUG_VIA_USB + } + + if (rx_frame.DLC = 12 && rx_frame.data.u8[4] == 0xE5 && rx_frame.data.u8[5] == 0x53) { //Min and max cell voltage + min_cell_voltage = (rx_frame.data.u8[6] <<8 | rx_frame.data.u8[7]); + max_cell_voltage = (rx_frame.data.u8[8] <<8 | rx_frame.data.u8[9]); + } + + if (rx_frame.DLC = 16 && rx_frame.data.u8[4] == 0xDD && rx_frame.data.u8[5] == 0xC0) { //Battery Temperature + min_battery_temperature = (rx_frame.data.u8[6] <<8 | rx_frame.data.u8[7])/10; + avg_battery_temperature = (rx_frame.data.u8[10] <<8 | rx_frame.data.u8[11])/10; + max_battery_temperature = (rx_frame.data.u8[8] <<8 | rx_frame.data.u8[9])/10; + } + if (rx_frame.DLC = 7 && rx_frame.data.u8[4] == 0xA3) { //Main Contactor Temperature CHECK FINGERPRINT 2 LEVEL + main_contactor_temperature = (rx_frame.data.u8[5] <<8 | rx_frame.data.u8[6]); + + } + break; default: break; } @@ -105,10 +376,58 @@ void send_can_battery() { // Send 100ms CAN Message if (currentMillis - previousMillis100 >= INTERVAL_100_MS) { previousMillis100 = currentMillis; + + uds_req_id_counter = increment_uds_req_id_counter(uds_req_id_counter); + switch (uds_req_id_counter){ + case 0: + //BMWiX_6F4.data.u8[3] = 0x22; + BMWiX_6F4.data.u8[3] = 0xDD; + BMWiX_6F4.data.u8[4] = 0xC0; + transmit_can(&BMWiX_6F4, can_config.battery); + break; + case 1: + BMWiX_6F4.data.u8[3] = 0xE5; + BMWiX_6F4.data.u8[4] = 0xCE; + transmit_can(&BMWiX_6F4, can_config.battery); + break; + case 2: + BMWiX_6F4.data.u8[4] = 0xC7; + transmit_can(&BMWiX_6F4, can_config.battery); + break; + case 3: + BMWiX_6F4.data.u8[4] = 0x53; + transmit_can(&BMWiX_6F4, can_config.battery); + break; + case 4: + BMWiX_6F4.data.u8[4] = 0x4A; + transmit_can(&BMWiX_6F4, can_config.battery); + break; + case 5: + BMWiX_6F4.data.u8[4] = 0x4D; + transmit_can(&BMWiX_6F4, can_config.battery); + break; + case 6: + BMWiX_6F4.data.u8[4] = 0x61; + transmit_can(&BMWiX_6F4, can_config.battery); + break; + case 7: + BMWiX_6F4.data.u8[4] = 0x54; + transmit_can(&BMWiX_6F4, can_config.battery); + break; + } + + + //Send SME Keep alive values 100ms + transmit_can(&BMWiX_510, can_config.battery); + } // Send 200ms CAN Message if (currentMillis - previousMillis200 >= INTERVAL_200_MS) { previousMillis200 = currentMillis; + + //Send SME Keep alive values 200ms + BMWiX_0C0.data.u8[0] = increment_0C0_counter(BMWiX_0C0.data.u8[0]); //Keep Alive 1 + transmit_can(&BMWiX_0C0, can_config.battery); } // Send 500ms CAN Message if (currentMillis - previousMillis500 >= INTERVAL_500_MS) { @@ -122,17 +441,22 @@ void send_can_battery() { if (currentMillis - previousMillis1000 >= INTERVAL_1_S) { previousMillis1000 = currentMillis; + //Send SME Keep alive values 1000ms + transmit_can(&BMWiX_06D, can_config.battery); + transmit_can(&BMWiX_2F1, can_config.battery); + transmit_can(&BMWiX_439, can_config.battery); + switch (cmdState) { case SOC: - transmit_can(&BMW_6F1_CELL, can_config.battery); + transmit_can(&BMW_6F4_CELL, can_config.battery); cmdState = CELL_VOLTAGE_MINMAX; break; case CELL_VOLTAGE_MINMAX: - transmit_can(&BMW_6F1_SOH, can_config.battery); + transmit_can(&BMW_6F4_SOH, can_config.battery); cmdState = SOH; break; case SOH: - transmit_can(&BMW_6F1_CELL_VOLTAGE_AVG, can_config.battery); + transmit_can(&BMW_6F4_CELL_VOLTAGE_AVG, can_config.battery); cmdState = CELL_VOLTAGE_CELLNO; current_cell_polled = 0; @@ -150,7 +474,7 @@ void send_can_battery() { } break; case CELL_VOLTAGE_CELLNO_LAST: - transmit_can(&BMW_6F1_SOC, can_config.battery); + transmit_can(&BMW_6F4_SOC, can_config.battery); cmdState = SOC; break; } @@ -188,6 +512,17 @@ void setup_battery(void) { // Performs one time setup at startup pinMode(WUP_PIN, OUTPUT); digitalWrite(WUP_PIN, HIGH); // Wake up the battery + + //Wake Battery + //Send SME Keep alive values 100ms + transmit_can(&BMWiX_510, can_config.battery); + //Send SME Keep alive values 200ms + BMWiX_0C0.data.u8[0] = increment_0C0_counter(BMWiX_0C0.data.u8[0]); //Keep Alive 1 + transmit_can(&BMWiX_0C0, can_config.battery); + //Send SME Keep alive values 1000ms + transmit_can(&BMWiX_06D, can_config.battery); + transmit_can(&BMWiX_2F1, can_config.battery); + transmit_can(&BMWiX_439, can_config.battery); } #endif From 61616628551a0d5ef6af3c5afcf54ad78b8575ea Mon Sep 17 00:00:00 2001 From: wjcloudy <56305354+wjcloudy@users.noreply.github.com> Date: Sun, 27 Oct 2024 11:31:22 +0000 Subject: [PATCH 060/210] Update BMW-IX-BATTERY.cpp Fix max/available capacity - now shows correct values. --- Software/src/battery/BMW-IX-BATTERY.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Software/src/battery/BMW-IX-BATTERY.cpp b/Software/src/battery/BMW-IX-BATTERY.cpp index c13d0eb8..ae17575e 100644 --- a/Software/src/battery/BMW-IX-BATTERY.cpp +++ b/Software/src/battery/BMW-IX-BATTERY.cpp @@ -36,6 +36,7 @@ No vehicle log available, SME asks for: 0x16 (CCU) 0x91 (EME1) 0xAA (EME2) + 0x?? Suspect there is a drive mode flag somewhere - balancing might only be active in some modes SME Output: @@ -178,8 +179,8 @@ static int16_t battery_voltage_after_contactor = 0; static int16_t min_soc_state = 0; static int16_t avg_soc_state = 0; static int16_t max_soc_state = 0; -static int16_t remaining_capacity = 0; -static int16_t max_capacity = 0; +static int32_t remaining_capacity = 0; +static int32_t max_capacity = 0; static int16_t min_battery_temperature = 0; static int16_t avg_battery_temperature = 0; static int16_t max_battery_temperature = 0; @@ -251,7 +252,7 @@ void update_values_battery() { //This function maps all the values fetched via datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; - datalayer.battery.info.number_of_cells = 108; //Hardcoded for the SE26 battery until values found + datalayer.battery.info.number_of_cells = 108; //Hardcoded for the 108S SE27 battery. SE26 96S TODO } void receive_can_battery(CAN_frame rx_frame) { @@ -329,8 +330,8 @@ void receive_can_battery(CAN_frame rx_frame) { } if (rx_frame.DLC = 12 && rx_frame.data.u8[4] == 0xE5 && rx_frame.data.u8[5] == 0xC7) { //Current and max capacity kWh. Stored in kWh as 0.01 scale with -50 bias - remaining_capacity = ((rx_frame.data.u8[6] <<8 | rx_frame.data.u8[7]) *1) -5000; - max_capacity = ((rx_frame.data.u8[8] <<8 | rx_frame.data.u8[9]) *1) -5000; //uint16 limits to 65 + remaining_capacity = ((rx_frame.data.u8[6] <<8 | rx_frame.data.u8[7]) *10) -50000; + max_capacity = ((rx_frame.data.u8[8] <<8 | rx_frame.data.u8[9]) *10) -50000; #ifdef DEBUG_VIA_USB Serial.print("Remaining Capacty: "); Serial.println( remaining_capacity); From f3f10b1db13577df1f4808cc66f218853f2b69d9 Mon Sep 17 00:00:00 2001 From: wjcloudy <56305354+wjcloudy@users.noreply.github.com> Date: Sun, 27 Oct 2024 11:36:15 +0000 Subject: [PATCH 061/210] Removed some debugging (max/remain capacity fixed) --- Software/src/battery/BMW-IX-BATTERY.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Software/src/battery/BMW-IX-BATTERY.cpp b/Software/src/battery/BMW-IX-BATTERY.cpp index ae17575e..c7d59f41 100644 --- a/Software/src/battery/BMW-IX-BATTERY.cpp +++ b/Software/src/battery/BMW-IX-BATTERY.cpp @@ -332,12 +332,6 @@ void receive_can_battery(CAN_frame rx_frame) { if (rx_frame.DLC = 12 && rx_frame.data.u8[4] == 0xE5 && rx_frame.data.u8[5] == 0xC7) { //Current and max capacity kWh. Stored in kWh as 0.01 scale with -50 bias remaining_capacity = ((rx_frame.data.u8[6] <<8 | rx_frame.data.u8[7]) *10) -50000; max_capacity = ((rx_frame.data.u8[8] <<8 | rx_frame.data.u8[9]) *10) -50000; - #ifdef DEBUG_VIA_USB - Serial.print("Remaining Capacty: "); - Serial.println( remaining_capacity); - Serial.print("Max Capacty: "); - Serial.println(max_capacity); - #endif //DEBUG_VIA_USB } if (rx_frame.DLC = 12 && rx_frame.data.u8[4] == 0xE5 && rx_frame.data.u8[5] == 0x53) { //Min and max cell voltage From adfa9f13e7ba14474c8705678b2ad3ac277f3f43 Mon Sep 17 00:00:00 2001 From: wjcloudy <56305354+wjcloudy@users.noreply.github.com> Date: Sun, 27 Oct 2024 12:29:19 +0000 Subject: [PATCH 062/210] iX Advanced Info support addition --- Software/src/devboard/webserver/advanced_battery_html.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Software/src/devboard/webserver/advanced_battery_html.cpp b/Software/src/devboard/webserver/advanced_battery_html.cpp index e8001d0c..99ff2201 100644 --- a/Software/src/devboard/webserver/advanced_battery_html.cpp +++ b/Software/src/devboard/webserver/advanced_battery_html.cpp @@ -16,6 +16,11 @@ String advanced_battery_processor(const String& var) { // Start a new block with a specific background color content += "
"; + +#ifdef BMW_IX_BATTERY + content += "

T30 Terminal Voltage: " + String(datalayer_extended.bmwix.T30_Voltage) + "mV

"; +#endif //BMW_IX_BATTERY + #ifdef BMW_I3_BATTERY content += "

SOC raw: " + String(datalayer_extended.bmwi3.SOC_raw) + "

"; content += "

SOC dash: " + String(datalayer_extended.bmwi3.SOC_dash) + "

"; @@ -152,7 +157,7 @@ String advanced_battery_processor(const String& var) { #endif #if !defined(TESLA_BATTERY) && !defined(NISSAN_LEAF_BATTERY) && \ - !defined(BMW_I3_BATTERY) //Only the listed types have extra info + !defined(BMW_I3_BATTERY)&& !defined(BMW_IX_BATTERY) //Only the listed types have extra info content += "No extra information available for this battery type"; #endif From 4c15d35144b73393d4be5f05486916843795f0fe Mon Sep 17 00:00:00 2001 From: wjcloudy <56305354+wjcloudy@users.noreply.github.com> Date: Sun, 27 Oct 2024 12:29:53 +0000 Subject: [PATCH 063/210] Add iX advanced values support --- Software/src/datalayer/datalayer_extended.h | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Software/src/datalayer/datalayer_extended.h b/Software/src/datalayer/datalayer_extended.h index 96bd5e07..aecbed70 100644 --- a/Software/src/datalayer/datalayer_extended.h +++ b/Software/src/datalayer/datalayer_extended.h @@ -3,6 +3,15 @@ #include "../include.h" +typedef struct { + /** uint16_t */ + /** Terminal 30 - 12V SME Supply Voltage */ + uint16_t T30_Voltage = 0; + /** Status HVIL, 1 HVIL OK, 0 HVIL disconnected*/ + uint8_t HVIL_Status= 0; +} DATALAYER_INFO_BMWIX; + + typedef struct { /** uint16_t */ /** SOC% raw battery value. Might not always reach 100% */ @@ -118,6 +127,7 @@ typedef struct { class DataLayerExtended { public: + DATALAYER_INFO_BMWIX bmwix; DATALAYER_INFO_BMWI3 bmwi3; DATALAYER_INFO_TESLA tesla; DATALAYER_INFO_NISSAN_LEAF nissanleaf; From 8fbc4d829ea4f790c847e2d4d8681feee40ac1be Mon Sep 17 00:00:00 2001 From: wjcloudy <56305354+wjcloudy@users.noreply.github.com> Date: Sun, 27 Oct 2024 12:32:44 +0000 Subject: [PATCH 064/210] IX Advanced values support - Battery module Adds support for showing advanced values - T30 voltage (12v SME Supply) --- Software/src/battery/BMW-IX-BATTERY.cpp | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/Software/src/battery/BMW-IX-BATTERY.cpp b/Software/src/battery/BMW-IX-BATTERY.cpp index c7d59f41..0b0b5a69 100644 --- a/Software/src/battery/BMW-IX-BATTERY.cpp +++ b/Software/src/battery/BMW-IX-BATTERY.cpp @@ -33,7 +33,10 @@ Suspected Vehicle comms required: No vehicle log available, SME asks for: 0x125 (CCU) - 0x16 (CCU) + 0x16E (CCU) + 0x340 (CCU) + 0x4F8 (CCU) + 0x188 (CCU) 0x91 (EME1) 0xAA (EME2) 0x?? Suspect there is a drive mode flag somewhere - balancing might only be active in some modes @@ -175,6 +178,7 @@ static bool battery_awake = false; //iX Intermediate vars static int32_t battery_current = 0; static int16_t battery_voltage = 0; +static int16_t terminal30_12v_voltage = 0; static int16_t battery_voltage_after_contactor = 0; static int16_t min_soc_state = 0; static int16_t avg_soc_state = 0; @@ -197,7 +201,7 @@ static uint8_t current_cell_polled = 0; static uint8_t increment_uds_req_id_counter(uint8_t counter) { counter++; - if (counter > 7) { + if (counter > 8) { counter = 0; } return counter; @@ -253,6 +257,8 @@ void update_values_battery() { //This function maps all the values fetched via datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; datalayer.battery.info.number_of_cells = 108; //Hardcoded for the 108S SE27 battery. SE26 96S TODO + + datalayer_extended.bmwix.T30_Voltage = terminal30_12v_voltage; } void receive_can_battery(CAN_frame rx_frame) { @@ -346,7 +352,9 @@ void receive_can_battery(CAN_frame rx_frame) { } if (rx_frame.DLC = 7 && rx_frame.data.u8[4] == 0xA3) { //Main Contactor Temperature CHECK FINGERPRINT 2 LEVEL main_contactor_temperature = (rx_frame.data.u8[5] <<8 | rx_frame.data.u8[6]); - + } + if (rx_frame.DLC = 7 && rx_frame.data.u8[4] == 0xA7) { //Terminal 30 Voltage (12V SME supply) + terminal30_12v_voltage = (rx_frame.data.u8[5] <<8 | rx_frame.data.u8[6]); } break; default: @@ -409,6 +417,10 @@ void send_can_battery() { BMWiX_6F4.data.u8[4] = 0x54; transmit_can(&BMWiX_6F4, can_config.battery); break; + case 8: + BMWiX_6F4.data.u8[4] = 0xA7; //Terminal 30 12v voltage + transmit_can(&BMWiX_6F4, can_config.battery); + break; } From d67e9dcf99ce1f36d39b2531bac8a1ae47d3ffce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Sun, 27 Oct 2024 21:22:41 +0200 Subject: [PATCH 065/210] Add ifdef to Modbus server error message printing --- Software/src/lib/eModbus-eModbus/ModbusServerRTU.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Software/src/lib/eModbus-eModbus/ModbusServerRTU.cpp b/Software/src/lib/eModbus-eModbus/ModbusServerRTU.cpp index 88859d8d..ae66ce55 100644 --- a/Software/src/lib/eModbus-eModbus/ModbusServerRTU.cpp +++ b/Software/src/lib/eModbus-eModbus/ModbusServerRTU.cpp @@ -256,7 +256,9 @@ void ModbusServerRTU::serve(ModbusServerRTU *myServer) { if (request[0] != TIMEOUT) { // Any other error could be important for debugging, so print it ModbusError me((Error)request[0]); + #ifdef DEBUG_VIA_USB LOG_E("RTU receive: %02X - %s\n", (int)me, (const char *)me); + #endif //DEBUG_VIA_USB } } // Give scheduler room to breathe From dbfb0575a0d2be141b85baf45d6e7fc1d213dd81 Mon Sep 17 00:00:00 2001 From: amarofarinha <151563493+amarofarinha@users.noreply.github.com> Date: Mon, 28 Oct 2024 09:57:39 +0000 Subject: [PATCH 066/210] Enhance MQTT Naming Customization for Home Assistant --- Software/USER_SETTINGS.cpp | 10 ++++- Software/USER_SETTINGS.h | 1 + Software/src/devboard/mqtt/mqtt.cpp | 64 ++++++++++++++++++----------- Software/src/devboard/mqtt/mqtt.h | 3 ++ 4 files changed, 52 insertions(+), 26 deletions(-) diff --git a/Software/USER_SETTINGS.cpp b/Software/USER_SETTINGS.cpp index 3f50223a..dd9ea5a0 100644 --- a/Software/USER_SETTINGS.cpp +++ b/Software/USER_SETTINGS.cpp @@ -45,8 +45,14 @@ const char* http_password = "admin"; // password to webserver authentication; #ifdef MQTT const char* mqtt_user = "REDACTED"; // Set NULL for no username const char* mqtt_password = "REDACTED"; // Set NULL for no password -#endif // USE_MQTT -#endif // WIFI +#ifdef MQTT_MANUAL_TOPIC_OBJECT_NAME +const char* mqtt_topic_name = "BE"; // Custom MQTT topic name. Default: "battery-emulator_esp32-XXXXXX" +const char* mqtt_object_id_prefix = "esp32_"; // Custom prefix for MQTT object ID. Default: "esp32-XXXXXX_" +const char* mqtt_device_name = + "BatteryEmulator_esp32"; // Custom device name in Home Assistant. Default: "BatteryEmulator_esp32-XXXXXX" +#endif // MQTT_MANUAL_TOPIC_OBJECT_NAME +#endif // USE_MQTT +#endif // WIFI #ifdef EQUIPMENT_STOP_BUTTON // Equipment stop button behavior. Use NC button for safety reasons. diff --git a/Software/USER_SETTINGS.h b/Software/USER_SETTINGS.h index c5cbd65f..8c340f06 100644 --- a/Software/USER_SETTINGS.h +++ b/Software/USER_SETTINGS.h @@ -75,6 +75,7 @@ // #define MQTT // Enable this line to enable MQTT #define MQTT_SERVER "192.168.xxx.yyy" #define MQTT_PORT 1883 +#define MQTT_MANUAL_TOPIC_OBJECT_NAME // Enable this to use custom MQTT topic, object ID prefix, and device name. If not defined, the default naming format 'battery-emulator_esp32-XXXXXX' (based on hardware ID) will be used. See USER_SETTINGS.cpp for configuration options. /* Home Assistant options */ #define HA_AUTODISCOVERY // Enable this line to send Home Assistant autodiscovery messages. If not enabled manual configuration of Home Assitant is required diff --git a/Software/src/devboard/mqtt/mqtt.cpp b/Software/src/devboard/mqtt/mqtt.cpp index 69a55cc4..d87470bf 100644 --- a/Software/src/devboard/mqtt/mqtt.cpp +++ b/Software/src/devboard/mqtt/mqtt.cpp @@ -15,7 +15,10 @@ PubSubClient client(espClient); char mqtt_msg[MQTT_MSG_BUFFER_SIZE]; MyTimer publish_global_timer(5000); //publish timer MyTimer check_global_timer(800); // check timmer - low-priority MQTT checks, where responsiveness is not critical. -static const char* hostname = WiFi.getHostname(); + +static const char* topic_name = ""; +static const char* object_id_prefix = ""; +static const char* device_name = ""; // Tracking reconnection attempts and failures static unsigned long lastReconnectAttempt = 0; @@ -68,17 +71,16 @@ SensorConfig sensorConfigs[] = { }; -static String generateCommonInfoAutoConfigTopic(const char* object_id, const char* hostname) { - return String("homeassistant/sensor/battery-emulator_") + String(hostname) + "/" + String(object_id) + "/config"; +static String generateCommonInfoAutoConfigTopic(const char* object_id) { + return String("homeassistant/sensor/") + String(topic_name) + "/" + String(object_id) + "/config"; } -static String generateCellVoltageAutoConfigTopic(int cell_number, const char* hostname) { - return String("homeassistant/sensor/battery-emulator_") + String(hostname) + "/cell_voltage" + String(cell_number) + - "/config"; +static String generateCellVoltageAutoConfigTopic(int cell_number) { + return String("homeassistant/sensor/") + String(topic_name) + "/cell_voltage" + String(cell_number) + "/config"; } -static String generateEventsAutoConfigTopic(const char* object_id, const char* hostname) { - return String("homeassistant/sensor/battery-emulator_") + String(hostname) + "/" + String(object_id) + "/config"; +static String generateEventsAutoConfigTopic(const char* object_id) { + return String("homeassistant/sensor/") + String(topic_name) + "/" + String(object_id) + "/config"; } #endif // HA_AUTODISCOVERY @@ -90,7 +92,7 @@ static void publish_common_info(void) { #ifdef HA_AUTODISCOVERY static bool mqtt_first_transmission = true; #endif // HA_AUTODISCOVERY - static String state_topic = String("battery-emulator_") + String(hostname) + "/info"; + static String state_topic = String(topic_name) + "/info"; #ifdef HA_AUTODISCOVERY if (mqtt_first_transmission == true) { mqtt_first_transmission = false; @@ -98,8 +100,8 @@ static void publish_common_info(void) { SensorConfig& config = sensorConfigs[i]; doc["name"] = config.name; doc["state_topic"] = state_topic; - doc["unique_id"] = "battery-emulator_" + String(hostname) + "_" + String(config.object_id); - doc["object_id"] = String(hostname) + "_" + String(config.object_id); + doc["unique_id"] = String(topic_name) + "_" + String(config.object_id); + doc["object_id"] = String(object_id_prefix) + String(config.object_id); doc["value_template"] = config.value_template; if (config.unit != nullptr && strlen(config.unit) > 0) doc["unit_of_measurement"] = config.unit; @@ -112,12 +114,12 @@ static void publish_common_info(void) { doc["device"]["identifiers"][0] = "battery-emulator"; doc["device"]["manufacturer"] = "DalaTech"; doc["device"]["model"] = "BatteryEmulator"; - doc["device"]["name"] = "BatteryEmulator_" + String(hostname); + 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(generateCommonInfoAutoConfigTopic(config.object_id, hostname).c_str(), mqtt_msg, true); + mqtt_publish(generateCommonInfoAutoConfigTopic(config.object_id).c_str(), mqtt_msg, true); doc.clear(); } @@ -166,7 +168,7 @@ static void publish_cell_voltages(void) { static bool mqtt_first_transmission = true; #endif // HA_AUTODISCOVERY static JsonDocument doc; - static String state_topic = String("battery-emulator_") + String(hostname) + "/spec_data"; + static String state_topic = String(topic_name) + "/spec_data"; // If the cell voltage number isn't initialized... if (datalayer.battery.info.number_of_cells == 0u) { @@ -181,8 +183,7 @@ static void publish_cell_voltages(void) { int cellNumber = i + 1; doc["name"] = "Battery Cell Voltage " + String(cellNumber); doc["object_id"] = "battery_voltage_cell" + String(cellNumber); - doc["unique_id"] = "battery-emulator_" + String(hostname) + "_battery_voltage_cell" + - String(cellNumber); //"battery-emulator_" + String(hostname) + "_" + + doc["unique_id"] = String(topic_name) + "_battery_voltage_cell" + String(cellNumber); doc["device_class"] = "voltage"; doc["state_class"] = "measurement"; doc["state_topic"] = state_topic; @@ -193,13 +194,13 @@ static void publish_cell_voltages(void) { doc["device"]["identifiers"][0] = "battery-emulator"; doc["device"]["manufacturer"] = "DalaTech"; doc["device"]["model"] = "BatteryEmulator"; - doc["device"]["name"] = "BatteryEmulator_" + String(hostname); + 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, sizeof(mqtt_msg)); - mqtt_publish(generateCellVoltageAutoConfigTopic(cellNumber, hostname).c_str(), mqtt_msg, true); + mqtt_publish(generateCellVoltageAutoConfigTopic(cellNumber).c_str(), mqtt_msg, true); } doc.clear(); // clear after sending autoconfig } else { @@ -234,15 +235,15 @@ void publish_events() { #ifdef HA_AUTODISCOVERY static bool mqtt_first_transmission = true; #endif // HA_AUTODISCOVERY - static String state_topic = String("battery-emulator_") + String(hostname) + "/events"; + static String state_topic = String(topic_name) + "/events"; #ifdef HA_AUTODISCOVERY if (mqtt_first_transmission == true) { mqtt_first_transmission = false; doc["name"] = "Battery Emulator Event"; doc["state_topic"] = state_topic; - doc["unique_id"] = "battery-emulator_" + String(hostname) + "_event"; - doc["object_id"] = String(hostname) + "_event"; + doc["unique_id"] = String(topic_name) + "_event"; + doc["object_id"] = String(object_id_prefix) + "event"; doc["value_template"] = "{{ value_json.event_type ~ ' (c:' ~ value_json.count ~ ',m:' ~ value_json.millis ~ ') ' ~ value_json.message " "}}"; @@ -252,12 +253,12 @@ void publish_events() { doc["device"]["identifiers"][0] = "battery-emulator"; doc["device"]["manufacturer"] = "DalaTech"; doc["device"]["model"] = "BatteryEmulator"; - doc["device"]["name"] = "BatteryEmulator_" + String(hostname); + 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(generateEventsAutoConfigTopic("event", hostname).c_str(), mqtt_msg, true); + mqtt_publish(generateEventsAutoConfigTopic("event").c_str(), mqtt_msg, true); doc.clear(); } else { @@ -313,7 +314,7 @@ static bool reconnect() { Serial.print("Attempting MQTT connection... "); #endif // DEBUG_VIA_USB char clientId[64]; // Adjust the size as needed - snprintf(clientId, sizeof(clientId), "LilyGoClient-%s", hostname); + snprintf(clientId, sizeof(clientId), "LilyGoClient-%s", topic_name); // Attempt to connect if (client.connect(clientId, mqtt_user, mqtt_password)) { connected_once = true; @@ -339,6 +340,21 @@ static bool reconnect() { } void init_mqtt(void) { + +#ifdef MQTT +#ifdef MQTT_MANUAL_TOPIC_OBJECT_NAME + // Use custom topic name, object ID prefix, and device name from user settings + topic_name = mqtt_topic_name; + object_id_prefix = mqtt_object_id_prefix; + device_name = mqtt_device_name; +#else + // Use default naming based on WiFi hostname for topic, object ID prefix, and device name + topic_name = String("battery-emulator_") + WiFi.getHostname(); + object_id_prefix = WiFi.getHostname() + String("_"); + device_name = "BatteryEmulator_" + String(hostname); +#endif +#endif + client.setServer(MQTT_SERVER, MQTT_PORT); #ifdef DEBUG_VIA_USB Serial.println("MQTT initialized"); diff --git a/Software/src/devboard/mqtt/mqtt.h b/Software/src/devboard/mqtt/mqtt.h index b2494c63..91ddc0ea 100644 --- a/Software/src/devboard/mqtt/mqtt.h +++ b/Software/src/devboard/mqtt/mqtt.h @@ -44,6 +44,9 @@ extern const char* version_number; // The current software version, used for mq extern const char* mqtt_user; 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 char mqtt_msg[MQTT_MSG_BUFFER_SIZE]; From e328bfd37bfee24766c8aa22f698b9fdf29b6809 Mon Sep 17 00:00:00 2001 From: amarofarinha <151563493+amarofarinha@users.noreply.github.com> Date: Mon, 28 Oct 2024 13:24:17 +0000 Subject: [PATCH 067/210] compile fixes and MQTT clientId name --- Software/src/devboard/mqtt/mqtt.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Software/src/devboard/mqtt/mqtt.cpp b/Software/src/devboard/mqtt/mqtt.cpp index d87470bf..b01f08df 100644 --- a/Software/src/devboard/mqtt/mqtt.cpp +++ b/Software/src/devboard/mqtt/mqtt.cpp @@ -16,9 +16,9 @@ char mqtt_msg[MQTT_MSG_BUFFER_SIZE]; MyTimer publish_global_timer(5000); //publish timer MyTimer check_global_timer(800); // check timmer - low-priority MQTT checks, where responsiveness is not critical. -static const char* topic_name = ""; -static const char* object_id_prefix = ""; -static const char* device_name = ""; +static String topic_name = ""; +static String object_id_prefix = ""; +static String device_name = ""; // Tracking reconnection attempts and failures static unsigned long lastReconnectAttempt = 0; @@ -314,7 +314,7 @@ static bool reconnect() { Serial.print("Attempting MQTT connection... "); #endif // DEBUG_VIA_USB char clientId[64]; // Adjust the size as needed - snprintf(clientId, sizeof(clientId), "LilyGoClient-%s", topic_name); + snprintf(clientId, sizeof(clientId), "BatteryEmulatorClient-%s", WiFi.getHostname()); // Attempt to connect if (client.connect(clientId, mqtt_user, mqtt_password)) { connected_once = true; @@ -349,9 +349,10 @@ void init_mqtt(void) { device_name = mqtt_device_name; #else // Use default naming based on WiFi hostname for topic, object ID prefix, and device name - topic_name = String("battery-emulator_") + WiFi.getHostname(); - object_id_prefix = WiFi.getHostname() + String("_"); - device_name = "BatteryEmulator_" + String(hostname); + topic_name = String("battery-emulator_") + String(WiFi.getHostname()); + object_id_prefix = String(WiFi.getHostname()) + String("_"); + device_name = "BatteryEmulator_" + String(WiFi.getHostname()); + #endif #endif From 062e9b3c5c9650931add828b272e13da5ff98362 Mon Sep 17 00:00:00 2001 From: amarofarinha <151563493+amarofarinha@users.noreply.github.com> Date: Mon, 28 Oct 2024 13:27:41 +0000 Subject: [PATCH 068/210] pre-commit fix --- Software/src/devboard/mqtt/mqtt.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Software/src/devboard/mqtt/mqtt.cpp b/Software/src/devboard/mqtt/mqtt.cpp index b01f08df..0c56a0dd 100644 --- a/Software/src/devboard/mqtt/mqtt.cpp +++ b/Software/src/devboard/mqtt/mqtt.cpp @@ -16,9 +16,9 @@ char mqtt_msg[MQTT_MSG_BUFFER_SIZE]; MyTimer publish_global_timer(5000); //publish timer MyTimer check_global_timer(800); // check timmer - low-priority MQTT checks, where responsiveness is not critical. -static String topic_name = ""; -static String object_id_prefix = ""; -static String device_name = ""; +static String topic_name = ""; +static String object_id_prefix = ""; +static String device_name = ""; // Tracking reconnection attempts and failures static unsigned long lastReconnectAttempt = 0; @@ -352,7 +352,7 @@ void init_mqtt(void) { topic_name = String("battery-emulator_") + String(WiFi.getHostname()); object_id_prefix = String(WiFi.getHostname()) + String("_"); device_name = "BatteryEmulator_" + String(WiFi.getHostname()); - + #endif #endif From 93cd2083cf9abe2b13f3d8f2dcb23deb5f4cc317 Mon Sep 17 00:00:00 2001 From: amarofarinha <151563493+amarofarinha@users.noreply.github.com> Date: Mon, 28 Oct 2024 17:34:18 +0000 Subject: [PATCH 069/210] Remove all unnecessary string casts --- Software/src/devboard/mqtt/mqtt.cpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Software/src/devboard/mqtt/mqtt.cpp b/Software/src/devboard/mqtt/mqtt.cpp index 0c56a0dd..94467bfb 100644 --- a/Software/src/devboard/mqtt/mqtt.cpp +++ b/Software/src/devboard/mqtt/mqtt.cpp @@ -72,15 +72,15 @@ SensorConfig sensorConfigs[] = { }; static String generateCommonInfoAutoConfigTopic(const char* object_id) { - return String("homeassistant/sensor/") + String(topic_name) + "/" + String(object_id) + "/config"; + return "homeassistant/sensor/" + topic_name + "/" + String(object_id) + "/config"; } static String generateCellVoltageAutoConfigTopic(int cell_number) { - return String("homeassistant/sensor/") + String(topic_name) + "/cell_voltage" + String(cell_number) + "/config"; + return "homeassistant/sensor/" + topic_name + "/cell_voltage" + String(cell_number) + "/config"; } static String generateEventsAutoConfigTopic(const char* object_id) { - return String("homeassistant/sensor/") + String(topic_name) + "/" + String(object_id) + "/config"; + return "homeassistant/sensor/" + topic_name + "/" + String(object_id) + "/config"; } #endif // HA_AUTODISCOVERY @@ -92,7 +92,7 @@ static void publish_common_info(void) { #ifdef HA_AUTODISCOVERY static bool mqtt_first_transmission = true; #endif // HA_AUTODISCOVERY - static String state_topic = String(topic_name) + "/info"; + static String state_topic = topic_name + "/info"; #ifdef HA_AUTODISCOVERY if (mqtt_first_transmission == true) { mqtt_first_transmission = false; @@ -100,8 +100,8 @@ static void publish_common_info(void) { SensorConfig& config = sensorConfigs[i]; doc["name"] = config.name; doc["state_topic"] = state_topic; - doc["unique_id"] = String(topic_name) + "_" + String(config.object_id); - doc["object_id"] = String(object_id_prefix) + String(config.object_id); + doc["unique_id"] = topic_name + "_" + String(config.object_id); + doc["object_id"] = object_id_prefix + String(config.object_id); doc["value_template"] = config.value_template; if (config.unit != nullptr && strlen(config.unit) > 0) doc["unit_of_measurement"] = config.unit; @@ -168,7 +168,7 @@ static void publish_cell_voltages(void) { static bool mqtt_first_transmission = true; #endif // HA_AUTODISCOVERY static JsonDocument doc; - static String state_topic = String(topic_name) + "/spec_data"; + static String state_topic = topic_name + "/spec_data"; // If the cell voltage number isn't initialized... if (datalayer.battery.info.number_of_cells == 0u) { @@ -183,7 +183,7 @@ static void publish_cell_voltages(void) { int cellNumber = i + 1; doc["name"] = "Battery Cell Voltage " + String(cellNumber); doc["object_id"] = "battery_voltage_cell" + String(cellNumber); - doc["unique_id"] = String(topic_name) + "_battery_voltage_cell" + String(cellNumber); + doc["unique_id"] = topic_name + "_battery_voltage_cell" + String(cellNumber); doc["device_class"] = "voltage"; doc["state_class"] = "measurement"; doc["state_topic"] = state_topic; @@ -235,15 +235,15 @@ void publish_events() { #ifdef HA_AUTODISCOVERY static bool mqtt_first_transmission = true; #endif // HA_AUTODISCOVERY - static String state_topic = String(topic_name) + "/events"; + static String state_topic = topic_name + "/events"; #ifdef HA_AUTODISCOVERY if (mqtt_first_transmission == true) { mqtt_first_transmission = false; doc["name"] = "Battery Emulator Event"; doc["state_topic"] = state_topic; - doc["unique_id"] = String(topic_name) + "_event"; - doc["object_id"] = String(object_id_prefix) + "event"; + doc["unique_id"] = topic_name + "_event"; + doc["object_id"] = object_id_prefix + "event"; doc["value_template"] = "{{ value_json.event_type ~ ' (c:' ~ value_json.count ~ ',m:' ~ value_json.millis ~ ') ' ~ value_json.message " "}}"; @@ -349,7 +349,7 @@ void init_mqtt(void) { device_name = mqtt_device_name; #else // Use default naming based on WiFi hostname for topic, object ID prefix, and device name - topic_name = String("battery-emulator_") + String(WiFi.getHostname()); + topic_name = "battery-emulator_" + String(WiFi.getHostname()); object_id_prefix = String(WiFi.getHostname()) + String("_"); device_name = "BatteryEmulator_" + String(WiFi.getHostname()); From d4d6939476791103df9b5020ac309533547cdd0b Mon Sep 17 00:00:00 2001 From: amarofarinha <151563493+amarofarinha@users.noreply.github.com> Date: Mon, 28 Oct 2024 17:44:19 +0000 Subject: [PATCH 070/210] Clarified comments and modified object_id_prefix --- Software/USER_SETTINGS.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/Software/USER_SETTINGS.cpp b/Software/USER_SETTINGS.cpp index dd9ea5a0..4307f05c 100644 --- a/Software/USER_SETTINGS.cpp +++ b/Software/USER_SETTINGS.cpp @@ -46,13 +46,15 @@ const char* http_password = "admin"; // password to webserver authentication; const char* mqtt_user = "REDACTED"; // Set NULL for no username const char* mqtt_password = "REDACTED"; // Set NULL for no password #ifdef MQTT_MANUAL_TOPIC_OBJECT_NAME -const char* mqtt_topic_name = "BE"; // Custom MQTT topic name. Default: "battery-emulator_esp32-XXXXXX" -const char* mqtt_object_id_prefix = "esp32_"; // Custom prefix for MQTT object ID. Default: "esp32-XXXXXX_" +const char* mqtt_topic_name = + "BE"; // Custom MQTT topic name. Previously, the name was automatically set to "battery-emulator_esp32-XXXXXX" +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 = - "BatteryEmulator_esp32"; // Custom device name in Home Assistant. Default: "BatteryEmulator_esp32-XXXXXX" -#endif // MQTT_MANUAL_TOPIC_OBJECT_NAME -#endif // USE_MQTT -#endif // WIFI + "BatteryEmulator_esp32"; // 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 +#endif // WIFI #ifdef EQUIPMENT_STOP_BUTTON // Equipment stop button behavior. Use NC button for safety reasons. From 3996bc2c0314c0afbeb0a50c22a69688504af8ad Mon Sep 17 00:00:00 2001 From: amarofarinha <151563493+amarofarinha@users.noreply.github.com> Date: Mon, 28 Oct 2024 18:23:22 +0000 Subject: [PATCH 071/210] changed mqtt_device_name --- Software/USER_SETTINGS.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Software/USER_SETTINGS.cpp b/Software/USER_SETTINGS.cpp index 4307f05c..7e916e68 100644 --- a/Software/USER_SETTINGS.cpp +++ b/Software/USER_SETTINGS.cpp @@ -51,7 +51,7 @@ const char* mqtt_topic_name = 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 = - "BatteryEmulator_esp32"; // 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 #endif // USE_MQTT #endif // WIFI From 75f3d9e0e945bea31ab4c5a502eba50822e461c8 Mon Sep 17 00:00:00 2001 From: amarofarinha <151563493+amarofarinha@users.noreply.github.com> Date: Mon, 28 Oct 2024 19:14:25 +0000 Subject: [PATCH 072/210] Updated comments to highlight the potential breaking change in MQTT naming conventions --- Software/USER_SETTINGS.h | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Software/USER_SETTINGS.h b/Software/USER_SETTINGS.h index 8c340f06..327c90de 100644 --- a/Software/USER_SETTINGS.h +++ b/Software/USER_SETTINGS.h @@ -75,7 +75,13 @@ // #define MQTT // Enable this line to enable MQTT #define MQTT_SERVER "192.168.xxx.yyy" #define MQTT_PORT 1883 -#define MQTT_MANUAL_TOPIC_OBJECT_NAME // Enable this to use custom MQTT topic, object ID prefix, and device name. If not defined, the default naming format 'battery-emulator_esp32-XXXXXX' (based on hardware ID) will be used. See USER_SETTINGS.cpp for configuration options. +#define MQTT_MANUAL_TOPIC_OBJECT_NAME // Enable this to use custom MQTT topic, object ID prefix, and device name. \ + // WARNING: If this is not defined, the previous default naming format \ + // 'battery-emulator_esp32-XXXXXX' (based on hardware ID) will be used. \ + // This naming convention was in place until version 7.5.0. \ + // Users should check the version from which they are updating, as this change \ + // may break compatibility with previous versions of MQTT naming. \ + // Please refer to USER_SETTINGS.cpp for configuration options. /* Home Assistant options */ #define HA_AUTODISCOVERY // Enable this line to send Home Assistant autodiscovery messages. If not enabled manual configuration of Home Assitant is required From da5b543ab223434164cf5b1a83f06c3d8a55d239 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Mon, 28 Oct 2024 23:16:54 +0200 Subject: [PATCH 073/210] Add known polling requests --- .../src/battery/RENAULT-ZOE-GEN2-BATTERY.cpp | 279 ++++++++++++++++-- .../src/battery/RENAULT-ZOE-GEN2-BATTERY.h | 42 +++ 2 files changed, 300 insertions(+), 21 deletions(-) diff --git a/Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.cpp b/Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.cpp index e6d7a249..2178a634 100644 --- a/Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.cpp +++ b/Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.cpp @@ -4,20 +4,54 @@ #include "../devboard/utils/events.h" #include "RENAULT-ZOE-GEN2-BATTERY.h" -/* Information in this file is based of the OVMS V3 vehicle_renaultzoe.cpp component +/* Information in this file is based on: https://github.com/openvehicles/Open-Vehicle-Monitoring-System-3/blob/master/vehicle/OVMS.V3/components/vehicle_renaultzoe_ph2_obd/src/vehicle_renaultzoe_ph2_obd.cpp +https://github.com/ljames28/Renault-Zoe-PH2-ZE50-Canbus-LBC-Information?tab=readme-ov-file +https://github.com/fesch/CanZE/tree/master/app/src/main/assets/ZOE_Ph2 /* /* Do not change code below unless you are sure what you are doing */ -static uint16_t LB_SOC = 50; -static uint16_t LB_SOH = 99; -static int16_t LB_Average_Temperature = 0; -static uint32_t LB_Charge_Power_W = 0; -static int32_t LB_Current = 0; -static uint16_t LB_kWh_Remaining = 0; -static uint16_t LB_Cell_Max_Voltage = 3700; -static uint16_t LB_Cell_Min_Voltage = 3700; -static uint16_t LB_Battery_Voltage = 3700; +static uint16_t battery_soc = 0; +static uint16_t battery_usable_soc = 5000; +static uint16_t battery_soh = 10000; +static uint16_t battery_pack_voltage = 370; +static uint16_t battery_max_cell_voltage = 3700; +static uint16_t battery_min_cell_voltage = 3700; +static uint16_t battery_12v = 0; +static uint16_t battery_avg_temp = 0; +static uint16_t battery_min_temp = 0; +static uint16_t battery_max_temp = 0; +static uint16_t battery_max_power = 0; +static uint16_t battery_interlock = 0; +static uint16_t battery_kwh = 0; +static uint16_t battery_current = 0; +static uint16_t battery_current_offset = 0; +static uint16_t battery_max_generated = 0; +static uint16_t battery_max_available = 0; +static uint16_t battery_current_voltage = 0; +static uint16_t battery_charging_status = 0; +static uint16_t battery_remaining_charge = 0; +static uint16_t battery_balance_capacity_total = 0; +static uint16_t battery_balance_time_total = 0; +static uint16_t battery_balance_capacity_sleep = 0; +static uint16_t battery_balance_time_sleep = 0; +static uint16_t battery_balance_capacity_wake = 0; +static uint16_t battery_balance_time_wake = 0; +static uint16_t battery_bms_state = 0; +static uint16_t battery_balance_switches = 0; +static uint16_t battery_energy_complete = 0; +static uint16_t battery_energy_partial = 0; +static uint16_t battery_slave_failures = 0; +static uint16_t battery_mileage = 0; +static uint16_t battery_fan_speed = 0; +static uint16_t battery_fan_period = 0; +static uint16_t battery_fan_control = 0; +static uint16_t battery_fan_duty = 0; +static uint16_t battery_temporisation = 0; +static uint16_t battery_time = 0; +static uint16_t battery_pack_time = 0; +static uint16_t battery_soc_min = 0; +static uint16_t battery_soc_max = 0; CAN_frame ZOE_373 = {.FD = false, .ext_ID = false, @@ -28,18 +62,83 @@ CAN_frame ZOE_POLL_18DADBF1 = {.FD = false, .ext_ID = true, .DLC = 8, .ID = 0x18DADBF1, - .data = {0x03, 0x22, 0x90, 0x00, 0xff, 0xff, 0xff, 0xff}}; + .data = {0x03, 0x22, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00}}; +//NVROL Reset +CAN_frame ZOE_NVROL_1_18DADBF1 = {.FD = false, + .ext_ID = true, + .DLC = 8, + .ID = 0x18DADBF1, + .data = {0x02, 0x10, 0x03, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}}; +CAN_frame ZOE_NVROL_2_18DADBF1 = {.FD = false, + .ext_ID = true, + .DLC = 8, + .ID = 0x18DADBF1, + .data = {0x04, 0x31, 0x01, 0xB0, 0x09, 0x00, 0xAA, 0xAA}}; +//Enable temporisation before sleep +CAN_frame ZOE_SLEEP_1_18DADBF1 = {.FD = false, + .ext_ID = true, + .DLC = 8, + .ID = 0x18DADBF1, + .data = {0x02, 0x10, 0x03, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}}; +CAN_frame ZOE_SLEEP_2_18DADBF1 = {.FD = false, + .ext_ID = true, + .DLC = 8, + .ID = 0x18DADBF1, + .data = {0x04, 0x2E, 0x92, 0x81, 0x01, 0xAA, 0xAA, 0xAA}}; + +const uint16_t poll_commands[41] = {POLL_SOH, + POLL_PACK_VOLTAGE, + POLL_MAX_CELL_VOLTAGE, + POLL_MIN_CELL_VOLTAGE, + POLL_12V, + POLL_AVG_TEMP, + POLL_MIN_TEMP, + POLL_MAX_TEMP, + POLL_MAX_POWER, + POLL_INTERLOCK, + POLL_KWH, + POLL_CURRENT, + POLL_CURRENT_OFFSET, + POLL_MAX_GENERATED, + POLL_MAX_AVAILABLE, + POLL_CURRENT_VOLTAGE, + POLL_CHARGING_STATUS, + POLL_REMAINING_CHARGE, + POLL_BALANCE_CAPACITY_TOTAL, + POLL_BALANCE_TIME_TOTAL, + POLL_BALANCE_CAPACITY_SLEEP, + POLL_BALANCE_TIME_SLEEP, + POLL_BALANCE_CAPACITY_WAKE, + POLL_BALANCE_TIME_WAKE, + POLL_BMS_STATE, + POLL_BALANCE_SWITCHES, + POLL_ENERGY_COMPLETE, + POLL_ENERGY_PARTIAL, + POLL_SLAVE_FAILURES, + POLL_MILEAGE, + POLL_FAN_SPEED, + POLL_FAN_PERIOD, + POLL_FAN_CONTROL, + POLL_FAN_DUTY, + POLL_TEMPORISATION, + POLL_TIME, + POLL_PACK_TIME, + POLL_SOC_MIN, + POLL_SOC_MAX}; +static uint8_t poll_index = 0; +static uint16_t currentpoll = POLL_SOC; +static uint16_t reply_poll = 0; static unsigned long previousMillis200 = 0; // will store last time a 200ms CAN Message was sent void update_values_battery() { //This function maps all the values fetched via CAN to the correct parameters used for modbus - datalayer.battery.status.soh_pptt = (LB_SOH * 100); //Increase range from 99% -> 99.00% + datalayer.battery.status.soh_pptt = battery_soh; - datalayer.battery.status.real_soc = (LB_SOC * 10); //increase LB_SOC range from 0-100.0 -> 100.00 + datalayer.battery.status.real_soc = battery_usable_soc; - datalayer.battery.status.voltage_dV = LB_Battery_Voltage; + datalayer.battery.status.voltage_dV = battery_pack_voltage; - datalayer.battery.status.current_dA = LB_Current; + datalayer.battery.status.current_dA = battery_current; //Calculate the remaining Wh amount from SOC% and max Wh value. datalayer.battery.status.remaining_capacity_Wh = static_cast( @@ -51,13 +150,13 @@ void update_values_battery() { //This function maps all the values fetched via datalayer.battery.status.active_power_W; - datalayer.battery.status.temperature_min_dC; + datalayer.battery.status.temperature_min_dC = battery_min_temp; - datalayer.battery.status.temperature_max_dC; + datalayer.battery.status.temperature_max_dC = battery_max_temp; - datalayer.battery.status.cell_min_voltage_mV; + datalayer.battery.status.cell_min_voltage_mV = battery_min_cell_voltage; - datalayer.battery.status.cell_max_voltage_mV; + datalayer.battery.status.cell_max_voltage_mV = battery_max_cell_voltage; #ifdef DEBUG_VIA_USB @@ -67,7 +166,137 @@ void update_values_battery() { //This function maps all the values fetched via void receive_can_battery(CAN_frame rx_frame) { datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; switch (rx_frame.ID) { - case 0x18daf1db: // LBC Reply from active polling + case 0x18DAF1DB: // LBC Reply from active polling + //frame 2 & 3 contains + reply_poll = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]; + + switch (reply_poll) { + case POLL_SOC: + battery_soc = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + break; + case POLL_USABLE_SOC: + battery_usable_soc = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + break; + case POLL_SOH: + battery_soh = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + break; + case POLL_PACK_VOLTAGE: + battery_pack_voltage = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + break; + case POLL_MAX_CELL_VOLTAGE: + battery_max_cell_voltage = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + break; + case POLL_MIN_CELL_VOLTAGE: + battery_min_cell_voltage = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + break; + case POLL_12V: + battery_12v = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + break; + case POLL_AVG_TEMP: + battery_avg_temp = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + break; + case POLL_MIN_TEMP: + battery_min_temp = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + break; + case POLL_MAX_TEMP: + battery_max_temp = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + break; + case POLL_MAX_POWER: + battery_max_power = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + break; + case POLL_INTERLOCK: + battery_interlock = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + break; + case POLL_KWH: + battery_kwh = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + break; + case POLL_CURRENT: + battery_current = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + break; + case POLL_CURRENT_OFFSET: + battery_current_offset = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + break; + case POLL_MAX_GENERATED: + battery_max_generated = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + break; + case POLL_MAX_AVAILABLE: + battery_max_available = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + break; + case POLL_CURRENT_VOLTAGE: + battery_current_voltage = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + break; + case POLL_CHARGING_STATUS: + battery_charging_status = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + break; + case POLL_REMAINING_CHARGE: + battery_remaining_charge = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + break; + case POLL_BALANCE_CAPACITY_TOTAL: + battery_balance_capacity_total = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + break; + case POLL_BALANCE_TIME_TOTAL: + battery_balance_time_total = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + break; + case POLL_BALANCE_CAPACITY_SLEEP: + battery_balance_capacity_sleep = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + break; + case POLL_BALANCE_TIME_SLEEP: + battery_balance_time_sleep = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + break; + case POLL_BALANCE_CAPACITY_WAKE: + battery_balance_capacity_wake = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + break; + case POLL_BALANCE_TIME_WAKE: + battery_balance_time_wake = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + break; + case POLL_BMS_STATE: + battery_bms_state = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + break; + case POLL_BALANCE_SWITCHES: + battery_balance_switches = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + break; + case POLL_ENERGY_COMPLETE: + battery_energy_complete = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + break; + case POLL_ENERGY_PARTIAL: + battery_energy_partial = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + break; + case POLL_SLAVE_FAILURES: + battery_slave_failures = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + break; + case POLL_MILEAGE: + battery_mileage = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + break; + case POLL_FAN_SPEED: + battery_fan_speed = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + break; + case POLL_FAN_PERIOD: + battery_fan_period = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + break; + case POLL_FAN_CONTROL: + battery_fan_control = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + break; + case POLL_FAN_DUTY: + battery_fan_duty = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + break; + case POLL_TEMPORISATION: + battery_temporisation = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + break; + case POLL_TIME: + battery_time = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + break; + case POLL_PACK_TIME: + battery_pack_time = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + break; + case POLL_SOC_MIN: + battery_soc_min = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + break; + case POLL_SOC_MAX: + battery_soc_max = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + break; + default: // Unknown reply + break; + } break; default: break; @@ -83,8 +312,16 @@ void send_can_battery() { set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis200)); } previousMillis200 = currentMillis; - transmit_can(&ZOE_373, can_config.battery); + + // Update current poll from the array + currentpoll = poll_commands[poll_index]; + poll_index = (poll_index + 1) % 41; + + ZOE_POLL_18DADBF1.data.u8[2] = (uint8_t)((currentpoll & 0xFF00) >> 8); + ZOE_POLL_18DADBF1.data.u8[3] = (uint8_t)(currentpoll & 0x00FF); + transmit_can(&ZOE_POLL_18DADBF1, can_config.battery); + transmit_can(&ZOE_373, can_config.battery); } } diff --git a/Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.h b/Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.h index 03981d72..c75bb774 100644 --- a/Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.h +++ b/Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.h @@ -12,4 +12,46 @@ void setup_battery(void); void transmit_can(CAN_frame* tx_frame, int interface); +#define POLL_SOC 0x9001 +#define POLL_USABLE_SOC 0x9002 +#define POLL_SOH 0x9003 +#define POLL_PACK_VOLTAGE 0x9005 +#define POLL_MAX_CELL_VOLTAGE 0x9007 +#define POLL_MIN_CELL_VOLTAGE 0x9009 +#define POLL_12V 0x9011 +#define POLL_AVG_TEMP 0x9012 +#define POLL_MIN_TEMP 0x9013 +#define POLL_MAX_TEMP 0x9014 +#define POLL_MAX_POWER 0x9018 +#define POLL_INTERLOCK 0x901A +#define POLL_KWH 0x91C8 +#define POLL_CURRENT 0x925D +#define POLL_CURRENT_OFFSET 0x900C +#define POLL_MAX_GENERATED 0x900E +#define POLL_MAX_AVAILABLE 0x900F +#define POLL_CURRENT_VOLTAGE 0x9130 +#define POLL_CHARGING_STATUS 0x9019 +#define POLL_REMAINING_CHARGE 0xF45B +#define POLL_BALANCE_CAPACITY_TOTAL 0x924F +#define POLL_BALANCE_TIME_TOTAL 0x9250 +#define POLL_BALANCE_CAPACITY_SLEEP 0x9251 +#define POLL_BALANCE_TIME_SLEEP 0x9252 +#define POLL_BALANCE_CAPACITY_WAKE 0x9262 +#define POLL_BALANCE_TIME_WAKE 0x9263 +#define POLL_BMS_STATE 0x9259 +#define POLL_BALANCE_SWITCHES 0x912B +#define POLL_ENERGY_COMPLETE 0x9210 +#define POLL_ENERGY_PARTIAL 0x9215 +#define POLL_SLAVE_FAILURES 0x9129 +#define POLL_MILEAGE 0x91CF +#define POLL_FAN_SPEED 0x912E +#define POLL_FAN_PERIOD 0x91F4 +#define POLL_FAN_CONTROL 0x91C9 +#define POLL_FAN_DUTY 0x91F5 +#define POLL_TEMPORISATION 0x9281 +#define POLL_TIME 0x9261 +#define POLL_PACK_TIME 0x91C1 +#define POLL_SOC_MIN 0x91B9 +#define POLL_SOC_MAX 0x91BA + #endif From 89e64914a73d5c39d4755c19279ecfb3dc22e867 Mon Sep 17 00:00:00 2001 From: amarofarinha <151563493+amarofarinha@users.noreply.github.com> Date: Mon, 28 Oct 2024 22:27:41 +0000 Subject: [PATCH 074/210] Remove "Battery Emulator" Prefix from MQTT Sensor Names and Standardize Cell Object IDs --- Software/src/devboard/mqtt/mqtt.cpp | 44 ++++++++++++++--------------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/Software/src/devboard/mqtt/mqtt.cpp b/Software/src/devboard/mqtt/mqtt.cpp index 94467bfb..fe69f9ba 100644 --- a/Software/src/devboard/mqtt/mqtt.cpp +++ b/Software/src/devboard/mqtt/mqtt.cpp @@ -47,27 +47,25 @@ struct SensorConfig { }; SensorConfig sensorConfigs[] = { - {"SOC", "Battery Emulator SOC (scaled)", "{{ value_json.SOC }}", "%", "battery"}, - {"SOC_real", "Battery Emulator SOC (real)", "{{ value_json.SOC_real }}", "%", "battery"}, - {"state_of_health", "Battery Emulator State Of Health", "{{ value_json.state_of_health }}", "%", "battery"}, - {"temperature_min", "Battery Emulator Temperature Min", "{{ value_json.temperature_min }}", "°C", "temperature"}, - {"temperature_max", "Battery Emulator Temperature Max", "{{ value_json.temperature_max }}", "°C", "temperature"}, - {"stat_batt_power", "Battery Emulator Stat Batt Power", "{{ value_json.stat_batt_power }}", "W", "power"}, - {"battery_current", "Battery Emulator Battery Current", "{{ value_json.battery_current }}", "A", "current"}, - {"cell_max_voltage", "Battery Emulator Cell Max Voltage", "{{ value_json.cell_max_voltage }}", "V", "voltage"}, - {"cell_min_voltage", "Battery Emulator Cell Min Voltage", "{{ value_json.cell_min_voltage }}", "V", "voltage"}, - {"battery_voltage", "Battery Emulator Battery Voltage", "{{ value_json.battery_voltage }}", "V", "voltage"}, - {"total_capacity", "Battery Emulator Battery Total Capacity", "{{ value_json.total_capacity }}", "Wh", "energy"}, - {"remaining_capacity", "Battery Emulator Battery Remaining Capacity (scaled)", - "{{ value_json.remaining_capacity }}", "Wh", "energy"}, - {"remaining_capacity_real", "Battery Emulator Battery Remaining Capacity (real)", - "{{ value_json.remaining_capacity_real }}", "Wh", "energy"}, - {"max_discharge_power", "Battery Emulator Battery Max Discharge Power", "{{ value_json.max_discharge_power }}", "W", - "power"}, - {"max_charge_power", "Battery Emulator Battery Max Charge Power", "{{ value_json.max_charge_power }}", "W", - "power"}, - {"bms_status", "Battery Emulator BMS Status", "{{ value_json.bms_status }}", "", ""}, - {"pause_status", "Battery Emulator Pause Status", "{{ value_json.pause_status }}", "", ""}, + {"SOC", "SOC (scaled)", "{{ value_json.SOC }}", "%", "battery"}, + {"SOC_real", "SOC (real)", "{{ value_json.SOC_real }}", "%", "battery"}, + {"state_of_health", "State Of Health", "{{ value_json.state_of_health }}", "%", "battery"}, + {"temperature_min", "Temperature Min", "{{ value_json.temperature_min }}", "°C", "temperature"}, + {"temperature_max", "Temperature Max", "{{ value_json.temperature_max }}", "°C", "temperature"}, + {"stat_batt_power", "Stat Batt Power", "{{ value_json.stat_batt_power }}", "W", "power"}, + {"battery_current", "Battery Current", "{{ value_json.battery_current }}", "A", "current"}, + {"cell_max_voltage", "Cell Max Voltage", "{{ value_json.cell_max_voltage }}", "V", "voltage"}, + {"cell_min_voltage", "Cell Min Voltage", "{{ value_json.cell_min_voltage }}", "V", "voltage"}, + {"battery_voltage", "Battery Voltage", "{{ value_json.battery_voltage }}", "V", "voltage"}, + {"total_capacity", "Battery Total Capacity", "{{ value_json.total_capacity }}", "Wh", "energy"}, + {"remaining_capacity", "Battery Remaining Capacity (scaled)", "{{ value_json.remaining_capacity }}", "Wh", + "energy"}, + {"remaining_capacity_real", "Battery Remaining Capacity (real)", "{{ value_json.remaining_capacity_real }}", "Wh", + "energy"}, + {"max_discharge_power", "Battery Max Discharge Power", "{{ value_json.max_discharge_power }}", "W", "power"}, + {"max_charge_power", "Battery Max Charge Power", "{{ value_json.max_charge_power }}", "W", "power"}, + {"bms_status", "BMS Status", "{{ value_json.bms_status }}", "", ""}, + {"pause_status", "Pause Status", "{{ value_json.pause_status }}", "", ""}, }; @@ -182,7 +180,7 @@ static void publish_cell_voltages(void) { for (int i = 0; i < datalayer.battery.info.number_of_cells; i++) { int cellNumber = i + 1; doc["name"] = "Battery Cell Voltage " + String(cellNumber); - doc["object_id"] = "battery_voltage_cell" + String(cellNumber); + doc["object_id"] = object_id_prefix + "battery_voltage_cell" + String(cellNumber); doc["unique_id"] = topic_name + "_battery_voltage_cell" + String(cellNumber); doc["device_class"] = "voltage"; doc["state_class"] = "measurement"; @@ -240,7 +238,7 @@ void publish_events() { if (mqtt_first_transmission == true) { mqtt_first_transmission = false; - doc["name"] = "Battery Emulator Event"; + doc["name"] = "Event"; doc["state_topic"] = state_topic; doc["unique_id"] = topic_name + "_event"; doc["object_id"] = object_id_prefix + "event"; From 2d5c2b20ffe2e28bde9c3afc31262a9ca9155a85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Tue, 29 Oct 2024 14:04:21 +0200 Subject: [PATCH 075/210] Add capacity writing to BYD CAN --- Software/src/inverter/BYD-CAN.cpp | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/Software/src/inverter/BYD-CAN.cpp b/Software/src/inverter/BYD-CAN.cpp index e92348ee..82527469 100644 --- a/Software/src/inverter/BYD-CAN.cpp +++ b/Software/src/inverter/BYD-CAN.cpp @@ -84,12 +84,14 @@ static uint16_t charge_current = 0; static int16_t temperature_average = 0; static uint16_t inverter_voltage = 0; static uint16_t inverter_SOC = 0; +static uint16_t remaining_capacity_ah = 0; +static uint16_t fully_charged_capacity_ah = 0; static long inverter_timestamp = 0; static bool initialDataSent = 0; void update_values_can_inverter() { //This function maps all the values fetched from battery CAN to the correct CAN messages - //Calculate values + /* 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) / @@ -103,22 +105,30 @@ void update_values_can_inverter() { //This function maps all the values fetched //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 */ temperature_average = ((datalayer.battery.status.temperature_max_dC + datalayer.battery.status.temperature_min_dC) / 2); + /* Calculate capacity, Amp hours(Ah) = Watt hours (Wh) / Voltage (V)*/ + if (datalayer.battery.status.voltage_dV > 10) { // Only update value when we have voltage available to avoid div0 + remaining_capacity_ah = + ((datalayer.battery.status.reported_remaining_capacity_Wh / datalayer.battery.status.voltage_dV) * 100); + fully_charged_capacity_ah = + ((datalayer.battery.info.total_capacity_Wh / datalayer.battery.status.voltage_dV) * 100); + } + //Map values to CAN messages //Maxvoltage (eg 400.0V = 4000 , 16bits long) BYD_110.data.u8[0] = (datalayer.battery.info.max_design_voltage_dV >> 8); @@ -139,12 +149,12 @@ void update_values_can_inverter() { //This function maps all the values fetched //StateOfHealth (100.00%) BYD_150.data.u8[2] = (datalayer.battery.status.soh_pptt >> 8); BYD_150.data.u8[3] = (datalayer.battery.status.soh_pptt & 0x00FF); - //Maximum discharge power allowed (Unit: A+1) - BYD_150.data.u8[4] = (discharge_current >> 8); - BYD_150.data.u8[5] = (discharge_current & 0x00FF); - //Maximum charge power allowed (Unit: A+1) - BYD_150.data.u8[6] = (charge_current >> 8); - BYD_150.data.u8[7] = (charge_current & 0x00FF); + //Remaining capacity (Ah+1) + BYD_150.data.u8[4] = (remaining_capacity_ah >> 8); + BYD_150.data.u8[5] = (remaining_capacity_ah & 0x00FF); + //Fully charged capacity (Ah+1) + BYD_150.data.u8[6] = (fully_charged_capacity_ah >> 8); + BYD_150.data.u8[7] = (fully_charged_capacity_ah & 0x00FF); //Voltage (ex 370.0) BYD_1D0.data.u8[0] = (datalayer.battery.status.voltage_dV >> 8); From b8b99b54252c6b1fc9d5c31a44500c65701912a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Tue, 29 Oct 2024 20:15:00 +0200 Subject: [PATCH 076/210] Add More Battery info for ZoePH2 --- .../src/battery/RENAULT-ZOE-GEN2-BATTERY.cpp | 44 +++++++++++++++++ Software/src/datalayer/datalayer_extended.h | 47 ++++++++++++++++++ .../webserver/advanced_battery_html.cpp | 49 ++++++++++++++++++- 3 files changed, 139 insertions(+), 1 deletion(-) diff --git a/Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.cpp b/Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.cpp index 2178a634..e79fe175 100644 --- a/Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.cpp +++ b/Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.cpp @@ -1,6 +1,7 @@ #include "../include.h" #ifdef RENAULT_ZOE_GEN2_BATTERY #include "../datalayer/datalayer.h" +#include "../datalayer/datalayer_extended.h" //For "More battery info" webpage #include "../devboard/utils/events.h" #include "RENAULT-ZOE-GEN2-BATTERY.h" @@ -158,6 +159,49 @@ void update_values_battery() { //This function maps all the values fetched via datalayer.battery.status.cell_max_voltage_mV = battery_max_cell_voltage; + // Update webserver datalayer + datalayer_extended.zoePH2.battery_soc = battery_soc; + datalayer_extended.zoePH2.battery_usable_soc = battery_usable_soc; + datalayer_extended.zoePH2.battery_soh = battery_soh; + datalayer_extended.zoePH2.battery_pack_voltage = battery_pack_voltage; + datalayer_extended.zoePH2.battery_max_cell_voltage = battery_max_cell_voltage; + datalayer_extended.zoePH2.battery_min_cell_voltage = battery_min_cell_voltage; + datalayer_extended.zoePH2.battery_12v = battery_12v; + datalayer_extended.zoePH2.battery_avg_temp = battery_avg_temp; + datalayer_extended.zoePH2.battery_min_temp = battery_min_temp; + datalayer_extended.zoePH2.battery_max_temp = battery_max_temp; + datalayer_extended.zoePH2.battery_max_power = battery_max_power; + datalayer_extended.zoePH2.battery_interlock = battery_interlock; + datalayer_extended.zoePH2.battery_kwh = battery_kwh; + datalayer_extended.zoePH2.battery_current = battery_current; + datalayer_extended.zoePH2.battery_current_offset = battery_current_offset; + datalayer_extended.zoePH2.battery_max_generated = battery_max_generated; + datalayer_extended.zoePH2.battery_max_available = battery_max_available; + datalayer_extended.zoePH2.battery_current_voltage = battery_current_voltage; + datalayer_extended.zoePH2.battery_charging_status = battery_charging_status; + datalayer_extended.zoePH2.battery_remaining_charge = battery_remaining_charge; + datalayer_extended.zoePH2.battery_balance_capacity_total = battery_balance_capacity_total; + datalayer_extended.zoePH2.battery_balance_time_total = battery_balance_time_total; + datalayer_extended.zoePH2.battery_balance_capacity_sleep = battery_balance_capacity_sleep; + datalayer_extended.zoePH2.battery_balance_time_sleep = battery_balance_time_sleep; + datalayer_extended.zoePH2.battery_balance_capacity_wake = battery_balance_capacity_wake; + datalayer_extended.zoePH2.battery_balance_time_wake = battery_balance_time_wake; + datalayer_extended.zoePH2.battery_bms_state = battery_bms_state; + datalayer_extended.zoePH2.battery_balance_switches = battery_balance_switches; + datalayer_extended.zoePH2.battery_energy_complete = battery_energy_complete; + datalayer_extended.zoePH2.battery_energy_partial = battery_energy_partial; + datalayer_extended.zoePH2.battery_slave_failures = battery_slave_failures; + datalayer_extended.zoePH2.battery_mileage = battery_mileage; + datalayer_extended.zoePH2.battery_fan_speed = battery_fan_speed; + datalayer_extended.zoePH2.battery_fan_period = battery_fan_period; + datalayer_extended.zoePH2.battery_fan_control = battery_fan_control; + datalayer_extended.zoePH2.battery_fan_duty = battery_fan_duty; + datalayer_extended.zoePH2.battery_temporisation = battery_temporisation; + datalayer_extended.zoePH2.battery_time = battery_time; + datalayer_extended.zoePH2.battery_pack_time = battery_pack_time; + datalayer_extended.zoePH2.battery_soc_min = battery_soc_min; + datalayer_extended.zoePH2.battery_soc_max = battery_soc_max; + #ifdef DEBUG_VIA_USB #endif diff --git a/Software/src/datalayer/datalayer_extended.h b/Software/src/datalayer/datalayer_extended.h index 9cae0eb6..5798f17b 100644 --- a/Software/src/datalayer/datalayer_extended.h +++ b/Software/src/datalayer/datalayer_extended.h @@ -135,12 +135,59 @@ typedef struct { } DATALAYER_INFO_NISSAN_LEAF; +typedef struct { + /** uint16_t */ + /** Values WIP*/ + uint16_t battery_soc = 0; + uint16_t battery_usable_soc = 0; + uint16_t battery_soh = 0; + uint16_t battery_pack_voltage = 0; + uint16_t battery_max_cell_voltage = 0; + uint16_t battery_min_cell_voltage = 0; + uint16_t battery_12v = 0; + uint16_t battery_avg_temp = 0; + uint16_t battery_min_temp = 0; + uint16_t battery_max_temp = 0; + uint16_t battery_max_power = 0; + uint16_t battery_interlock = 0; + uint16_t battery_kwh = 0; + uint16_t battery_current = 0; + uint16_t battery_current_offset = 0; + uint16_t battery_max_generated = 0; + uint16_t battery_max_available = 0; + uint16_t battery_current_voltage = 0; + uint16_t battery_charging_status = 0; + uint16_t battery_remaining_charge = 0; + uint16_t battery_balance_capacity_total = 0; + uint16_t battery_balance_time_total = 0; + uint16_t battery_balance_capacity_sleep = 0; + uint16_t battery_balance_time_sleep = 0; + uint16_t battery_balance_capacity_wake = 0; + uint16_t battery_balance_time_wake = 0; + uint16_t battery_bms_state = 0; + uint16_t battery_balance_switches = 0; + uint16_t battery_energy_complete = 0; + uint16_t battery_energy_partial = 0; + uint16_t battery_slave_failures = 0; + uint16_t battery_mileage = 0; + uint16_t battery_fan_speed = 0; + uint16_t battery_fan_period = 0; + uint16_t battery_fan_control = 0; + uint16_t battery_fan_duty = 0; + uint16_t battery_temporisation = 0; + uint16_t battery_time = 0; + uint16_t battery_pack_time = 0; + uint16_t battery_soc_min = 0; + uint16_t battery_soc_max = 0; +} DATALAYER_INFO_ZOE_PH2; + class DataLayerExtended { public: DATALAYER_INFO_BMWI3 bmwi3; DATALAYER_INFO_BYDATTO3 bydAtto3; DATALAYER_INFO_TESLA tesla; DATALAYER_INFO_NISSAN_LEAF nissanleaf; + DATALAYER_INFO_ZOE_PH2 zoePH2; }; extern DataLayerExtended datalayer_extended; diff --git a/Software/src/devboard/webserver/advanced_battery_html.cpp b/Software/src/devboard/webserver/advanced_battery_html.cpp index 223b67a4..cb5476af 100644 --- a/Software/src/devboard/webserver/advanced_battery_html.cpp +++ b/Software/src/devboard/webserver/advanced_battery_html.cpp @@ -159,8 +159,55 @@ String advanced_battery_processor(const String& var) { content += "

Heating requested: " + String(datalayer_extended.nissanleaf.HeaterSendRequest) + "

"; #endif +#ifdef RENAULT_ZOE_GEN2_BATTERY + content += "

soc: " + String(datalayer_extended.zoePH2.battery_soc) + "

"; + content += "

usable soc: " + String(datalayer_extended.zoePH2.battery_usable_soc) + "

"; + content += "

soh: " + String(datalayer_extended.zoePH2.battery_soh) + "

"; + content += "

pack voltage: " + String(datalayer_extended.zoePH2.battery_pack_voltage) + "

"; + content += "

max cell voltage: " + String(datalayer_extended.zoePH2.battery_max_cell_voltage) + "

"; + content += "

min cell voltage: " + String(datalayer_extended.zoePH2.battery_min_cell_voltage) + "

"; + content += "

12v: " + String(datalayer_extended.zoePH2.battery_12v) + "

"; + content += "

avg temp: " + String(datalayer_extended.zoePH2.battery_avg_temp) + "

"; + content += "

min temp: " + String(datalayer_extended.zoePH2.battery_min_temp) + "

"; + content += "

max temp: " + String(datalayer_extended.zoePH2.battery_max_temp) + "

"; + content += "

max power: " + String(datalayer_extended.zoePH2.battery_max_power) + "

"; + content += "

interlock: " + String(datalayer_extended.zoePH2.battery_interlock) + "

"; + content += "

kwh: " + String(datalayer_extended.zoePH2.battery_kwh) + "

"; + content += "

current: " + String(datalayer_extended.zoePH2.battery_current) + "

"; + content += "

current offset: " + String(datalayer_extended.zoePH2.battery_current_offset) + "

"; + content += "

max generated: " + String(datalayer_extended.zoePH2.battery_max_generated) + "

"; + content += "

max available: " + String(datalayer_extended.zoePH2.battery_max_available) + "

"; + content += "

current voltage: " + String(datalayer_extended.zoePH2.battery_current_voltage) + "

"; + content += "

charging status: " + String(datalayer_extended.zoePH2.battery_charging_status) + "

"; + content += "

remaining charge: " + String(datalayer_extended.zoePH2.battery_remaining_charge) + "

"; + content += + "

balance capacity total: " + String(datalayer_extended.zoePH2.battery_balance_capacity_total) + "

"; + content += "

balance time total: " + String(datalayer_extended.zoePH2.battery_balance_time_total) + "

"; + content += + "

balance capacity sleep: " + String(datalayer_extended.zoePH2.battery_balance_capacity_sleep) + "

"; + content += "

balance time sleep: " + String(datalayer_extended.zoePH2.battery_balance_time_sleep) + "

"; + content += + "

balance capacity wake: " + String(datalayer_extended.zoePH2.battery_balance_capacity_wake) + "

"; + content += "

balance time wake: " + String(datalayer_extended.zoePH2.battery_balance_time_wake) + "

"; + content += "

bms state: " + String(datalayer_extended.zoePH2.battery_bms_state) + "

"; + content += "

balance switches: " + String(datalayer_extended.zoePH2.battery_balance_switches) + "

"; + content += "

energy complete: " + String(datalayer_extended.zoePH2.battery_energy_complete) + "

"; + content += "

energy partial: " + String(datalayer_extended.zoePH2.battery_energy_partial) + "

"; + content += "

slave failures: " + String(datalayer_extended.zoePH2.battery_slave_failures) + "

"; + content += "

mileage: " + String(datalayer_extended.zoePH2.battery_mileage) + "

"; + content += "

fan speed: " + String(datalayer_extended.zoePH2.battery_fan_speed) + "

"; + content += "

fan period: " + String(datalayer_extended.zoePH2.battery_fan_period) + "

"; + content += "

fan control: " + String(datalayer_extended.zoePH2.battery_fan_control) + "

"; + content += "

fan duty: " + String(datalayer_extended.zoePH2.battery_fan_duty) + "

"; + content += "

temporisation: " + String(datalayer_extended.zoePH2.battery_temporisation) + "

"; + content += "

time: " + String(datalayer_extended.zoePH2.battery_time) + "

"; + content += "

pack time: " + String(datalayer_extended.zoePH2.battery_pack_time) + "

"; + content += "

soc min: " + String(datalayer_extended.zoePH2.battery_soc_min) + "

"; + content += "

soc max: " + String(datalayer_extended.zoePH2.battery_soc_max) + "

"; +#endif //RENAULT_ZOE_GEN2_BATTERY + #if !defined(TESLA_BATTERY) && !defined(NISSAN_LEAF_BATTERY) && !defined(BMW_I3_BATTERY) && \ - !defined(BYD_ATTO_3_BATTERY) //Only the listed types have extra info + !defined(BYD_ATTO_3_BATTERY) && !defined(RENAULT_ZOE_GEN2_BATTERY) //Only the listed types have extra info content += "No extra information available for this battery type"; #endif From 774c544bca3093c47cb78b56f4932fab7ced9edc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Tue, 29 Oct 2024 20:38:24 +0200 Subject: [PATCH 077/210] Add contactor opening incase of FAULT --- Software/src/battery/TESLA-BATTERY.cpp | 33 ++++++++++++++++++-------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/Software/src/battery/TESLA-BATTERY.cpp b/Software/src/battery/TESLA-BATTERY.cpp index a35751d9..3eac62cc 100644 --- a/Software/src/battery/TESLA-BATTERY.cpp +++ b/Software/src/battery/TESLA-BATTERY.cpp @@ -22,7 +22,7 @@ CAN_frame TESLA_221_2 = { .DLC = 8, .ID = 0x221, .data = {0x61, 0x15, 0x01, 0x00, 0x00, 0x00, 0x20, 0xBA}}; //Contactor Frame 221 - hv_up_for_drive - +static uint16_t sendContactorClosingMessagesStill = 300; static uint32_t battery_total_discharge = 0; static uint32_t battery_total_charge = 0; static uint16_t battery_volts = 0; // V @@ -1003,7 +1003,7 @@ unsigned long lastSend118 = 0; int index_1CF = 0; int index_118 = 0; -#endif +#endif //defined(TESLA_MODEL_SX_BATTERY) || defined(EXP_TESLA_BMS_DIGITAL_HVIL) void send_can_battery() { /*From bielec: My fist 221 message, to close the contactors is 0x41, 0x11, 0x01, 0x00, 0x00, 0x00, 0x20, 0x96 and then, @@ -1014,13 +1014,13 @@ the first, for a few cycles, then stop all messages which causes the contactor unsigned long currentMillis = millis(); #if defined(TESLA_MODEL_SX_BATTERY) || defined(EXP_TESLA_BMS_DIGITAL_HVIL) - if (datalayer.system.status.inverter_allows_contactor_closing) { + if ((datalayer.system.status.inverter_allows_contactor_closing) && (datalayer.battery.status.bms_status != FAULT)) { if (currentMillis - lastSend1CF >= 10) { transmit_can(&can_msg_1CF[index_1CF], can_config.battery); #ifdef DOUBLE_BATTERY transmit_can(&can_msg_1CF[index_1CF], can_config.battery_double); -#endif +#endif // DOUBLE_BATTERY index_1CF = (index_1CF + 1) % 8; lastSend1CF = currentMillis; @@ -1030,7 +1030,7 @@ the first, for a few cycles, then stop all messages which causes the contactor transmit_can(&can_msg_118[index_118], can_config.battery); #ifdef DOUBLE_BATTERY transmit_can(&can_msg_1CF[index_1CF], can_config.battery_double); -#endif +#endif //DOUBLE_BATTERY index_118 = (index_118 + 1) % 16; lastSend118 = currentMillis; @@ -1039,7 +1039,7 @@ the first, for a few cycles, then stop all messages which causes the contactor index_1CF = 0; index_118 = 0; } -#endif +#endif //defined(TESLA_MODEL_SX_BATTERY) || defined(EXP_TESLA_BMS_DIGITAL_HVIL) //Send 30ms message if (currentMillis - previousMillis30 >= INTERVAL_30_MS) { @@ -1051,15 +1051,26 @@ the first, for a few cycles, then stop all messages which causes the contactor } previousMillis30 = currentMillis; - if (datalayer.system.status.inverter_allows_contactor_closing) { + if ((datalayer.system.status.inverter_allows_contactor_closing == true) && + (datalayer.battery.status.bms_status != FAULT)) { + sendContactorClosingMessagesStill = 300; transmit_can(&TESLA_221_1, can_config.battery); transmit_can(&TESLA_221_2, can_config.battery); #ifdef DOUBLE_BATTERY if (datalayer.system.status.battery2_allows_contactor_closing) { - transmit_can(&TESLA_221_1, can_config.battery_double); // CAN2 connected to battery 2 + transmit_can(&TESLA_221_1, can_config.battery_double); transmit_can(&TESLA_221_2, can_config.battery_double); } +#endif //DOUBLE_BATTERY + } else { // Faulted state, or inverter blocks contactor closing + if (sendContactorClosingMessagesStill > 0) { + transmit_can(&TESLA_221_1, can_config.battery); + sendContactorClosingMessagesStill--; + +#ifdef DOUBLE_BATTERY + transmit_can(&TESLA_221_1, can_config.battery_double); #endif //DOUBLE_BATTERY + } } } } @@ -1091,7 +1102,8 @@ void printFaultCodesIfActive() { } if (datalayer.system.status.inverter_allows_contactor_closing == false) { Serial.println( - "ERROR: Solar inverter does not allow for contactor closing. Check communication connection to the inverter OR " + "ERROR: Solar inverter does not allow for contactor closing. Check communication connection to the inverter " + "OR " "disable the inverter protocol to proceed with contactor closing"); } // Check each symbol and print debug information if its value is 1 @@ -1166,7 +1178,8 @@ void printFaultCodesIfActive_battery2() { } if (datalayer.system.status.inverter_allows_contactor_closing == false) { Serial.println( - "ERROR: Solar inverter does not allow for contactor closing. Check communication connection to the inverter OR " + "ERROR: Solar inverter does not allow for contactor closing. Check communication connection to the inverter " + "OR " "disable the inverter protocol to proceed with contactor closing"); } // Check each symbol and print debug information if its value is 1 From 1fa076573a3402a8dd4b00c16d4c2c2acb4bc35d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Tue, 29 Oct 2024 22:10:08 +0200 Subject: [PATCH 078/210] Add scaling for values --- .../src/battery/RENAULT-ZOE-GEN2-BATTERY.cpp | 31 +++++++++++-------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.cpp b/Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.cpp index e79fe175..59c45f9f 100644 --- a/Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.cpp +++ b/Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.cpp @@ -19,13 +19,13 @@ static uint16_t battery_pack_voltage = 370; static uint16_t battery_max_cell_voltage = 3700; static uint16_t battery_min_cell_voltage = 3700; static uint16_t battery_12v = 0; -static uint16_t battery_avg_temp = 0; -static uint16_t battery_min_temp = 0; -static uint16_t battery_max_temp = 0; +static uint16_t battery_avg_temp = 920; +static uint16_t battery_min_temp = 920; +static uint16_t battery_max_temp = 920; static uint16_t battery_max_power = 0; static uint16_t battery_interlock = 0; static uint16_t battery_kwh = 0; -static uint16_t battery_current = 0; +static uint16_t battery_current = 32640; static uint16_t battery_current_offset = 0; static uint16_t battery_max_generated = 0; static uint16_t battery_max_available = 0; @@ -135,29 +135,34 @@ static unsigned long previousMillis200 = 0; // will store last time a 200ms CAN void update_values_battery() { //This function maps all the values fetched via CAN to the correct parameters used for modbus datalayer.battery.status.soh_pptt = battery_soh; - datalayer.battery.status.real_soc = battery_usable_soc; + if (battery_soc >= 300) { + datalayer.battery.status.real_soc = battery_soc - 300; + } else { + datalayer.battery.status.real_soc = 0; + } datalayer.battery.status.voltage_dV = battery_pack_voltage; - datalayer.battery.status.current_dA = battery_current; + datalayer.battery.status.current_dA = ((battery_current - 32640) * 0.3125); //Calculate the remaining Wh amount from SOC% and max Wh value. datalayer.battery.status.remaining_capacity_Wh = static_cast( (static_cast(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh); - datalayer.battery.status.max_discharge_power_W; + datalayer.battery.status.max_discharge_power_W = battery_max_available * 10; - datalayer.battery.status.max_charge_power_W; + datalayer.battery.status.max_charge_power_W = battery_max_generated * 10; - datalayer.battery.status.active_power_W; + datalayer.battery.status.active_power_W = + (datalayer.battery.status.current_dA * (datalayer.battery.status.voltage_dV / 100)); - datalayer.battery.status.temperature_min_dC = battery_min_temp; + datalayer.battery.status.temperature_min_dC = ((battery_min_temp - 400) / 2); - datalayer.battery.status.temperature_max_dC = battery_max_temp; + datalayer.battery.status.temperature_max_dC = ((battery_max_temp - 400) / 2); - datalayer.battery.status.cell_min_voltage_mV = battery_min_cell_voltage; + datalayer.battery.status.cell_min_voltage_mV = (battery_min_cell_voltage * 0.976563); - datalayer.battery.status.cell_max_voltage_mV = battery_max_cell_voltage; + datalayer.battery.status.cell_max_voltage_mV = (battery_max_cell_voltage * 0.976563); // Update webserver datalayer datalayer_extended.zoePH2.battery_soc = battery_soc; From c10dea05e0b78e5bc2f2030954b8373caf9c9cda Mon Sep 17 00:00:00 2001 From: Kukumagi Date: Wed, 30 Oct 2024 11:43:28 +0200 Subject: [PATCH 079/210] Enable selecting CAN_FD crystal frequency in user settings --- Software/USER_SETTINGS.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Software/USER_SETTINGS.h b/Software/USER_SETTINGS.h index 327c90de..4fb50c77 100644 --- a/Software/USER_SETTINGS.h +++ b/Software/USER_SETTINGS.h @@ -57,6 +57,11 @@ //#define DUAL_CAN //Enable this line to activate an isolated secondary CAN Bus using add-on MCP2515 chip (Needed for some inverters / double battery) #define CRYSTAL_FREQUENCY_MHZ 8 //DUAL_CAN option, what is your MCP2515 add-on boards crystal frequency? //#define CAN_FD //Enable this line to activate an isolated secondary CAN-FD bus using add-on MCP2518FD chip / Native CANFD on Stark board +#ifdef CAN_FD // CAN_FD additional options if enabled +#define CAN_FD_CRYSTAL_FREQUENCY_MHZ \ + ACAN2517FDSettings:: \ + OSC_40MHz //CAN_FD option, what is your MCP2518 add-on boards crystal frequency? (Default OSC_40MHz) +#endif //#define USE_CANFD_INTERFACE_AS_CLASSIC_CAN // Enable this line if you intend to use the CANFD as normal CAN //#define SERIAL_LINK_RECEIVER //Enable this line to receive battery data over RS485 pins from another Lilygo (This LilyGo interfaces with inverter) //#define SERIAL_LINK_TRANSMITTER //Enable this line to send battery data over RS485 pins to another Lilygo (This LilyGo interfaces with battery) From 5dff800b63119c11e1462c84d36af67385d6c0e7 Mon Sep 17 00:00:00 2001 From: Kukumagi Date: Wed, 30 Oct 2024 11:43:40 +0200 Subject: [PATCH 080/210] Enable selecting CAN_FD crystal frequency in user settings --- Software/Software.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Software/Software.ino b/Software/Software.ino index 22dc38be..3bf4f64d 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -454,7 +454,7 @@ void init_CAN() { Serial.println("CAN FD add-on (ESP32+MCP2517) selected"); #endif SPI.begin(MCP2517_SCK, MCP2517_SDO, MCP2517_SDI); - ACAN2517FDSettings settings(ACAN2517FDSettings::OSC_40MHz, 500 * 1000, + ACAN2517FDSettings settings(CAN_FD_CRYSTAL_FREQUENCY_MHZ, 500 * 1000, DataBitRateFactor::x4); // Arbitration bit rate: 500 kbit/s, data bit rate: 2 Mbit/s #ifdef USE_CANFD_INTERFACE_AS_CLASSIC_CAN settings.mRequestedMode = ACAN2517FDSettings::Normal20B; // ListenOnly / Normal20B / NormalFD From 79ee6c896ab6513ec3b07cdf2dce260033243693 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Wed, 30 Oct 2024 18:37:45 +0200 Subject: [PATCH 081/210] Add cellpower BMS support --- Software/USER_SETTINGS.h | 3 +- Software/src/battery/BATTERIES.h | 4 + Software/src/battery/CELLPOWER-BMS.cpp | 298 ++++++++++++++++++ Software/src/battery/CELLPOWER-BMS.h | 19 ++ Software/src/datalayer/datalayer_extended.h | 67 ++++ .../webserver/advanced_battery_html.cpp | 137 +++++++- Software/src/devboard/webserver/webserver.cpp | 3 + 7 files changed, 529 insertions(+), 2 deletions(-) create mode 100644 Software/src/battery/CELLPOWER-BMS.cpp create mode 100644 Software/src/battery/CELLPOWER-BMS.h diff --git a/Software/USER_SETTINGS.h b/Software/USER_SETTINGS.h index 4fb50c77..3cd15f88 100644 --- a/Software/USER_SETTINGS.h +++ b/Software/USER_SETTINGS.h @@ -11,6 +11,7 @@ /* Select battery used */ //#define BMW_I3_BATTERY //#define BYD_ATTO_3_BATTERY +//#define CELLPOWER_BMS //#define CHADEMO_BATTERY //NOTE: inherently enables CONTACTOR_CONTROL below //#define IMIEV_CZERO_ION_BATTERY //#define JAGUAR_IPACE_BATTERY @@ -45,7 +46,7 @@ //#define SOLAX_CAN //Enable this line to emulate a "SolaX Triple Power LFP" over CAN bus /* Select hardware used for Battery-Emulator */ -#define HW_LILYGO +//#define HW_LILYGO //#define HW_STARK /* Other options */ diff --git a/Software/src/battery/BATTERIES.h b/Software/src/battery/BATTERIES.h index 8539c551..268386a0 100644 --- a/Software/src/battery/BATTERIES.h +++ b/Software/src/battery/BATTERIES.h @@ -10,6 +10,10 @@ #include "BYD-ATTO-3-BATTERY.h" #endif +#ifdef CELLPOWER_BMS +#include "CELLPOWER-BMS.h" +#endif + #ifdef CHADEMO_BATTERY #include "CHADEMO-BATTERY.h" #endif diff --git a/Software/src/battery/CELLPOWER-BMS.cpp b/Software/src/battery/CELLPOWER-BMS.cpp new file mode 100644 index 00000000..a337ec1d --- /dev/null +++ b/Software/src/battery/CELLPOWER-BMS.cpp @@ -0,0 +1,298 @@ +#include "../include.h" +#ifdef CELLPOWER_BMS +#include "../datalayer/datalayer.h" +#include "../datalayer/datalayer_extended.h" //For "More battery info" webpage +#include "../devboard/utils/events.h" +#include "CELLPOWER-BMS.h" + +/* Do not change code below unless you are sure what you are doing */ +static unsigned long previousMillis1s = 0; // will store last time a 1s CAN Message was sent + +//Actual content messages +// Optional add-on charger module. Might not be needed to send these towards the BMS to keep it happy. +CAN_frame CELLPOWER_18FF50E9 = {.FD = false, + .ext_ID = true, + .DLC = 5, + .ID = 0x18FF50E9, + .data = {0x00, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame CELLPOWER_18FF50E8 = {.FD = false, + .ext_ID = true, + .DLC = 5, + .ID = 0x18FF50E8, + .data = {0x00, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame CELLPOWER_18FF50E7 = {.FD = false, + .ext_ID = true, + .DLC = 5, + .ID = 0x18FF50E7, + .data = {0x00, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame CELLPOWER_18FF50E5 = {.FD = false, + .ext_ID = true, + .DLC = 5, + .ID = 0x18FF50E5, + .data = {0x00, 0x00, 0x00, 0x00, 0x00}}; + +static bool system_state_discharge = false; +static bool system_state_charge = false; +static bool system_state_cellbalancing = false; +static bool system_state_tricklecharge = false; +static bool system_state_idle = false; +static bool system_state_chargecompleted = false; +static bool system_state_maintenancecharge = false; +static bool IO_state_main_positive_relay = false; +static bool IO_state_main_negative_relay = false; +static bool IO_state_charge_enable = false; +static bool IO_state_precharge_relay = false; +static bool IO_state_discharge_enable = false; +static bool IO_state_IO_6 = false; +static bool IO_state_IO_7 = false; +static bool IO_state_IO_8 = false; +static bool error_Cell_overvoltage = false; +static bool error_Cell_undervoltage = false; +static bool error_Cell_end_of_life_voltage = false; +static bool error_Cell_voltage_misread = false; +static bool error_Cell_over_temperature = false; +static bool error_Cell_under_temperature = false; +static bool error_Cell_unmanaged = false; +static bool error_LMU_over_temperature = false; +static bool error_LMU_under_temperature = false; +static bool error_Temp_sensor_open_circuit = false; +static bool error_Temp_sensor_short_circuit = false; +static bool error_SUB_communication = false; +static bool error_LMU_communication = false; +static bool error_Over_current_IN = false; +static bool error_Over_current_OUT = false; +static bool error_Short_circuit = false; +static bool error_Leak_detected = false; +static bool error_Leak_detection_failed = false; +static bool error_Voltage_difference = false; +static bool error_BMCU_supply_over_voltage = false; +static bool error_BMCU_supply_under_voltage = false; +static bool error_Main_positive_contactor = false; +static bool error_Main_negative_contactor = false; +static bool error_Precharge_contactor = false; +static bool error_Midpack_contactor = false; +static bool error_Precharge_timeout = false; +static bool error_Emergency_connector_override = false; +static bool warning_High_cell_voltage = false; +static bool warning_Low_cell_voltage = false; +static bool warning_High_cell_temperature = false; +static bool warning_Low_cell_temperature = false; +static bool warning_High_LMU_temperature = false; +static bool warning_Low_LMU_temperature = false; +static bool warning_SUB_communication_interfered = false; +static bool warning_LMU_communication_interfered = false; +static bool warning_High_current_IN = false; +static bool warning_High_current_OUT = false; +static bool warning_Pack_resistance_difference = false; +static bool warning_High_pack_resistance = false; +static bool warning_Cell_resistance_difference = false; +static bool warning_High_cell_resistance = false; +static bool warning_High_BMCU_supply_voltage = false; +static bool warning_Low_BMCU_supply_voltage = false; +static bool warning_Low_SOC = false; +static bool warning_Balancing_required_OCV_model = false; +static bool warning_Charger_not_responding = false; +static uint16_t cell_voltage_max_mV = 3700; +static uint16_t cell_voltage_min_mV = 3700; +static int8_t pack_temperature_high_C = 0; +static int8_t pack_temperature_low_C = 0; +static uint16_t battery_pack_voltage_dV = 3700; +static int16_t battery_pack_current_dA = 0; +static uint8_t battery_SOH_percentage = 99; +static uint8_t battery_SOC_percentage = 50; +static uint16_t battery_remaining_dAh = 0; +static uint8_t cell_with_highest_voltage = 0; +static uint8_t cell_with_lowest_voltage = 0; +static uint16_t requested_charge_current_dA = 0; +static uint16_t average_charge_current_dA = 0; +static uint16_t actual_charge_current_dA = 0; +static bool requested_exceeding_average_current = 0; +static bool error_state = false; + +void update_values_battery() { + + /* Update values from CAN */ + + datalayer.battery.status.real_soc = battery_SOC_percentage * 100; + + datalayer.battery.status.remaining_capacity_Wh = static_cast( + (static_cast(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh); + + datalayer.battery.status.soh_pptt = battery_SOH_percentage * 100; + + datalayer.battery.status.voltage_dV = battery_pack_voltage_dV; + + datalayer.battery.status.current_dA = battery_pack_current_dA; + + datalayer.battery.status.active_power_W = //Power in watts, Negative = charging batt + ((datalayer.battery.status.voltage_dV * datalayer.battery.status.current_dA) / 100); + + datalayer.battery.status.max_charge_power_W = ((requested_charge_current_dA * battery_pack_voltage_dV) / 100); + + datalayer.battery.status.max_discharge_power_W = 5000; //TODO, is this available via CAN? + + datalayer.battery.status.temperature_min_dC = (int16_t)(pack_temperature_low_C * 10); + + datalayer.battery.status.temperature_max_dC = (int16_t)(pack_temperature_high_C * 10); + + datalayer.battery.status.cell_max_voltage_mV = cell_voltage_max_mV; + + datalayer.battery.status.cell_min_voltage_mV = cell_voltage_min_mV; + + /* Peform safety checks */ + if (system_state_chargecompleted) { + //TODO, shall we set max_charge_power_W to 0 incase this is true? + } + if (IO_state_charge_enable) { + //TODO, shall we react on this? + } + if (IO_state_discharge_enable) { + //TODO, shall we react on this? + } +} + +void receive_can_battery(CAN_frame rx_frame) { + + /* + // All CAN messages recieved will be logged via serial + Serial.print(millis()); // Example printout, time, ID, length, data: 7553 1DB 8 FF C0 B9 EA 0 0 2 5D + Serial.print(" "); + Serial.print(rx_frame.ID, HEX); + Serial.print(" "); + Serial.print(rx_frame.DLC); + Serial.print(" "); + for (int i = 0; i < rx_frame.DLC; ++i) { + Serial.print(rx_frame.data.u8[i], HEX); + Serial.print(" "); + } + Serial.println(""); + */ + switch (rx_frame.ID) { + case 0x1A4: //PDO1_TX - 200ms + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + cell_voltage_max_mV = (uint16_t)((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]); + cell_voltage_min_mV = (uint16_t)((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]); + pack_temperature_high_C = (int8_t)rx_frame.data.u8[4]; + pack_temperature_low_C = (int8_t)rx_frame.data.u8[5]; + system_state_discharge = (rx_frame.data.u8[6] & 0x01); + system_state_charge = ((rx_frame.data.u8[6] & 0x02) >> 1); + system_state_cellbalancing = ((rx_frame.data.u8[6] & 0x04) >> 2); + system_state_tricklecharge = ((rx_frame.data.u8[6] & 0x08) >> 3); + system_state_idle = ((rx_frame.data.u8[6] & 0x10) >> 4); + system_state_chargecompleted = ((rx_frame.data.u8[6] & 0x20) >> 5); + system_state_maintenancecharge = ((rx_frame.data.u8[6] & 0x40) >> 6); + IO_state_main_positive_relay = (rx_frame.data.u8[7] & 0x01); + IO_state_main_negative_relay = ((rx_frame.data.u8[7] & 0x02) >> 1); + IO_state_charge_enable = ((rx_frame.data.u8[7] & 0x04) >> 2); + IO_state_precharge_relay = ((rx_frame.data.u8[7] & 0x08) >> 3); + IO_state_discharge_enable = ((rx_frame.data.u8[7] & 0x10) >> 4); + IO_state_IO_6 = ((rx_frame.data.u8[7] & 0x20) >> 5); + IO_state_IO_7 = ((rx_frame.data.u8[7] & 0x40) >> 6); + IO_state_IO_8 = ((rx_frame.data.u8[7] & 0x80) >> 7); + break; + case 0x2A4: //PDO2_TX - 200ms + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + battery_pack_voltage_dV = (uint16_t)((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]); + battery_pack_current_dA = (int16_t)((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]); + battery_SOH_percentage = (uint8_t)rx_frame.data.u8[4]; + battery_SOC_percentage = (uint8_t)rx_frame.data.u8[5]; + battery_remaining_dAh = (uint16_t)((rx_frame.data.u8[7] << 8) | rx_frame.data.u8[6]); + break; + case 0x3A4: //PDO3_TX - 200ms + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + cell_with_highest_voltage = (uint8_t)rx_frame.data.u8[0]; + cell_with_lowest_voltage = (uint8_t)rx_frame.data.u8[1]; + break; + case 0x4A4: //PDO4_TX - 200ms + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + error_Cell_overvoltage = (rx_frame.data.u8[0] & 0x01); + error_Cell_undervoltage = ((rx_frame.data.u8[0] & 0x02) >> 1); + error_Cell_end_of_life_voltage = ((rx_frame.data.u8[0] & 0x04) >> 2); + error_Cell_voltage_misread = ((rx_frame.data.u8[0] & 0x08) >> 3); + error_Cell_over_temperature = ((rx_frame.data.u8[0] & 0x10) >> 4); + error_Cell_under_temperature = ((rx_frame.data.u8[0] & 0x20) >> 5); + error_Cell_unmanaged = ((rx_frame.data.u8[0] & 0x40) >> 6); + error_LMU_over_temperature = ((rx_frame.data.u8[0] & 0x80) >> 7); + error_LMU_under_temperature = (rx_frame.data.u8[1] & 0x01); + error_Temp_sensor_open_circuit = ((rx_frame.data.u8[1] & 0x02) >> 1); + error_Temp_sensor_short_circuit = ((rx_frame.data.u8[1] & 0x04) >> 2); + error_SUB_communication = ((rx_frame.data.u8[1] & 0x08) >> 3); + error_LMU_communication = ((rx_frame.data.u8[1] & 0x10) >> 4); + error_Over_current_IN = ((rx_frame.data.u8[1] & 0x20) >> 5); + error_Over_current_OUT = ((rx_frame.data.u8[1] & 0x40) >> 6); + error_Short_circuit = ((rx_frame.data.u8[1] & 0x80) >> 7); + error_Leak_detected = (rx_frame.data.u8[2] & 0x01); + error_Leak_detection_failed = ((rx_frame.data.u8[2] & 0x02) >> 1); + error_Voltage_difference = ((rx_frame.data.u8[2] & 0x04) >> 2); + error_BMCU_supply_over_voltage = ((rx_frame.data.u8[2] & 0x08) >> 3); + error_BMCU_supply_under_voltage = ((rx_frame.data.u8[2] & 0x10) >> 4); + error_Main_positive_contactor = ((rx_frame.data.u8[2] & 0x20) >> 5); + error_Main_negative_contactor = ((rx_frame.data.u8[2] & 0x40) >> 6); + error_Precharge_contactor = ((rx_frame.data.u8[2] & 0x80) >> 7); + error_Midpack_contactor = (rx_frame.data.u8[3] & 0x01); + error_Precharge_timeout = ((rx_frame.data.u8[3] & 0x02) >> 1); + error_Emergency_connector_override = ((rx_frame.data.u8[3] & 0x04) >> 2); + warning_High_cell_voltage = (rx_frame.data.u8[4] & 0x01); + warning_Low_cell_voltage = ((rx_frame.data.u8[4] & 0x02) >> 1); + warning_High_cell_temperature = ((rx_frame.data.u8[4] & 0x04) >> 2); + warning_Low_cell_temperature = ((rx_frame.data.u8[4] & 0x08) >> 3); + warning_High_LMU_temperature = ((rx_frame.data.u8[4] & 0x10) >> 4); + warning_Low_LMU_temperature = ((rx_frame.data.u8[4] & 0x20) >> 5); + warning_SUB_communication_interfered = ((rx_frame.data.u8[4] & 0x40) >> 6); + warning_LMU_communication_interfered = ((rx_frame.data.u8[4] & 0x80) >> 7); + warning_High_current_IN = (rx_frame.data.u8[5] & 0x01); + warning_High_current_OUT = ((rx_frame.data.u8[5] & 0x02) >> 1); + warning_Pack_resistance_difference = ((rx_frame.data.u8[5] & 0x04) >> 2); + warning_High_pack_resistance = ((rx_frame.data.u8[5] & 0x08) >> 3); + warning_Cell_resistance_difference = ((rx_frame.data.u8[5] & 0x10) >> 4); + warning_High_cell_resistance = ((rx_frame.data.u8[5] & 0x20) >> 5); + warning_High_BMCU_supply_voltage = ((rx_frame.data.u8[5] & 0x40) >> 6); + warning_Low_BMCU_supply_voltage = ((rx_frame.data.u8[5] & 0x80) >> 7); + warning_Low_SOC = (rx_frame.data.u8[6] & 0x01); + warning_Balancing_required_OCV_model = ((rx_frame.data.u8[6] & 0x02) >> 1); + warning_Charger_not_responding = ((rx_frame.data.u8[6] & 0x04) >> 2); + break; + case 0x7A4: //PDO7_TX - 200ms + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + requested_charge_current_dA = (uint16_t)((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]); + average_charge_current_dA = (uint16_t)((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]); + actual_charge_current_dA = (uint16_t)((rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4]); + requested_exceeding_average_current = (rx_frame.data.u8[6] & 0x01); + break; + case 0x7A5: //PDO7.1_TX - 200ms + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + error_state = (rx_frame.data.u8[0] & 0x01); + break; + default: + break; + } +} + +void send_can_battery() { + unsigned long currentMillis = millis(); + // Send 1s CAN Message + if (currentMillis - previousMillis1s >= INTERVAL_1_S) { + + previousMillis1s = currentMillis; + + /* + transmit_can(&CELLPOWER_18FF50E9, can_config.battery); + transmit_can(&CELLPOWER_18FF50E8, can_config.battery); + transmit_can(&CELLPOWER_18FF50E7, can_config.battery); + transmit_can(&CELLPOWER_18FF50E5, can_config.battery); + */ + } +} + +void setup_battery(void) { // Performs one time setup at startup +#ifdef DEBUG_VIA_USB + Serial.println("Cellpower BMS selected"); +#endif + + datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; + datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV; + datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV; + datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV; +} + +#endif // CELLPOWER_BMS diff --git a/Software/src/battery/CELLPOWER-BMS.h b/Software/src/battery/CELLPOWER-BMS.h new file mode 100644 index 00000000..e1956b74 --- /dev/null +++ b/Software/src/battery/CELLPOWER-BMS.h @@ -0,0 +1,19 @@ +#ifndef CELLPOWER_BMS_H +#define CELLPOWER_BMS_H +#include +#include "../include.h" + +/* Tweak these according to your battery build */ +#define MAX_PACK_VOLTAGE_DV 5000 //5000 = 500.0V +#define MIN_PACK_VOLTAGE_DV 1500 +#define MAX_CELL_VOLTAGE_MV 4250 //Battery is put into emergency stop if one cell goes over this value +#define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value + +/* Do not modify any rows below*/ +#define BATTERY_SELECTED +#define NATIVECAN_250KBPS + +void setup_battery(void); +void transmit_can(CAN_frame* tx_frame, int interface); + +#endif diff --git a/Software/src/datalayer/datalayer_extended.h b/Software/src/datalayer/datalayer_extended.h index 9cae0eb6..e1ed206c 100644 --- a/Software/src/datalayer/datalayer_extended.h +++ b/Software/src/datalayer/datalayer_extended.h @@ -64,6 +64,72 @@ typedef struct { } DATALAYER_INFO_BYDATTO3; +typedef struct { + /** bool */ + /** All values either True or false */ + bool system_state_discharge = false; + bool system_state_charge = false; + bool system_state_cellbalancing = false; + bool system_state_tricklecharge = false; + bool system_state_idle = false; + bool system_state_chargecompleted = false; + bool system_state_maintenancecharge = false; + bool IO_state_main_positive_relay = false; + bool IO_state_main_negative_relay = false; + bool IO_state_charge_enable = false; + bool IO_state_precharge_relay = false; + bool IO_state_discharge_enable = false; + bool IO_state_IO_6 = false; + bool IO_state_IO_7 = false; + bool IO_state_IO_8 = false; + bool error_Cell_overvoltage = false; + bool error_Cell_undervoltage = false; + bool error_Cell_end_of_life_voltage = false; + bool error_Cell_voltage_misread = false; + bool error_Cell_over_temperature = false; + bool error_Cell_under_temperature = false; + bool error_Cell_unmanaged = false; + bool error_LMU_over_temperature = false; + bool error_LMU_under_temperature = false; + bool error_Temp_sensor_open_circuit = false; + bool error_Temp_sensor_short_circuit = false; + bool error_SUB_communication = false; + bool error_LMU_communication = false; + bool error_Over_current_IN = false; + bool error_Over_current_OUT = false; + bool error_Short_circuit = false; + bool error_Leak_detected = false; + bool error_Leak_detection_failed = false; + bool error_Voltage_difference = false; + bool error_BMCU_supply_over_voltage = false; + bool error_BMCU_supply_under_voltage = false; + bool error_Main_positive_contactor = false; + bool error_Main_negative_contactor = false; + bool error_Precharge_contactor = false; + bool error_Midpack_contactor = false; + bool error_Precharge_timeout = false; + bool error_Emergency_connector_override = false; + bool warning_High_cell_voltage = false; + bool warning_Low_cell_voltage = false; + bool warning_High_cell_temperature = false; + bool warning_Low_cell_temperature = false; + bool warning_High_LMU_temperature = false; + bool warning_Low_LMU_temperature = false; + bool warning_SUB_communication_interfered = false; + bool warning_LMU_communication_interfered = false; + bool warning_High_current_IN = false; + bool warning_High_current_OUT = false; + bool warning_Pack_resistance_difference = false; + bool warning_High_pack_resistance = false; + bool warning_Cell_resistance_difference = false; + bool warning_High_cell_resistance = false; + bool warning_High_BMCU_supply_voltage = false; + bool warning_Low_BMCU_supply_voltage = false; + bool warning_Low_SOC = false; + bool warning_Balancing_required_OCV_model = false; + bool warning_Charger_not_responding = false; +} DATALAYER_INFO_CELLPOWER; + typedef struct { /** uint8_t */ /** Contactor status */ @@ -139,6 +205,7 @@ class DataLayerExtended { public: DATALAYER_INFO_BMWI3 bmwi3; DATALAYER_INFO_BYDATTO3 bydAtto3; + DATALAYER_INFO_CELLPOWER cellpower; DATALAYER_INFO_TESLA tesla; DATALAYER_INFO_NISSAN_LEAF nissanleaf; }; diff --git a/Software/src/devboard/webserver/advanced_battery_html.cpp b/Software/src/devboard/webserver/advanced_battery_html.cpp index 223b67a4..9718f61e 100644 --- a/Software/src/devboard/webserver/advanced_battery_html.cpp +++ b/Software/src/devboard/webserver/advanced_battery_html.cpp @@ -100,6 +100,141 @@ String advanced_battery_processor(const String& var) { #endif //BMW_I3_BATTERY +#ifdef CELLPOWER_BMS + static const char* falseTrue[2] = {"False", "True"}; + content += "

States:

"; + content += "

Discharge: " + String(falseTrue[datalayer_extended.cellpower.system_state_discharge]) + "

"; + content += "

Charge: " + String(falseTrue[datalayer_extended.cellpower.system_state_charge]) + "

"; + content += + "

Cellbalancing: " + String(falseTrue[datalayer_extended.cellpower.system_state_cellbalancing]) + "

"; + content += + "

Tricklecharging: " + String(falseTrue[datalayer_extended.cellpower.system_state_tricklecharge]) + "

"; + content += "

Idle: " + String(falseTrue[datalayer_extended.cellpower.system_state_idle]) + "

"; + content += "

Charge completed: " + String(falseTrue[datalayer_extended.cellpower.system_state_chargecompleted]) + + "

"; + content += + "

Maintenance charge: " + String(falseTrue[datalayer_extended.cellpower.system_state_maintenancecharge]) + + "

"; + content += "

IO:

"; + content += + "

Main positive relay: " + String(falseTrue[datalayer_extended.cellpower.IO_state_main_positive_relay]) + + "

"; + content += + "

Main negative relay: " + String(falseTrue[datalayer_extended.cellpower.IO_state_main_negative_relay]) + + "

"; + content += + "

Charge enabled: " + String(falseTrue[datalayer_extended.cellpower.IO_state_charge_enable]) + "

"; + content += + "

Precharge relay: " + String(falseTrue[datalayer_extended.cellpower.IO_state_precharge_relay]) + "

"; + content += + "

Discharge enable: " + String(falseTrue[datalayer_extended.cellpower.IO_state_discharge_enable]) + "

"; + content += "

IO 6: " + String(falseTrue[datalayer_extended.cellpower.IO_state_IO_6]) + "

"; + content += "

IO 7: " + String(falseTrue[datalayer_extended.cellpower.IO_state_IO_7]) + "

"; + content += "

IO 8: " + String(falseTrue[datalayer_extended.cellpower.IO_state_IO_8]) + "

"; + content += "

Errors:

"; + content += + "

Cell overvoltage: " + String(falseTrue[datalayer_extended.cellpower.error_Cell_overvoltage]) + "

"; + content += + "

Cell undervoltage: " + String(falseTrue[datalayer_extended.cellpower.error_Cell_undervoltage]) + "

"; + content += "

Cell end of life voltage: " + + String(falseTrue[datalayer_extended.cellpower.error_Cell_end_of_life_voltage]) + "

"; + content += + "

Cell voltage misread: " + String(falseTrue[datalayer_extended.cellpower.error_Cell_voltage_misread]) + + "

"; + content += + "

Cell over temperature: " + String(falseTrue[datalayer_extended.cellpower.error_Cell_over_temperature]) + + "

"; + content += + "

Cell under temperature: " + String(falseTrue[datalayer_extended.cellpower.error_Cell_under_temperature]) + + "

"; + content += "

Cell unmanaged: " + String(falseTrue[datalayer_extended.cellpower.error_Cell_unmanaged]) + "

"; + content += + "

LMU over temperature: " + String(falseTrue[datalayer_extended.cellpower.error_LMU_over_temperature]) + + "

"; + content += + "

LMU under temperature: " + String(falseTrue[datalayer_extended.cellpower.error_LMU_under_temperature]) + + "

"; + content += "

Temp sensor open circuit: " + + String(falseTrue[datalayer_extended.cellpower.error_Temp_sensor_open_circuit]) + "

"; + content += "

Temp sensor short circuit: " + + String(falseTrue[datalayer_extended.cellpower.error_Temp_sensor_short_circuit]) + "

"; + content += "

SUB comm: " + String(falseTrue[datalayer_extended.cellpower.error_SUB_communication]) + "

"; + content += "

LMU comm: " + String(falseTrue[datalayer_extended.cellpower.error_LMU_communication]) + "

"; + content += + "

Over current In: " + String(falseTrue[datalayer_extended.cellpower.error_Over_current_IN]) + "

"; + content += + "

Over current Out: " + String(falseTrue[datalayer_extended.cellpower.error_Over_current_OUT]) + "

"; + content += "

Short circuit: " + String(falseTrue[datalayer_extended.cellpower.error_Short_circuit]) + "

"; + content += "

Leak detected: " + String(falseTrue[datalayer_extended.cellpower.error_Leak_detected]) + "

"; + content += + "

Leak detection failed: " + String(falseTrue[datalayer_extended.cellpower.error_Leak_detection_failed]) + + "

"; + content += + "

Voltage diff: " + String(falseTrue[datalayer_extended.cellpower.error_Voltage_difference]) + "

"; + content += "

BMCU supply overvoltage: " + + String(falseTrue[datalayer_extended.cellpower.error_BMCU_supply_over_voltage]) + "

"; + content += "

BMCU supply undervoltage: " + + String(falseTrue[datalayer_extended.cellpower.error_BMCU_supply_under_voltage]) + "

"; + content += "

Main positive contactor: " + + String(falseTrue[datalayer_extended.cellpower.error_Main_positive_contactor]) + "

"; + content += "

Main negative contactor: " + + String(falseTrue[datalayer_extended.cellpower.error_Main_negative_contactor]) + "

"; + content += "

Precharge contactor: " + String(falseTrue[datalayer_extended.cellpower.error_Precharge_contactor]) + + "

"; + content += + "

Midpack contactor: " + String(falseTrue[datalayer_extended.cellpower.error_Midpack_contactor]) + "

"; + content += + "

Precharge timeout: " + String(falseTrue[datalayer_extended.cellpower.error_Precharge_timeout]) + "

"; + content += "

EMG connector override: " + + String(falseTrue[datalayer_extended.cellpower.error_Emergency_connector_override]) + "

"; + content += "

Warnings:

"; + content += + "

High cell voltage: " + String(falseTrue[datalayer_extended.cellpower.warning_High_cell_voltage]) + "

"; + content += + "

Low cell voltage: " + String(falseTrue[datalayer_extended.cellpower.warning_Low_cell_voltage]) + "

"; + content += + "

High cell temperature: " + String(falseTrue[datalayer_extended.cellpower.warning_High_cell_temperature]) + + "

"; + content += + "

Low cell temperature: " + String(falseTrue[datalayer_extended.cellpower.warning_Low_cell_temperature]) + + "

"; + content += + "

High LMU temperature: " + String(falseTrue[datalayer_extended.cellpower.warning_High_LMU_temperature]) + + "

"; + content += + "

Low LMU temperature: " + String(falseTrue[datalayer_extended.cellpower.warning_Low_LMU_temperature]) + + "

"; + content += + "

SUB comm interf: " + String(falseTrue[datalayer_extended.cellpower.warning_SUB_communication_interfered]) + + "

"; + content += + "

LMU comm interf: " + String(falseTrue[datalayer_extended.cellpower.warning_LMU_communication_interfered]) + + "

"; + content += + "

High current In: " + String(falseTrue[datalayer_extended.cellpower.warning_High_current_IN]) + "

"; + content += + "

High current Out: " + String(falseTrue[datalayer_extended.cellpower.warning_High_current_OUT]) + "

"; + content += "

Pack resistance diff: " + + String(falseTrue[datalayer_extended.cellpower.warning_Pack_resistance_difference]) + "

"; + content += + "

High pack resistance: " + String(falseTrue[datalayer_extended.cellpower.warning_High_pack_resistance]) + + "

"; + content += "

Cell resistance diff: " + + String(falseTrue[datalayer_extended.cellpower.warning_Cell_resistance_difference]) + "

"; + content += + "

High cell resistance: " + String(falseTrue[datalayer_extended.cellpower.warning_High_cell_resistance]) + + "

"; + content += "

High BMCU supply voltage: " + + String(falseTrue[datalayer_extended.cellpower.warning_High_BMCU_supply_voltage]) + "

"; + content += "

Low BMCU supply voltage: " + + String(falseTrue[datalayer_extended.cellpower.warning_Low_BMCU_supply_voltage]) + "

"; + content += "

Low SOC: " + String(falseTrue[datalayer_extended.cellpower.warning_Low_SOC]) + "

"; + content += "

Balancing required: " + + String(falseTrue[datalayer_extended.cellpower.warning_Balancing_required_OCV_model]) + "

"; + content += "

Charger not responding: " + + String(falseTrue[datalayer_extended.cellpower.warning_Charger_not_responding]) + "

"; +#endif //CELLPOWER_BMS + #ifdef BYD_ATTO_3_BATTERY content += "

SOC estimated: " + String(datalayer_extended.bydAtto3.SOC_estimated) + "

"; content += "

SOC highprec: " + String(datalayer_extended.bydAtto3.SOC_highprec) + "

"; @@ -160,7 +295,7 @@ String advanced_battery_processor(const String& var) { #endif #if !defined(TESLA_BATTERY) && !defined(NISSAN_LEAF_BATTERY) && !defined(BMW_I3_BATTERY) && \ - !defined(BYD_ATTO_3_BATTERY) //Only the listed types have extra info + !defined(BYD_ATTO_3_BATTERY) && !defined(CELLPOWER_BMS) //Only the listed types have extra info content += "No extra information available for this battery type"; #endif diff --git a/Software/src/devboard/webserver/webserver.cpp b/Software/src/devboard/webserver/webserver.cpp index f1b3230a..038c5d02 100644 --- a/Software/src/devboard/webserver/webserver.cpp +++ b/Software/src/devboard/webserver/webserver.cpp @@ -516,6 +516,9 @@ String processor(const String& var) { #ifdef BYD_ATTO_3_BATTERY content += "BYD Atto 3"; #endif // BYD_ATTO_3_BATTERY +#ifdef CELLPOWER_BMS + content += "Cellpower BMS"; +#endif // CELLPOWER_BMS #ifdef CHADEMO_BATTERY content += "Chademo V2X mode"; #endif // CHADEMO_BATTERY From 40bba6d2a3d829e06506361aad67d78ebb0397f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Wed, 30 Oct 2024 18:38:11 +0200 Subject: [PATCH 082/210] Add missing HW def --- Software/USER_SETTINGS.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Software/USER_SETTINGS.h b/Software/USER_SETTINGS.h index 3cd15f88..6e2d03d2 100644 --- a/Software/USER_SETTINGS.h +++ b/Software/USER_SETTINGS.h @@ -46,7 +46,7 @@ //#define SOLAX_CAN //Enable this line to emulate a "SolaX Triple Power LFP" over CAN bus /* Select hardware used for Battery-Emulator */ -//#define HW_LILYGO +#define HW_LILYGO //#define HW_STARK /* Other options */ From 32c23c8d95522d2ad1ca278584e92c770c100103 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Wed, 30 Oct 2024 18:43:09 +0200 Subject: [PATCH 083/210] Fix temperature scaling --- Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.cpp b/Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.cpp index 59c45f9f..0f3995f9 100644 --- a/Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.cpp +++ b/Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.cpp @@ -156,9 +156,9 @@ void update_values_battery() { //This function maps all the values fetched via datalayer.battery.status.active_power_W = (datalayer.battery.status.current_dA * (datalayer.battery.status.voltage_dV / 100)); - datalayer.battery.status.temperature_min_dC = ((battery_min_temp - 400) / 2); + datalayer.battery.status.temperature_min_dC = ((battery_min_temp - 640) * 0.625); - datalayer.battery.status.temperature_max_dC = ((battery_max_temp - 400) / 2); + datalayer.battery.status.temperature_max_dC = ((battery_max_temp - 640) * 0.625); datalayer.battery.status.cell_min_voltage_mV = (battery_min_cell_voltage * 0.976563); From f7839a0bb9e085e04a80091a82646dddda0a5c86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Wed, 30 Oct 2024 21:34:59 +0200 Subject: [PATCH 084/210] Add extended datalayer mappings --- Software/src/battery/CELLPOWER-BMS.cpp | 63 ++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/Software/src/battery/CELLPOWER-BMS.cpp b/Software/src/battery/CELLPOWER-BMS.cpp index a337ec1d..0020d082 100644 --- a/Software/src/battery/CELLPOWER-BMS.cpp +++ b/Software/src/battery/CELLPOWER-BMS.cpp @@ -139,6 +139,69 @@ void update_values_battery() { datalayer.battery.status.cell_min_voltage_mV = cell_voltage_min_mV; + /* Update webserver datalayer */ + datalayer_extended.cellpower.system_state_discharge = system_state_discharge; + datalayer_extended.cellpower.system_state_charge = system_state_charge; + datalayer_extended.cellpower.system_state_cellbalancing = system_state_cellbalancing; + datalayer_extended.cellpower.system_state_tricklecharge = system_state_tricklecharge; + datalayer_extended.cellpower.system_state_idle = system_state_idle; + datalayer_extended.cellpower.system_state_chargecompleted = system_state_chargecompleted; + datalayer_extended.cellpower.system_state_maintenancecharge = system_state_maintenancecharge; + datalayer_extended.cellpower.IO_state_main_positive_relay = IO_state_main_positive_relay; + datalayer_extended.cellpower.IO_state_main_negative_relay = IO_state_main_negative_relay; + datalayer_extended.cellpower.IO_state_charge_enable = IO_state_charge_enable; + datalayer_extended.cellpower.IO_state_precharge_relay = IO_state_precharge_relay; + datalayer_extended.cellpower.IO_state_discharge_enable = IO_state_discharge_enable; + datalayer_extended.cellpower.IO_state_IO_6 = IO_state_IO_6; + datalayer_extended.cellpower.IO_state_IO_7 = IO_state_IO_7; + datalayer_extended.cellpower.IO_state_IO_8 = IO_state_IO_8; + datalayer_extended.cellpower.error_Cell_overvoltage = error_Cell_overvoltage; + datalayer_extended.cellpower.error_Cell_undervoltage = error_Cell_undervoltage; + datalayer_extended.cellpower.error_Cell_end_of_life_voltage = error_Cell_end_of_life_voltage; + datalayer_extended.cellpower.error_Cell_voltage_misread = error_Cell_voltage_misread; + datalayer_extended.cellpower.error_Cell_over_temperature = error_Cell_over_temperature; + datalayer_extended.cellpower.error_Cell_under_temperature = error_Cell_under_temperature; + datalayer_extended.cellpower.error_Cell_unmanaged = error_Cell_unmanaged; + datalayer_extended.cellpower.error_LMU_over_temperature = error_LMU_over_temperature; + datalayer_extended.cellpower.error_LMU_under_temperature = error_LMU_under_temperature; + datalayer_extended.cellpower.error_Temp_sensor_open_circuit = error_Temp_sensor_open_circuit; + datalayer_extended.cellpower.error_Temp_sensor_short_circuit = error_Temp_sensor_short_circuit; + datalayer_extended.cellpower.error_SUB_communication = error_SUB_communication; + datalayer_extended.cellpower.error_LMU_communication = error_LMU_communication; + datalayer_extended.cellpower.error_Over_current_IN = error_Over_current_IN; + datalayer_extended.cellpower.error_Over_current_OUT = error_Over_current_OUT; + datalayer_extended.cellpower.error_Short_circuit = error_Short_circuit; + datalayer_extended.cellpower.error_Leak_detected = error_Leak_detected; + datalayer_extended.cellpower.error_Leak_detection_failed = error_Leak_detection_failed; + datalayer_extended.cellpower.error_Voltage_difference = error_Voltage_difference; + datalayer_extended.cellpower.error_BMCU_supply_over_voltage = error_BMCU_supply_over_voltage; + datalayer_extended.cellpower.error_BMCU_supply_under_voltage = error_BMCU_supply_under_voltage; + datalayer_extended.cellpower.error_Main_positive_contactor = error_Main_positive_contactor; + datalayer_extended.cellpower.error_Main_negative_contactor = error_Main_negative_contactor; + datalayer_extended.cellpower.error_Precharge_contactor = error_Precharge_contactor; + datalayer_extended.cellpower.error_Midpack_contactor = error_Midpack_contactor; + datalayer_extended.cellpower.error_Precharge_timeout = error_Precharge_timeout; + datalayer_extended.cellpower.error_Emergency_connector_override = error_Emergency_connector_override; + datalayer_extended.cellpower.warning_High_cell_voltage = warning_High_cell_voltage; + datalayer_extended.cellpower.warning_Low_cell_voltage = warning_Low_cell_voltage; + datalayer_extended.cellpower.warning_High_cell_temperature = warning_High_cell_temperature; + datalayer_extended.cellpower.warning_Low_cell_temperature = warning_Low_cell_temperature; + datalayer_extended.cellpower.warning_High_LMU_temperature = warning_High_LMU_temperature; + datalayer_extended.cellpower.warning_Low_LMU_temperature = warning_Low_LMU_temperature; + datalayer_extended.cellpower.warning_SUB_communication_interfered = warning_SUB_communication_interfered; + datalayer_extended.cellpower.warning_LMU_communication_interfered = warning_LMU_communication_interfered; + datalayer_extended.cellpower.warning_High_current_IN = warning_High_current_IN; + datalayer_extended.cellpower.warning_High_current_OUT = warning_High_current_OUT; + datalayer_extended.cellpower.warning_Pack_resistance_difference = warning_Pack_resistance_difference; + datalayer_extended.cellpower.warning_High_pack_resistance = warning_High_pack_resistance; + datalayer_extended.cellpower.warning_Cell_resistance_difference = warning_Cell_resistance_difference; + datalayer_extended.cellpower.warning_High_cell_resistance = warning_High_cell_resistance; + datalayer_extended.cellpower.warning_High_BMCU_supply_voltage = warning_High_BMCU_supply_voltage; + datalayer_extended.cellpower.warning_Low_BMCU_supply_voltage = warning_Low_BMCU_supply_voltage; + datalayer_extended.cellpower.warning_Low_SOC = warning_Low_SOC; + datalayer_extended.cellpower.warning_Balancing_required_OCV_model = warning_Balancing_required_OCV_model; + datalayer_extended.cellpower.warning_Charger_not_responding = warning_Charger_not_responding; + /* Peform safety checks */ if (system_state_chargecompleted) { //TODO, shall we set max_charge_power_W to 0 incase this is true? From a662c7bdcd2dd88061606b65e17cd821e553efee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Wed, 30 Oct 2024 22:30:51 +0200 Subject: [PATCH 085/210] Speed up value updating --- Software/Software.ino | 13 ++++++------- Software/src/datalayer/datalayer.h | 8 ++++---- Software/src/devboard/utils/types.h | 4 ++-- Software/src/devboard/webserver/webserver.cpp | 2 +- 4 files changed, 13 insertions(+), 14 deletions(-) diff --git a/Software/Software.ino b/Software/Software.ino index 3bf4f64d..b575dc86 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -56,7 +56,7 @@ Preferences settings; // Store user settings const char* version_number = "7.6.dev"; // Interval settings -uint16_t intervalUpdateValues = INTERVAL_5_S; // Interval at which to update inverter values / Modbus registers +uint16_t intervalUpdateValues = INTERVAL_1_S; // Interval at which to update inverter values / Modbus registers unsigned long previousMillis10ms = 50; unsigned long previousMillisUpdateVal = 0; @@ -283,9 +283,8 @@ void core_loop(void* task_time_us) { } END_TIME_MEASUREMENT_MAX(time_10ms, datalayer.system.status.time_10ms_us); - START_TIME_MEASUREMENT(time_5s); - if (millis() - previousMillisUpdateVal >= intervalUpdateValues) // Every 5s normally - { + START_TIME_MEASUREMENT(time_values); + if (millis() - previousMillisUpdateVal >= intervalUpdateValues) { previousMillisUpdateVal = millis(); // Order matters on the update_loop! update_values_battery(); // Fetch battery values #ifdef DOUBLE_BATTERY @@ -300,7 +299,7 @@ void core_loop(void* task_time_us) { set_event(EVENT_DUMMY_ERROR, (uint8_t)millis()); } } - END_TIME_MEASUREMENT_MAX(time_5s, datalayer.system.status.time_5s_us); + END_TIME_MEASUREMENT_MAX(time_values, datalayer.system.status.time_values_us); START_TIME_MEASUREMENT(cantx); // Output @@ -316,7 +315,7 @@ void core_loop(void* task_time_us) { // Record snapshots of task times datalayer.system.status.time_snap_comm_us = datalayer.system.status.time_comm_us; datalayer.system.status.time_snap_10ms_us = datalayer.system.status.time_10ms_us; - datalayer.system.status.time_snap_5s_us = datalayer.system.status.time_5s_us; + datalayer.system.status.time_snap_values_us = datalayer.system.status.time_values_us; datalayer.system.status.time_snap_cantx_us = datalayer.system.status.time_cantx_us; datalayer.system.status.time_snap_ota_us = datalayer.system.status.time_ota_us; } @@ -327,7 +326,7 @@ void core_loop(void* task_time_us) { datalayer.system.status.time_ota_us = 0; datalayer.system.status.time_comm_us = 0; datalayer.system.status.time_10ms_us = 0; - datalayer.system.status.time_5s_us = 0; + datalayer.system.status.time_values_us = 0; datalayer.system.status.time_cantx_us = 0; datalayer.system.status.core_task_10s_max_us = 0; } diff --git a/Software/src/datalayer/datalayer.h b/Software/src/datalayer/datalayer.h index 10d49743..d0b6e840 100644 --- a/Software/src/datalayer/datalayer.h +++ b/Software/src/datalayer/datalayer.h @@ -145,8 +145,8 @@ typedef struct { int64_t time_comm_us = 0; /** 10 ms function measurement variable */ int64_t time_10ms_us = 0; - /** 5 s function measurement variable */ - int64_t time_5s_us = 0; + /** Value update function measurement variable */ + int64_t time_values_us = 0; /** CAN TX function measurement variable */ int64_t time_cantx_us = 0; @@ -163,9 +163,9 @@ typedef struct { */ int64_t time_snap_10ms_us = 0; /** Function measurement snapshot variable. - * This will show the performance of the 5 s functionality of the core task when the total time reached a new worst case + * This will show the performance of the values functionality of the core task when the total time reached a new worst case */ - int64_t time_snap_5s_us = 0; + int64_t time_snap_values_us = 0; /** Function measurement snapshot variable. * This will show the performance of CAN TX when the total time reached a new worst case */ diff --git a/Software/src/devboard/utils/types.h b/Software/src/devboard/utils/types.h index af66f505..5af0845f 100644 --- a/Software/src/devboard/utils/types.h +++ b/Software/src/devboard/utils/types.h @@ -33,8 +33,8 @@ enum led_color { GREEN, YELLOW, RED, BLUE, RGB }; #define INTERVAL_200_MS_DELAYED 240 #define INTERVAL_500_MS_DELAYED 550 -#define CAN_STILL_ALIVE 12 -// Set by battery each time we get a CAN message. Decrements every 5seconds. When reaching 0, sets event +#define CAN_STILL_ALIVE 60 +// Set by battery each time we get a CAN message. Decrements every second. When reaching 0, sets event /* CAN Frame structure */ typedef struct { diff --git a/Software/src/devboard/webserver/webserver.cpp b/Software/src/devboard/webserver/webserver.cpp index f1b3230a..479b6a10 100644 --- a/Software/src/devboard/webserver/webserver.cpp +++ b/Software/src/devboard/webserver/webserver.cpp @@ -459,7 +459,7 @@ String processor(const String& var) { "

loop() task max load last 10 s: " + String(datalayer.system.status.loop_task_10s_max_us) + " us

"; content += "

Max load @ worst case execution of core task:

"; content += "

10ms function timing: " + String(datalayer.system.status.time_snap_10ms_us) + " us

"; - content += "

5s function timing: " + String(datalayer.system.status.time_snap_5s_us) + " us

"; + content += "

Values function timing: " + String(datalayer.system.status.time_snap_values_us) + " us

"; content += "

CAN/serial RX function timing: " + String(datalayer.system.status.time_snap_comm_us) + " us

"; content += "

CAN TX function timing: " + String(datalayer.system.status.time_snap_cantx_us) + " us

"; content += "

OTA function timing: " + String(datalayer.system.status.time_snap_ota_us) + " us

"; From 280ac3cbee51e6ac371c32b0a50048d2ec38507f Mon Sep 17 00:00:00 2001 From: wjcloudy <56305354+wjcloudy@users.noreply.github.com> Date: Wed, 30 Oct 2024 21:40:04 +0000 Subject: [PATCH 086/210] 0.2alpha version - Update BMW-IX-BATTERY.h --- Software/src/battery/BMW-IX-BATTERY.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Software/src/battery/BMW-IX-BATTERY.h b/Software/src/battery/BMW-IX-BATTERY.h index 8c2e24c9..46ea9820 100644 --- a/Software/src/battery/BMW-IX-BATTERY.h +++ b/Software/src/battery/BMW-IX-BATTERY.h @@ -5,8 +5,8 @@ #define BATTERY_SELECTED -#define WUP_PIN 25 -#define MAX_PACK_VOLTAGE_DV 5000 //5000 = 500.0V +//#define WUP_PIN 25 //Not used +#define MAX_PACK_VOLTAGE_DV 4600 //4600 = 460.0V #define MIN_PACK_VOLTAGE_DV 3000 #define MAX_CELL_DEVIATION_MV 500 #define MAX_CELL_VOLTAGE_MV 4250 //Battery is put into emergency stop if one cell goes over this value From 9ea8458bf7d4a14080695cf283e4c7455dc05166 Mon Sep 17 00:00:00 2001 From: wjcloudy <56305354+wjcloudy@users.noreply.github.com> Date: Wed, 30 Oct 2024 21:40:25 +0000 Subject: [PATCH 087/210] 0.2alpha version - Update BMW-IX-BATTERY.cpp --- Software/src/battery/BMW-IX-BATTERY.cpp | 491 +++++++++++++----------- 1 file changed, 264 insertions(+), 227 deletions(-) diff --git a/Software/src/battery/BMW-IX-BATTERY.cpp b/Software/src/battery/BMW-IX-BATTERY.cpp index 0b0b5a69..4f4627f8 100644 --- a/Software/src/battery/BMW-IX-BATTERY.cpp +++ b/Software/src/battery/BMW-IX-BATTERY.cpp @@ -23,27 +23,15 @@ static CmdState cmdState = SOC; /* Suspected Vehicle comms required: - 0x06D DLC? 1000ms - counters? 0x2F1 DLC? 1000ms during run : 0xFF, 0xFF, 0xFF, 0xFF, 0x9B, 0x00, 0xF3, 0xFF - at startup 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xF3, 0xFF. Suspect byte [4] is a counter 0x439 DLC4 1000ms STATIC 0x0C0 DLC2 200ms needs counter - 0x510 DLC8 100ms STATIC 40 10 40 00 6F DF 19 00 during run - Startup sends this once: 0x40 0x10 0x02 0x00 0x00 0x00 0x00 0x00 0x587 DLC8 appears at startup 0x78 0x07 0x00 0x00 0xFF 0xFF 0xFF 0xFF , 0x01 0x03 0x80 0xFF 0xFF 0xFF 0xFF 0xFF, 0x78 0x07 0x00 0x00 0xFF 0xFF 0xFF 0xFF, 0x06 0x00 0x00 0xFF 0xFF 0xFF 0xFF 0xFF, 0x01 0x03 0x82 0xFF 0xFF 0xFF 0xFF 0xFF, 0x01 0x03 0x80 0xFF 0xFF 0xFF 0xFF 0xFF -No vehicle log available, SME asks for: - 0x125 (CCU) - 0x16E (CCU) - 0x340 (CCU) - 0x4F8 (CCU) - 0x188 (CCU) - 0x91 (EME1) - 0xAA (EME2) - 0x?? Suspect there is a drive mode flag somewhere - balancing might only be active in some modes - SME Output: - 0x08F DLC48 10ms - Appears to have analog readings like volt/temp/current + 0x12B8D087 5000ms - Extended ID 0x1D2 DLC8 1000ms 0x20B DLC8 1000ms 0x2E2 DLC16 1000ms @@ -54,135 +42,134 @@ SME Output: 0x49C DLC8 1000ms 0x4A1 DLC8 1000ms 0x4BB DLC64 200ms - seems multplexed on [0] - 0x4D0 DLC64 1000ms - some slow/flickering values + 0x4D0 DLC64 1000ms - some slow/flickering values - possible change during fault + 0x510 DLC8 100ms STATIC 40 10 40 00 6F DF 19 00 during run - Startup sends this once: 0x40 0x10 0x02 0x00 0x00 0x00 0x00 0x00 0x607 UDS Response +No vehicle log available, SME asks for: + 0x125 (CCU) + 0x16E (CCU) + 0x340 (CCU) + 0x4F8 (CCU) + 0x188 (CCU) + 0x91 (EME1) + 0xAA (EME2) + 0x?? Suspect there is a drive mode flag somewhere - balancing might only be active in some modes -UDS Map: - 69 - service disconnect (1 = closed) - c7 - available energy - available energy charged - ce - min avg max SOC - 61 len12 = current sensor - 53 - min and max cell voltage - 4d- main battery voltage - 4a - after contactor voltage - a4 = charnging contactor temp - A3 - MAIN CONTACTOR VOLTAGE - f4 00 0a 62 DD = main battery temp - A7 - T30 12v voltage (SME input voltage) - B6 - T30C 12v voltage (12v pyro sensor) - 51 = release, switch contactors. 1 = control of switch contactors active - CD = charge contactors - - TX 07 03 22 E5 54 - UDS request cell voltages - RX F4 10 E3 62 E5 54 - 16bit cell voltages batch1 (29 cells) - TX 07 30 00 02 - UDS request continue data - RX F4 21 0F - 16bit cell voltages continue2 (31 cells) - RX F4 22 0F - 16bit cell voltages continue3 (31 cells) - RX F4 23 0F - 16bit cell voltages continue4 (17 cells) - - TX 07 03 22 E5 9A - UDS request cell SOC - RX F4 10 E3 62 E5 9A?? - 16bit cell SOC batch1 (29 cells) - - TX 07 03 22 E5 CA - UDS request cell temps - RX F4 10 81 62 E5 CA - 16bit cell temps batch1 - RX F4 21 81 62 E5 CA - 16bit cell temps continue2 - TX 07 30 00 02 - UDS request continue data +TODO +- Request batt serial number on F1 8C (already parsing RX) +- Check PWM required from ACSM +- Use voltage qualifier for extended data? +- More Balancing values +- Check for stale min/max values +- Prevent fault state on SME reset */ +//Vehicle CAN START +CAN_frame BMWiX_06D = {.FD = true, .ext_ID = false, .DLC = 8, .ID = 0x06D, + .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0xFF}}; // 1000ms BDC Output - [0] static [1,2][3,4] counter x2. 3,4 is 9 higher than 1,2 is needed? [5-7] static +CAN_frame BMWiX_0C0 = {.FD = true, .ext_ID = false, .DLC = 2, .ID = 0x0C0, + .data = {0xF0, 0x08}}; // Keep Alive 2 BDC>SME 200ms First byte cycles F0 > FE second byte 08 static - MINIMUM ID TO KEEP SME AWAKE -CAN_frame BMWiX_06D = {.FD = true, - .ext_ID = false, - .DLC = 8, - .ID = 0x06D, - .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0xFF}}; // 1000ms BDC Output - [0] static [1]-[4] counter x2? is needed? [5-7] static - +CAN_frame BMWiX_276 = {.FD = true, .ext_ID = false, .DLC = 8, .ID = 0x476, + .data = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC}}; // 5000ms BDC Output - Suspected keep alive Static CONFIRM NEEDED -CAN_frame BMWiX_2F1 = {.FD = true, - .ext_ID = false, - .DLC = 8, - .ID = 0x2F1, +CAN_frame BMWiX_2F1 = {.FD = true, .ext_ID = false, .DLC = 8, .ID = 0x2F1, .data = {0xFF, 0xFF, 0xFF, 0xFF, 0x9B, 0x00, 0xF3, 0xFF}}; // 1000ms BDC Output - Static values - varies at startup - - -CAN_frame BMWiX_439 = {.FD = true, - .ext_ID = false, - .DLC = 4, - .ID = 0x439, +CAN_frame BMWiX_439 = {.FD = true, .ext_ID = false, .DLC = 4, .ID = 0x439, .data = {0xFF, 0xBF, 0xFF, 0xFF}}; // 1000ms BDC Output - Static values +CAN_frame BMWiX_486 = {.FD = true, .ext_ID = false, .DLC = 48, .ID = 0x486, + .data = {0xFE, 0xFF , 0xFE , 0xFF , 0xFE , 0xFF , 0xFE , 0xFF , 0xFE , 0xFF , 0xFE , 0xFF , 0xFE , 0xFF , 0xFE , 0xFF , 0xFE , 0xFF , 0xFE , 0xFF , 0xFE , 0xFF , 0xFE , 0xFF , 0xFE , 0xFF , 0xFE , 0xFF , 0xFF , 0x7F , 0x33 , 0xFD , 0xFD , 0xFD , 0xFD , 0xC0 , 0x41 , 0xFF , 0xFF , 0xFF , 0xFF , 0xFF , 0xFF , 0xFF , 0xFF , 0xFF , 0xFF , 0xFF}}; // 1000ms BDC Output - Suspected keep alive Static CONFIRM NEEDED +CAN_frame BMWiX_49C = {.FD = true, .ext_ID = false, .DLC = 4, .ID = 0x49C, + .data = {0xD2, 0xF2, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}}; // 1000ms BDC Output - Suspected keep alive Static CONFIRM NEEDED -CAN_frame BMWiX_510 = {.FD = true, - .ext_ID = false, - .DLC = 8, - .ID = 0x510, +CAN_frame BMWiX_510 = {.FD = true, .ext_ID = false, .DLC = 8, .ID = 0x510, .data = {0x40, 0x10, 0x40, 0x00, 0x6F, 0xDF, 0x19, 0x00}}; // 100ms BDC Output - Static values +CAN_frame BMWiX_12B8D087 = {.FD = true, .ext_ID = true, .DLC = 2, .ID = 0x12B8D087, + .data = {0xFC, 0xFF}}; // 5000ms SME Output - Static values +//Vehicle CAN END + +//Request Data CAN START +CAN_frame BMWiX_6F4 = {.FD = true, .ext_ID = false, .DLC = 5, .ID = 0x6F4, .data = {0x07, 0x03, 0x22, 0xE5, 0xC7}}; // Generic UDS Request data from SME. byte 4 selects requested value +CAN_frame BMWiX_6F4_REQUEST_SLEEPMODE = {.FD = true, .ext_ID = false, .DLC = 4, .ID = 0x6F4, .data = {0x07, 0x02, 0x11, 0x04}}; // UDS Request Request BMS/SME goes to Sleep Mode +CAN_frame BMWiX_6F4_REQUEST_HARD_RESET = {.FD = true, .ext_ID = false, .DLC = 4, .ID = 0x6F4, .data = {0x07, 0x02, 0x11, 0x01}}; // UDS Request Hard reset of BMS/SME +CAN_frame BMWiX_6F4_REQUEST_CELL_TEMP = {.FD = true, .ext_ID = false, .DLC = 5, .ID = 0x6F4, .data = {0x07, 0x03, 0x22, 0xDD, 0xC0}}; // UDS Request Cell Temperatures +CAN_frame BMWiX_6F4_REQUEST_SOC = {.FD = true, .ext_ID = false, .DLC = 5, .ID = 0x6F4, .data = {0x07, 0x03, 0x22, 0xE5, 0xCE}}; // Min/Avg/Max SOC% +CAN_frame BMWiX_6F4_REQUEST_CAPACITY = {.FD = true, .ext_ID = false, .DLC = 5, .ID = 0x6F4, .data = {0x07, 0x03, 0x22, 0xE5, 0xC7}}; //Current and max capacity kWh. Stored in kWh as 0.01 scale with -50 bias +CAN_frame BMWiX_6F4_REQUEST_MINMAXCELLV = {.FD = true, .ext_ID = false, .DLC = 5, .ID = 0x6F4, .data = {0x07, 0x03, 0x22, 0xE5, 0x53}}; //Min and max cell voltage 10V = Qualifier Invalid +CAN_frame BMWiX_6F4_REQUEST_MAINVOLTAGE_POSTCONTACTOR = {.FD = true, .ext_ID = false, .DLC = 5, .ID = 0x6F4, .data = {0x07, 0x03, 0x22, 0xE5, 0x4A}}; //Main Battery Voltage (After Contactor) +CAN_frame BMWiX_6F4_REQUEST_MAINVOLTAGE_PRECONTACTOR = {.FD = true, .ext_ID = false, .DLC = 5, .ID = 0x6F4, .data = {0x07, 0x03, 0x22, 0xE5, 0x4D}}; //Main Battery Voltage (Pre Contactor) +CAN_frame BMWiX_6F4_REQUEST_BATTERYCURRENT = {.FD = true, .ext_ID = false, .DLC = 5, .ID = 0x6F4, .data = {0x07, 0x03, 0x22, 0xE5, 0x61}}; //Current amps 32bit signed MSB. dA . negative is discharge +CAN_frame BMWiX_6F4_REQUEST_CELL_VOLTAGE = {.FD = true, .ext_ID = false, .DLC = 5, .ID = 0x6F4, .data = {0x07, 0x03, 0x22, 0xE5, 0x54}}; //MultiFrameIndividual Cell Voltages +CAN_frame BMWiX_6F4_REQUEST_T30VOLTAGE = {.FD = true, .ext_ID = false, .DLC = 5, .ID = 0x6F4, .data = {0x07, 0x03, 0x22, 0xE5, 0xA7}}; //Terminal 30 Voltage (12V SME supply) +CAN_frame BMWiX_6F4_REQUEST_EOL_ISO = {.FD = true, .ext_ID = false, .DLC = 5, .ID = 0x6F4, .data = {0x07, 0x03, 0x22, 0xA8, 0x60}}; //Request EOL Reading including ISO +CAN_frame BMWiX_6F4_REQUEST_SOH = {.FD = true, .ext_ID = false, .DLC = 5, .ID = 0x6F4, .data = {0x07, 0x03, 0x22, 0xE5, 0x45}}; //SOH Max Min Mean Request +CAN_frame BMWiX_6F4_REQUEST_DATASUMMARY = {.FD = true, .ext_ID = false, .DLC = 5, .ID = 0x6F4, .data = {0x07, 0x03, 0x22, 0xE5, 0x45}}; //MultiFrame Summary Request, includes SOC/SOH/MinMax/MaxCapac/RemainCapac/max v and t at last charge. slow refreshrate +CAN_frame BMWiX_6F4_REQUEST_PYRO = {.FD = true, .ext_ID = false, .DLC = 5, .ID = 0x6F4, .data = {0x07, 0x03, 0x22, 0xAC, 0x93}}; //Pyro Status +CAN_frame BMWiX_6F4_REQUEST_UPTIME = {.FD = true, .ext_ID = false, .DLC = 5, .ID = 0x6F4, .data = {0x07, 0x03, 0x22, 0xE4, 0xC0}}; // Uptime and Vehicle Time Status +CAN_frame BMWiX_6F4_REQUEST_HVIL = {.FD = true, .ext_ID = false, .DLC = 5, .ID = 0x6F4, .data = {0x07, 0x03, 0x22, 0xE5, 0x69}}; // Request HVIL State +CAN_frame BMWiX_6F4_REQUEST_BALANCINGSTATUS = {.FD = true, .ext_ID = false, .DLC = 5, .ID = 0x6F4, .data = {0x07, 0x03, 0x22, 0xE4, 0xCA}}; // Request Balancing Data +CAN_frame BMWiX_6F4_REQUEST_MAX_CHARGE_DISCHARGE_AMPS = {.FD = true, .ext_ID = false, .DLC = 5, .ID = 0x6F4, .data = {0x07, 0x03, 0x22, 0xE5, 0x62}}; // Request allowable charge discharge amps +CAN_frame BMWiX_6F4_REQUEST_QUALIFIER_CHECK = {.FD = true, .ext_ID = false, .DLC = 5, .ID = 0x6F4, .data = {0x07, 0x03, 0x22, 0xE5, 0x4B}}; // Request HV Voltage Qualifier +CAN_frame BMWiX_6F4_REQUEST_CONTACTORS_CLOSE = {.FD = true, .ext_ID = false, .DLC = 6, .ID = 0x6F4, .data = {0x07, 0x03, 0x22, 0xE5, 0x51, 0x01}}; // Request Contactors Close - Unconfirmed +CAN_frame BMWiX_6F4_REQUEST_CONTACTORS_OPEN = {.FD = true, .ext_ID = false, .DLC = 6, .ID = 0x6F4, .data = {0x07, 0x03, 0x22, 0xE5, 0x51, 0x01}}; // Request Contactors Open - Unconfirmed +CAN_frame BMWiX_6F4_REQUEST_BALANCING_START = {.FD = true, .ext_ID = false, .DLC = 6, .ID = 0x6F4, .data = {0xF4, 0x04, 0x71, 0x01, 0xAE, 0x77}}; // Request Balancing command? +CAN_frame BMWiX_6F4_CONTINUE_DATA = {.FD = true, .ext_ID = false, .DLC = 4, .ID = 0x6F4, .data = {0x07, 0x30, 0x00, 0x02}}; -CAN_frame BMWiX_6F4 = {.FD = true, - .ext_ID = false, - .DLC = 5, - .ID = 0x6F4, - .data = {0x07, 0x03, 0x22, 0xE5, 0xC7}}; // UDS Request data from SME. byte 4 selects requested value - - -CAN_frame BMWiX_0C0 = {.FD = true, - .ext_ID = false, - .DLC = 2, - .ID = 0x0C0, - .data = {0xF0, 0x08}}; // Keep Alive 2 BDC>SME 200ms First byte cycles F0 > FE second byte 08 static - +//Action Requests: +CAN_frame BMW_10B = {.FD = true, .ext_ID = false, .DLC = 3, .ID = 0x10B, .data = {0xCD, 0x00, 0xFC}}; // Contactor closing command? -CAN_frame BMWiX_6F4_CELL_VOLTAGE = {.FD = true, .ext_ID = false, .DLC = 5, .ID = 0x6F4, .data = {0x07, 0x03, 0x22, 0xE5, 0x54}}; CAN_frame BMWiX_6F4_CELL_SOC = {.FD = true, .ext_ID = false, .DLC = 5, .ID = 0x6F4, .data = {0x07, 0x03, 0x22, 0xE5, 0x9A}}; CAN_frame BMWiX_6F4_CELL_TEMP = {.FD = true, .ext_ID = false, .DLC = 5, .ID = 0x6F4, .data = {0x07, 0x03, 0x22, 0xE5, 0xCA}}; - -CAN_frame BMWiX_6F4_CONTINUE_DATA = {.FD = true, .ext_ID = false, .DLC = 4, .ID = 0x6F4, .data = {0x07, 0x30, 0x00, 0x02}}; - -CAN_frame BMW_6F4_CELL = {.FD = true, .ext_ID = false, .DLC = 5, .ID = 0x6F4, .data = {0x07, 0x03, 0x22, 0xDD, 0xBF}};// gives error F4 03 7F 22 31 -CAN_frame BMW_6F4_SOH = {.FD = true, .ext_ID = false, .DLC = 5, .ID = 0x6F4, .data = {0x07, 0x03, 0x22, 0x63, 0x35}}; // gives error F4 03 7F 22 31 -CAN_frame BMW_6F4_SOC = {.FD = true, .ext_ID = false, .DLC = 5, .ID = 0x6F4, .data = {0x07, 0x03, 0x22, 0xDD, 0xBC}}; // gives blanks - -CAN_frame BMW_10B = {.FD = true, - .ext_ID = false, - .DLC = 3, - .ID = 0x10B, - .data = {0xCD, 0x00, 0xFC}}; // Contactor closing command - -CAN_frame BMW_6F4_CELL_VOLTAGE_AVG = {.FD = true, - .ext_ID = false, - .DLC = 5, - .ID = 0x6F4, - .data = {0x07, 0x03, 0x22, 0xDF, 0xA0}}; -CAN_frame BMW_6F4_CELL_VOLTAGE_CELLNO = {.FD = false, - .ext_ID = false, - .DLC = 7, - .ID = 0x6F4, - .data = {0x07, 0x05, 0x31, 0x01, 0xAD, 0x6E, 0x01}}; // gives error -CAN_frame BMW_6F4_CELL_CONTINUE = {.FD = true, - .ext_ID = false, - .DLC = 6, - .ID = 0x6F4, - .data = {0x07, 0x04, 0x31, 0x03, 0xAD, 0x6E}}; +//Request Data CAN End static bool battery_awake = false; +//Setup UDS values to poll for +CAN_frame* UDS_REQUESTS100MS[] = { + &BMWiX_6F4_REQUEST_CELL_TEMP, + &BMWiX_6F4_REQUEST_SOC, + &BMWiX_6F4_REQUEST_CAPACITY, + &BMWiX_6F4_REQUEST_MINMAXCELLV, + &BMWiX_6F4_REQUEST_MAINVOLTAGE_POSTCONTACTOR, + &BMWiX_6F4_REQUEST_MAINVOLTAGE_PRECONTACTOR, + &BMWiX_6F4_REQUEST_BATTERYCURRENT, + &BMWiX_6F4_REQUEST_CELL_VOLTAGE, + &BMWiX_6F4_REQUEST_T30VOLTAGE, + &BMWiX_6F4_REQUEST_SOH, + &BMWiX_6F4_REQUEST_UPTIME, + &BMWiX_6F4_REQUEST_PYRO, + &BMWiX_6F4_REQUEST_EOL_ISO, + &BMWiX_6F4_REQUEST_HVIL, + &BMWiX_6F4_REQUEST_MAX_CHARGE_DISCHARGE_AMPS, + &BMWiX_6F4_REQUEST_BALANCINGSTATUS + }; +int numUDSreqs = sizeof(UDS_REQUESTS100MS) / sizeof(UDS_REQUESTS100MS[0]); // Number of elements in the array + //iX Intermediate vars +static uint32_t battery_serial_number = 0; static int32_t battery_current = 0; -static int16_t battery_voltage = 0; +static int16_t battery_voltage = 370; static int16_t terminal30_12v_voltage = 0; static int16_t battery_voltage_after_contactor = 0; -static int16_t min_soc_state = 0; -static int16_t avg_soc_state = 0; -static int16_t max_soc_state = 0; +static int16_t min_soc_state = 50; +static int16_t avg_soc_state = 50; +static int16_t max_soc_state = 50; +static int16_t min_soh_state = 99;// Uses E5 45, also available in 78 73 +static int16_t avg_soh_state = 99;// Uses E5 45, also available in 78 73 +static int16_t max_soh_state = 99;// Uses E5 45, also available in 78 73 +static uint16_t battery_max_charge_voltage = 0; +static uint16_t battery_min_discharge_voltage = 0; static int32_t remaining_capacity = 0; static int32_t max_capacity = 0; static int16_t min_battery_temperature = 0; @@ -191,20 +178,41 @@ static int16_t max_battery_temperature = 0; static int16_t main_contactor_temperature = 0; static int16_t min_cell_voltage = 0; static int16_t max_cell_voltage = 0; +static unsigned min_cell_voltage_lastreceived = 0; +static unsigned max_cell_voltage_lastreceived = 0; static int16_t battery_power = 0; +static uint32_t sme_uptime = 0; //Uses E4 C0 +static int16_t allowable_charge_amps = 0; //E5 62 +static int16_t allowable_discharge_amps = 0; //E5 62 +static int32_t iso_safety_positive = 0; //Uses A8 60 +static int32_t iso_safety_negative = 0; //Uses A8 60 +static int32_t iso_safety_parallel = 0; //Uses A8 60 +static int16_t count_full_charges = 0; //TODO 42 +static int16_t count_charges = 0; //TODO 42 +static int16_t hvil_status = 0; +static int16_t voltage_qualifier_status = 0; //0 = Valid, 1 = Invalid +static int16_t balancing_status = 0; //4 = not active +static uint8_t contactors_closed = 0; //TODO E5 BF or E5 51 +static uint8_t contactor_status_precharge = 0; //TODO E5 BF +static uint8_t contactor_status_negative = 0; //TODO E5 BF +static uint8_t contactor_status_positive = 0; //TODO E5 BF +static uint8_t pyro_status_pss1 = 0; //Using AC 93 +static uint8_t pyro_status_pss4 = 0; //Using AC 93 +static uint8_t pyro_status_pss6 = 0; //Using AC 93 static uint8_t uds_req_id_counter = 0; + static byte iX_0C0_counter = 0xF0; // Initialize to 0xF0 //End iX Intermediate vars static uint8_t current_cell_polled = 0; -static uint8_t increment_uds_req_id_counter(uint8_t counter) { - counter++; - if (counter > 8) { - counter = 0; +static uint8_t increment_uds_req_id_counter(uint8_t index) { + index++; + if (index >= numUDSreqs) { + index = 0; } - return counter; + return index; } static uint8_t increment_alive_counter(uint8_t counter) { @@ -236,11 +244,11 @@ void update_values_battery() { //This function maps all the values fetched via datalayer.battery.status.remaining_capacity_Wh = remaining_capacity; - datalayer.battery.status.soh_pptt = 9900; + datalayer.battery.status.soh_pptt = min_soh_state; - datalayer.battery.status.max_discharge_power_W = 6000; + datalayer.battery.status.max_discharge_power_W = 10000; //Aux HV Port has 100A Fuse - datalayer.battery.status.max_charge_power_W = 6000; + datalayer.battery.status.max_charge_power_W = 10000; //Aux HV Port has 100A Fuse battery_power = (datalayer.battery.status.current_dA * (datalayer.battery.status.voltage_dV / 100)); @@ -256,11 +264,34 @@ void update_values_battery() { //This function maps all the values fetched via datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; - datalayer.battery.info.number_of_cells = 108; //Hardcoded for the 108S SE27 battery. SE26 96S TODO + datalayer.battery.info.number_of_cells = 108; //init with 108S before autodetection datalayer_extended.bmwix.T30_Voltage = terminal30_12v_voltage; -} + datalayer_extended.bmwix.hvil_status= hvil_status; + + datalayer_extended.bmwix.bms_uptime = sme_uptime; + + datalayer_extended.bmwix.pyro_status_pss1 = pyro_status_pss1; + + datalayer_extended.bmwix.pyro_status_pss4 = pyro_status_pss4; + + datalayer_extended.bmwix.pyro_status_pss6 = pyro_status_pss6; + + datalayer_extended.bmwix.iso_safety_positive = iso_safety_positive; + + datalayer_extended.bmwix.iso_safety_negative = iso_safety_negative; + + datalayer_extended.bmwix.iso_safety_parallel= iso_safety_parallel; + + datalayer_extended.bmwix.allowable_charge_amps = allowable_charge_amps; + + datalayer_extended.bmwix.allowable_discharge_amps= allowable_discharge_amps; + + datalayer_extended.bmwix.balancing_status = balancing_status; + + +} void receive_can_battery(CAN_frame rx_frame) { battery_awake = true; switch (rx_frame.ID) { @@ -276,7 +307,10 @@ void receive_can_battery(CAN_frame rx_frame) { int num_voltages = 29; // number of voltage readings to get for (int i = start_index; i < (start_index + num_voltages * 2); i += 2) { uint16_t voltage = (rx_frame.data.u8[i] <<8) | rx_frame.data.u8[i + 1]; - datalayer.battery.status.cell_voltages_mV[voltage_index++] = voltage; + if (voltage < 10000) { //Check reading is plausible - otherwise ignore + datalayer.battery.status.cell_voltages_mV[voltage_index] = voltage; + } + voltage_index++; } } @@ -290,7 +324,10 @@ void receive_can_battery(CAN_frame rx_frame) { int num_voltages = 31; // number of voltage readings to get for (int i = start_index; i < (start_index + num_voltages * 2); i += 2) { uint16_t voltage = (rx_frame.data.u8[i] <<8) | rx_frame.data.u8[i + 1]; - datalayer.battery.status.cell_voltages_mV[voltage_index++] = voltage; + if (voltage < 10000) { //Check reading is plausible - otherwise ignore + datalayer.battery.status.cell_voltages_mV[voltage_index] = voltage; + } + voltage_index++; } } @@ -300,14 +337,25 @@ void receive_can_battery(CAN_frame rx_frame) { int num_voltages = 31; // number of voltage readings to get for (int i = start_index; i < (start_index + num_voltages * 2); i += 2) { uint16_t voltage = (rx_frame.data.u8[i] <<8) | rx_frame.data.u8[i + 1]; - datalayer.battery.status.cell_voltages_mV[voltage_index++] = voltage; + if (voltage < 10000) { //Check reading is plausible - otherwise ignore + datalayer.battery.status.cell_voltages_mV[voltage_index] = voltage; + } + voltage_index++; } } if (rx_frame.DLC = 64 && rx_frame.data.u8[0] == 0xF4 && rx_frame.data.u8[1] == 0x23){ //Individual Cell Voltages - 3rd Continue frame int start_index = 2; //Data starts here int voltage_index = 91; //Start cell ID - int num_voltages = 17; // number of voltage readings to get + int num_voltages; + if (rx_frame.data.u8[12] == 0xFF && rx_frame.data.u8[13]== 0xFF){ //97th cell is blank - assume 96S Battery + num_voltages = 5; // number of voltage readings to get - 6 more to get on 96S + datalayer.battery.info.number_of_cells = 96; + } else { //We have data in 97th cell, assume 108S Battery + num_voltages = 17; // number of voltage readings to get - 17 more to get on 108S + datalayer.battery.info.number_of_cells = 108; + } + for (int i = start_index; i < (start_index + num_voltages * 2); i += 2) { uint16_t voltage = (rx_frame.data.u8[i] <<8) | rx_frame.data.u8[i + 1]; datalayer.battery.status.cell_voltages_mV[voltage_index++] = voltage; @@ -329,6 +377,9 @@ void receive_can_battery(CAN_frame rx_frame) { rx_frame.data.u8[8]); } + if (rx_frame.DLC = 64 && rx_frame.data.u8[4] == 0xE4 && rx_frame.data.u8[5] == 0xCA) { //Balancing Data + balancing_status = ( rx_frame.data.u8[6]); //4 = No symmetry mode active, invalid qualifier + } if (rx_frame.DLC = 7 && rx_frame.data.u8[4] == 0xE5 && rx_frame.data.u8[5] == 0xCE) { //Min/Avg/Max SOC% min_soc_state = (rx_frame.data.u8[8] <<8 | rx_frame.data.u8[9]); avg_soc_state = (rx_frame.data.u8[6] <<8 | rx_frame.data.u8[7]); @@ -340,12 +391,52 @@ void receive_can_battery(CAN_frame rx_frame) { max_capacity = ((rx_frame.data.u8[8] <<8 | rx_frame.data.u8[9]) *10) -50000; } - if (rx_frame.DLC = 12 && rx_frame.data.u8[4] == 0xE5 && rx_frame.data.u8[5] == 0x53) { //Min and max cell voltage + if (rx_frame.DLC = 20 && rx_frame.data.u8[4] == 0xE5 && rx_frame.data.u8[5] == 0x45) { //SOH Max Min Mean Request + min_soh_state = ((rx_frame.data.u8[8] <<8 | rx_frame.data.u8[9])); + avg_soh_state = ((rx_frame.data.u8[10] <<8 | rx_frame.data.u8[11])); + max_soh_state = ((rx_frame.data.u8[12] <<8 | rx_frame.data.u8[13])); + } + + if (rx_frame.DLC = 10 && rx_frame.data.u8[4] == 0xE5 && rx_frame.data.u8[5] == 0x62) { //Max allowed charge and discharge current - Signed 16bit + allowable_charge_amps = (int16_t)((rx_frame.data.u8[6] <<8 | rx_frame.data.u8[7])); + allowable_discharge_amps = (int16_t)((rx_frame.data.u8[8] <<8 | rx_frame.data.u8[9])); + } + + if (rx_frame.DLC = 9 && rx_frame.data.u8[4] == 0xE5 && rx_frame.data.u8[5] == 0x4B) { //Max allowed charge and discharge current - Signed 16bit + voltage_qualifier_status = ( rx_frame.data.u8[8]); // Request HV Voltage Qualifier + } + + if (rx_frame.DLC = 48 && rx_frame.data.u8[4] == 0xA8 && rx_frame.data.u8[5] == 0x60) { // Safety Isolation Measurements + iso_safety_positive = (rx_frame.data.u8[34] << 24) | (rx_frame.data.u8[35] << 16) | (rx_frame.data.u8[36] << 8) |rx_frame.data.u8[37]; //Assuming 32bit + iso_safety_negative = (rx_frame.data.u8[38] << 24) | (rx_frame.data.u8[39] << 16) | (rx_frame.data.u8[40] << 8) |rx_frame.data.u8[41]; //Assuming 32bit + iso_safety_parallel = (rx_frame.data.u8[42] << 24) | (rx_frame.data.u8[43] << 16) | (rx_frame.data.u8[44] << 8) |rx_frame.data.u8[45]; //Assuming 32bit + } + + + if (rx_frame.DLC = 48 && rx_frame.data.u8[4] == 0xE4 && rx_frame.data.u8[5] == 0xC0) { // Uptime and Vehicle Time Status + sme_uptime = (rx_frame.data.u8[10] << 24) | (rx_frame.data.u8[11] << 16) | (rx_frame.data.u8[12] << 8) |rx_frame.data.u8[13]; //Assuming 32bit + } + + if (rx_frame.DLC = 8 && rx_frame.data.u8[3] == 0xAC && rx_frame.data.u8[4] == 0x93) { // Pyro Status + pyro_status_pss1 = (rx_frame.data.u8[5]); + pyro_status_pss4 = (rx_frame.data.u8[6]); + pyro_status_pss6 = (rx_frame.data.u8[7]); + } + + if (rx_frame.DLC = 12 && rx_frame.data.u8[4] == 0xE5 && rx_frame.data.u8[5] == 0x53) { //Min and max cell voltage 10V = Qualifier Invalid + if((rx_frame.data.u8[6] <<8 | rx_frame.data.u8[7]) == 10000 && (rx_frame.data.u8[8] <<8 | rx_frame.data.u8[9]) == 10000){ //Qualifier Invalid Mode - Request Reboot + #ifdef DEBUG_VIA_USB + Serial.println("Cell MinMax Qualifier Invalid - Requesting BMS Reset"); + #endif + set_event(EVENT_SOC_UNAVAILABLE, (millis())); + transmit_can(&BMWiX_6F4_REQUEST_HARD_RESET, can_config.battery); + } else{ //Only ingest values if they are not the 10V Error state min_cell_voltage = (rx_frame.data.u8[6] <<8 | rx_frame.data.u8[7]); max_cell_voltage = (rx_frame.data.u8[8] <<8 | rx_frame.data.u8[9]); + } } - if (rx_frame.DLC = 16 && rx_frame.data.u8[4] == 0xDD && rx_frame.data.u8[5] == 0xC0) { //Battery Temperature + if (rx_frame.DLC = 16 && rx_frame.data.u8[4] == 0xDD && rx_frame.data.u8[5] == 0xC0) { //Battery Temperature min_battery_temperature = (rx_frame.data.u8[6] <<8 | rx_frame.data.u8[7])/10; avg_battery_temperature = (rx_frame.data.u8[10] <<8 | rx_frame.data.u8[11])/10; max_battery_temperature = (rx_frame.data.u8[8] <<8 | rx_frame.data.u8[9])/10; @@ -356,6 +447,19 @@ void receive_can_battery(CAN_frame rx_frame) { if (rx_frame.DLC = 7 && rx_frame.data.u8[4] == 0xA7) { //Terminal 30 Voltage (12V SME supply) terminal30_12v_voltage = (rx_frame.data.u8[5] <<8 | rx_frame.data.u8[6]); } + if (rx_frame.DLC = 6 && rx_frame.data.u8[3] == 0xE5 && rx_frame.data.u8[4] == 0x69) { //HVIL Status + hvil_status = ( rx_frame.data.u8[5]); + } + if (rx_frame.DLC = 16 && rx_frame.data.u8[3] == 0xF1 && rx_frame.data.u8[4] == 0x8C ) { //Battery Serial Number + //Convert hex bytes to ASCII characters and combine them into a string + char numberString[11]; // 10 characters + null terminator + for (int i = 0; i < 10; i++) { + numberString[i] = char(rx_frame.data.u8[i+6]); + } + numberString[10] = '\0'; // Null-terminate the string + // Step 3: Convert the string to an unsigned long integer + battery_serial_number = strtoul(numberString, NULL, 10); + } break; default: break; @@ -365,7 +469,7 @@ void receive_can_battery(CAN_frame rx_frame) { void send_can_battery() { unsigned long currentMillis = millis(); - if (battery_awake) { + //if (battery_awake) { //We can always send CAN as the iX BMS will wake up on vehicle comms //Send 20ms message if (currentMillis - previousMillis20 >= INTERVAL_20_MS) { // Check if sending of CAN messages has been delayed too much. @@ -380,61 +484,22 @@ void send_can_battery() { if (currentMillis - previousMillis100 >= INTERVAL_100_MS) { previousMillis100 = currentMillis; - uds_req_id_counter = increment_uds_req_id_counter(uds_req_id_counter); - switch (uds_req_id_counter){ - case 0: - //BMWiX_6F4.data.u8[3] = 0x22; - BMWiX_6F4.data.u8[3] = 0xDD; - BMWiX_6F4.data.u8[4] = 0xC0; - transmit_can(&BMWiX_6F4, can_config.battery); - break; - case 1: - BMWiX_6F4.data.u8[3] = 0xE5; - BMWiX_6F4.data.u8[4] = 0xCE; - transmit_can(&BMWiX_6F4, can_config.battery); - break; - case 2: - BMWiX_6F4.data.u8[4] = 0xC7; - transmit_can(&BMWiX_6F4, can_config.battery); - break; - case 3: - BMWiX_6F4.data.u8[4] = 0x53; - transmit_can(&BMWiX_6F4, can_config.battery); - break; - case 4: - BMWiX_6F4.data.u8[4] = 0x4A; - transmit_can(&BMWiX_6F4, can_config.battery); - break; - case 5: - BMWiX_6F4.data.u8[4] = 0x4D; - transmit_can(&BMWiX_6F4, can_config.battery); - break; - case 6: - BMWiX_6F4.data.u8[4] = 0x61; - transmit_can(&BMWiX_6F4, can_config.battery); - break; - case 7: - BMWiX_6F4.data.u8[4] = 0x54; - transmit_can(&BMWiX_6F4, can_config.battery); - break; - case 8: - BMWiX_6F4.data.u8[4] = 0xA7; //Terminal 30 12v voltage - transmit_can(&BMWiX_6F4, can_config.battery); - break; - } + //Loop through and send a different UDS request each cycle + uds_req_id_counter = increment_uds_req_id_counter(uds_req_id_counter); + transmit_can(UDS_REQUESTS100MS[uds_req_id_counter], can_config.battery); - //Send SME Keep alive values 100ms - transmit_can(&BMWiX_510, can_config.battery); + //Send SME Keep alive values 100ms + transmit_can(&BMWiX_510, can_config.battery); } // Send 200ms CAN Message if (currentMillis - previousMillis200 >= INTERVAL_200_MS) { previousMillis200 = currentMillis; - //Send SME Keep alive values 200ms - BMWiX_0C0.data.u8[0] = increment_0C0_counter(BMWiX_0C0.data.u8[0]); //Keep Alive 1 - transmit_can(&BMWiX_0C0, can_config.battery); + //Send SME Keep alive values 200ms + BMWiX_0C0.data.u8[0] = increment_0C0_counter(BMWiX_0C0.data.u8[0]); //Keep Alive 1 + transmit_can(&BMWiX_0C0, can_config.battery); } // Send 500ms CAN Message if (currentMillis - previousMillis500 >= INTERVAL_500_MS) { @@ -448,43 +513,11 @@ void send_can_battery() { if (currentMillis - previousMillis1000 >= INTERVAL_1_S) { previousMillis1000 = currentMillis; - //Send SME Keep alive values 1000ms - transmit_can(&BMWiX_06D, can_config.battery); - transmit_can(&BMWiX_2F1, can_config.battery); - transmit_can(&BMWiX_439, can_config.battery); - - switch (cmdState) { - case SOC: - transmit_can(&BMW_6F4_CELL, can_config.battery); - cmdState = CELL_VOLTAGE_MINMAX; - break; - case CELL_VOLTAGE_MINMAX: - transmit_can(&BMW_6F4_SOH, can_config.battery); - cmdState = SOH; - break; - case SOH: - transmit_can(&BMW_6F4_CELL_VOLTAGE_AVG, can_config.battery); - cmdState = CELL_VOLTAGE_CELLNO; - current_cell_polled = 0; - - break; - case CELL_VOLTAGE_CELLNO: - current_cell_polled++; - if (current_cell_polled > 96) { - datalayer.battery.info.number_of_cells = 97; - cmdState = CELL_VOLTAGE_CELLNO_LAST; - } else { - cmdState = CELL_VOLTAGE_CELLNO; - - BMW_6F4_CELL_VOLTAGE_CELLNO.data.u8[6] = current_cell_polled; - transmit_can(&BMW_6F4_CELL_VOLTAGE_CELLNO, can_config.battery); - } - break; - case CELL_VOLTAGE_CELLNO_LAST: - transmit_can(&BMW_6F4_SOC, can_config.battery); - cmdState = SOC; - break; - } + //Send SME Keep alive values 1000ms + //test disable transmit_can(&BMWiX_06D, can_config.battery); + //test disable transmit_can(&BMWiX_2F1, can_config.battery); + //test disable transmit_can(&BMWiX_439, can_config.battery); + } // Send 5000ms CAN Message if (currentMillis - previousMillis5000 >= INTERVAL_5_S) { @@ -494,31 +527,33 @@ void send_can_battery() { if (currentMillis - previousMillis10000 >= INTERVAL_10_S) { previousMillis10000 = currentMillis; } - } else { - previousMillis20 = currentMillis; - previousMillis100 = currentMillis; - previousMillis200 = currentMillis; - previousMillis500 = currentMillis; - previousMillis640 = currentMillis; - previousMillis1000 = currentMillis; - previousMillis5000 = currentMillis; - previousMillis10000 = currentMillis; } -} + //We can always send CAN as the iX BMS will wake up on vehicle comms + // else { + // previousMillis20 = currentMillis; + // previousMillis100 = currentMillis; + // previousMillis200 = currentMillis; + // previousMillis500 = currentMillis; + // previousMillis640 = currentMillis; + // previousMillis1000 = currentMillis; + // previousMillis5000 = currentMillis; + // previousMillis10000 = currentMillis; + // } +//} //We can always send CAN as the iX BMS will wake up on vehicle comms void setup_battery(void) { // Performs one time setup at startup #ifdef DEBUG_VIA_USB Serial.println("BMW iX battery selected"); #endif //DEBUG_VIA_USB - //Before we have started up and detected which battery is in use, use 60AH values + //Before we have started up and detected which battery is in use, use 108S values datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV; datalayer.battery.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV; datalayer.system.status.battery_allows_contactor_closing = true; - pinMode(WUP_PIN, OUTPUT); - digitalWrite(WUP_PIN, HIGH); // Wake up the battery + //pinMode(WUP_PIN, OUTPUT); // Not needed - can hold WUP pin High with iX BMS + //digitalWrite(WUP_PIN, HIGH); // Wake up the battery // Not needed - can hold WUP pin High with iX BMS //Wake Battery //Send SME Keep alive values 100ms @@ -526,10 +561,12 @@ void setup_battery(void) { // Performs one time setup at startup //Send SME Keep alive values 200ms BMWiX_0C0.data.u8[0] = increment_0C0_counter(BMWiX_0C0.data.u8[0]); //Keep Alive 1 transmit_can(&BMWiX_0C0, can_config.battery); + + //Send SME Keep alive values 1000ms - transmit_can(&BMWiX_06D, can_config.battery); - transmit_can(&BMWiX_2F1, can_config.battery); - transmit_can(&BMWiX_439, can_config.battery); + //test disable transmit_can(&BMWiX_06D, can_config.battery); + //test disable transmit_can(&BMWiX_2F1, can_config.battery); + //test disable transmit_can(&BMWiX_439, can_config.battery); } #endif From 17a7e90b3d36ff46f79d31fb6d9288dc193e7497 Mon Sep 17 00:00:00 2001 From: wjcloudy <56305354+wjcloudy@users.noreply.github.com> Date: Wed, 30 Oct 2024 21:40:45 +0000 Subject: [PATCH 088/210] 0.2alpha version - Update datalayer_extended.h --- Software/src/datalayer/datalayer_extended.h | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/Software/src/datalayer/datalayer_extended.h b/Software/src/datalayer/datalayer_extended.h index aecbed70..18fd8f4e 100644 --- a/Software/src/datalayer/datalayer_extended.h +++ b/Software/src/datalayer/datalayer_extended.h @@ -8,7 +8,21 @@ typedef struct { /** Terminal 30 - 12V SME Supply Voltage */ uint16_t T30_Voltage = 0; /** Status HVIL, 1 HVIL OK, 0 HVIL disconnected*/ - uint8_t HVIL_Status= 0; + uint8_t hvil_status= 0; + /** Min/Max Cell SOH*/ + uint16_t min_soh_state = 0; + uint16_t max_soh_state=0; + uint32_t bms_uptime=0; + uint8_t pyro_status_pss1=0; + uint8_t pyro_status_pss4=0; + uint8_t pyro_status_pss6=0; + int32_t iso_safety_positive=0; + int32_t iso_safety_negative=0; + int32_t iso_safety_parallel=0; + int32_t allowable_charge_amps = 0; + int32_t allowable_discharge_amps = 0; + int16_t balancing_status = 0; + } DATALAYER_INFO_BMWIX; From 450d690edf34f84a59f9cf2e30a9d9e55edbdf95 Mon Sep 17 00:00:00 2001 From: wjcloudy <56305354+wjcloudy@users.noreply.github.com> Date: Wed, 30 Oct 2024 21:41:11 +0000 Subject: [PATCH 089/210] 0.2alpha version - Update advanced_battery_html.cpp --- .../webserver/advanced_battery_html.cpp | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/Software/src/devboard/webserver/advanced_battery_html.cpp b/Software/src/devboard/webserver/advanced_battery_html.cpp index 99ff2201..8a018ce7 100644 --- a/Software/src/devboard/webserver/advanced_battery_html.cpp +++ b/Software/src/devboard/webserver/advanced_battery_html.cpp @@ -18,7 +18,29 @@ String advanced_battery_processor(const String& var) { #ifdef BMW_IX_BATTERY - content += "

T30 Terminal Voltage: " + String(datalayer_extended.bmwix.T30_Voltage) + "mV

"; + content += "

T30 Terminal Voltage: " + String(datalayer_extended.bmwix.T30_Voltage) + " mV

"; + content += "

Detected Cell Count: " + String(datalayer.battery.info.number_of_cells) + "

"; + static const char* balanceText[5] = {"0 No balancing mode active", + "1 Voltage-Controlled Balancing Mode", + "2 Time-Controlled Balancing Mode with Demand Calculation at End of Charging" , + "3 Time-Controlled Balancing Mode with Demand Calculation at Resting Voltage" , + "4 No balancing mode active, qualifier invalid" + }; + content += "

Balancing Status: " + String((balanceText[datalayer_extended.bmwix.balancing_status])) + "

"; + static const char* hvilText[2] = {"Error (Loop Open)", + "OK (Loop Closed)"}; + content += "

HVIL Status: " + String(hvilText[datalayer_extended.bmwix.hvil_status]) + "

"; + content += "

BMS Uptime: " + String(datalayer_extended.bmwix.bms_uptime) + " seconds

"; + content += "

BMS Allowed Charge Amps: " + String(datalayer_extended.bmwix.allowable_charge_amps) + " A

"; + content += "

BMS Allowed Disharge Amps: " + String(datalayer_extended.bmwix.allowable_discharge_amps) + " A

"; + content += "
"; + content += "

HV Isolation (2147483647kOhm = maximum/invalid)

"; + content += "

Isolation Positive: " + String(datalayer_extended.bmwix.iso_safety_positive) + " kOhm

"; + content += "

Isolation Negative: " + String(datalayer_extended.bmwix.iso_safety_negative) + " kOhm

"; + content += "

Isolation Parallel: " + String(datalayer_extended.bmwix.iso_safety_parallel) + " kOhm

"; + content += "

Pyro Status PSS1: " + String(datalayer_extended.bmwix.pyro_status_pss1) + "

"; + content += "

Pyro Status PSS4: " + String(datalayer_extended.bmwix.pyro_status_pss4) + "

"; + content += "

Pyro Status PSS6: " + String(datalayer_extended.bmwix.pyro_status_pss6) + "

"; #endif //BMW_IX_BATTERY #ifdef BMW_I3_BATTERY From 5ae76f164216abfc619a48cf37c5f1dd97b0bb0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Thu, 31 Oct 2024 16:08:31 +0200 Subject: [PATCH 090/210] Make limit failures match new update rate --- Software/src/devboard/safety/safety.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Software/src/devboard/safety/safety.h b/Software/src/devboard/safety/safety.h index 82a6298f..32ae0ab8 100644 --- a/Software/src/devboard/safety/safety.h +++ b/Software/src/devboard/safety/safety.h @@ -6,7 +6,7 @@ #define MAX_CAN_FAILURES 50 -#define MAX_CHARGE_DISCHARGE_LIMIT_FAILURES 1 +#define MAX_CHARGE_DISCHARGE_LIMIT_FAILURES 5 //battery pause status begin enum battery_pause_status { NORMAL = 0, PAUSING = 1, PAUSED = 2, RESUMING = 3 }; From eb40deee69da74224146072aa20931c69ec59754 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Thu, 31 Oct 2024 16:43:31 +0200 Subject: [PATCH 091/210] Add battery to workflow --- .github/workflows/compile-all-batteries.yml | 1 + Software/src/battery/CELLPOWER-BMS.cpp | 22 +++++---------------- 2 files changed, 6 insertions(+), 17 deletions(-) diff --git a/.github/workflows/compile-all-batteries.yml b/.github/workflows/compile-all-batteries.yml index b2306708..1cd8955e 100644 --- a/.github/workflows/compile-all-batteries.yml +++ b/.github/workflows/compile-all-batteries.yml @@ -35,6 +35,7 @@ jobs: battery: - BMW_I3_BATTERY - BYD_ATTO_3_BATTERY + - CELLPOWER_BMS - CHADEMO_BATTERY - IMIEV_CZERO_ION_BATTERY - JAGUAR_IPACE_BATTERY diff --git a/Software/src/battery/CELLPOWER-BMS.cpp b/Software/src/battery/CELLPOWER-BMS.cpp index 0020d082..8bd5a1b5 100644 --- a/Software/src/battery/CELLPOWER-BMS.cpp +++ b/Software/src/battery/CELLPOWER-BMS.cpp @@ -127,7 +127,7 @@ void update_values_battery() { datalayer.battery.status.active_power_W = //Power in watts, Negative = charging batt ((datalayer.battery.status.voltage_dV * datalayer.battery.status.current_dA) / 100); - datalayer.battery.status.max_charge_power_W = ((requested_charge_current_dA * battery_pack_voltage_dV) / 100); + datalayer.battery.status.max_charge_power_W = 5000; //TODO, is this available via CAN? datalayer.battery.status.max_discharge_power_W = 5000; //TODO, is this available via CAN? @@ -212,24 +212,12 @@ void update_values_battery() { if (IO_state_discharge_enable) { //TODO, shall we react on this? } + if (error_state) { + //TODO, shall we react on this? + } } - void receive_can_battery(CAN_frame rx_frame) { - /* - // All CAN messages recieved will be logged via serial - Serial.print(millis()); // Example printout, time, ID, length, data: 7553 1DB 8 FF C0 B9 EA 0 0 2 5D - Serial.print(" "); - Serial.print(rx_frame.ID, HEX); - Serial.print(" "); - Serial.print(rx_frame.DLC); - Serial.print(" "); - for (int i = 0; i < rx_frame.DLC; ++i) { - Serial.print(rx_frame.data.u8[i], HEX); - Serial.print(" "); - } - Serial.println(""); - */ switch (rx_frame.ID) { case 0x1A4: //PDO1_TX - 200ms datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; @@ -351,7 +339,7 @@ void setup_battery(void) { // Performs one time setup at startup #ifdef DEBUG_VIA_USB Serial.println("Cellpower BMS selected"); #endif - + datalayer.system.status.battery_allows_contactor_closing = true; datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV; datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV; From 97b6706c3ad2fffe3b69bedc52b1bd47011db72a Mon Sep 17 00:00:00 2001 From: mathl79 Date: Fri, 1 Nov 2024 10:57:00 +0100 Subject: [PATCH 092/210] increase default Serial_Link baud rate to 115200 and make it configurable in the USER_SETTINGS.h --- Software/Software.ino | 2 +- Software/USER_SETTINGS.h | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Software/Software.ino b/Software/Software.ino index b575dc86..6b9cbc4a 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -931,7 +931,7 @@ void runSerialDataLink() { void init_serialDataLink() { #if defined(SERIAL_LINK_RECEIVER) || defined(SERIAL_LINK_TRANSMITTER) - Serial2.begin(9600, SERIAL_8N1, RS485_RX_PIN, RS485_TX_PIN); + Serial2.begin(SERIAL_LINK_BAUDRATE, SERIAL_8N1, RS485_RX_PIN, RS485_TX_PIN); #endif } diff --git a/Software/USER_SETTINGS.h b/Software/USER_SETTINGS.h index 6e2d03d2..39603659 100644 --- a/Software/USER_SETTINGS.h +++ b/Software/USER_SETTINGS.h @@ -64,8 +64,13 @@ OSC_40MHz //CAN_FD option, what is your MCP2518 add-on boards crystal frequency? (Default OSC_40MHz) #endif //#define USE_CANFD_INTERFACE_AS_CLASSIC_CAN // Enable this line if you intend to use the CANFD as normal CAN + //#define SERIAL_LINK_RECEIVER //Enable this line to receive battery data over RS485 pins from another Lilygo (This LilyGo interfaces with inverter) //#define SERIAL_LINK_TRANSMITTER //Enable this line to send battery data over RS485 pins to another Lilygo (This LilyGo interfaces with battery) +#if defined(SERIAL_LINK_RECEIVER) || defined(SERIAL_LINK_TRANSMITTER) +#define SERIAL_LINK_BAUDRATE 112500 +#endif + #define WIFI //#define WIFICONFIG //Enable this line to set a static IP address / gateway /subnet mask for the device. see USER_SETTINGS.cpp for the settings #define WEBSERVER //Enable this line to enable WiFi, and to run the webserver. See USER_SETTINGS.cpp for the Wifi settings. From e802247d644cb9c2d61f4ca3d1669062b9d68bda Mon Sep 17 00:00:00 2001 From: mathl79 <88651276+mathl79@users.noreply.github.com> Date: Sat, 2 Nov 2024 08:36:05 +0100 Subject: [PATCH 093/210] Update USER_SETTINGS.h revert to original. --- Software/USER_SETTINGS.h | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Software/USER_SETTINGS.h b/Software/USER_SETTINGS.h index 39603659..6e2d03d2 100644 --- a/Software/USER_SETTINGS.h +++ b/Software/USER_SETTINGS.h @@ -64,13 +64,8 @@ OSC_40MHz //CAN_FD option, what is your MCP2518 add-on boards crystal frequency? (Default OSC_40MHz) #endif //#define USE_CANFD_INTERFACE_AS_CLASSIC_CAN // Enable this line if you intend to use the CANFD as normal CAN - //#define SERIAL_LINK_RECEIVER //Enable this line to receive battery data over RS485 pins from another Lilygo (This LilyGo interfaces with inverter) //#define SERIAL_LINK_TRANSMITTER //Enable this line to send battery data over RS485 pins to another Lilygo (This LilyGo interfaces with battery) -#if defined(SERIAL_LINK_RECEIVER) || defined(SERIAL_LINK_TRANSMITTER) -#define SERIAL_LINK_BAUDRATE 112500 -#endif - #define WIFI //#define WIFICONFIG //Enable this line to set a static IP address / gateway /subnet mask for the device. see USER_SETTINGS.cpp for the settings #define WEBSERVER //Enable this line to enable WiFi, and to run the webserver. See USER_SETTINGS.cpp for the Wifi settings. From 0cb47e0117200fe33d00506c6e0c2372bcbde70a Mon Sep 17 00:00:00 2001 From: mathl79 <88651276+mathl79@users.noreply.github.com> Date: Sat, 2 Nov 2024 08:40:35 +0100 Subject: [PATCH 094/210] Update Software.ino add SerialLink baud rate define --- Software/Software.ino | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Software/Software.ino b/Software/Software.ino index 6b9cbc4a..ecaa687f 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -85,6 +85,9 @@ uint16_t mbPV[MB_RTU_NUM_VALUES]; // Process variable memory // Create a ModbusRTU server instance listening on Serial2 with 2000ms timeout ModbusServerRTU MBserver(Serial2, 2000); #endif +#if defined(SERIAL_LINK_RECEIVER) || defined(SERIAL_LINK_TRANSMITTER) +#define SERIAL_LINK_BAUDRATE 112500 +#endif // Common charger parameters volatile float charger_setpoint_HV_VDC = 0.0f; From 8def6051921efb91949ba2618f2952879ac3c2d4 Mon Sep 17 00:00:00 2001 From: amarofarinha <151563493+amarofarinha@users.noreply.github.com> Date: Sun, 3 Nov 2024 07:49:27 +0000 Subject: [PATCH 095/210] Replace BMS status check with CAN communication status for MQTT updates --- Software/src/devboard/mqtt/mqtt.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Software/src/devboard/mqtt/mqtt.cpp b/Software/src/devboard/mqtt/mqtt.cpp index fe69f9ba..df0863d6 100644 --- a/Software/src/devboard/mqtt/mqtt.cpp +++ b/Software/src/devboard/mqtt/mqtt.cpp @@ -127,7 +127,7 @@ static void publish_common_info(void) { doc["pause_status"] = get_emulator_pause_status(); //only publish these values if BMS is active and we are comunication with the battery (can send CAN messages to the battery) - if (datalayer.battery.status.bms_status == ACTIVE && allowed_to_send_CAN && millis() > BOOTUP_TIME) { + if (datalayer.battery.status.CAN_battery_still_alive && allowed_to_send_CAN && millis() > BOOTUP_TIME) { doc["SOC"] = ((float)datalayer.battery.status.reported_soc) / 100.0; doc["SOC_real"] = ((float)datalayer.battery.status.real_soc) / 100.0; doc["state_of_health"] = ((float)datalayer.battery.status.soh_pptt) / 100.0; From 16fc2fb4f17378e2f207db255f9ca167f6aace26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Sun, 3 Nov 2024 13:31:56 +0200 Subject: [PATCH 096/210] Add degradation data reset for LEAF --- Software/src/battery/NISSAN-LEAF-BATTERY.cpp | 306 +++++++++++++++++- Software/src/battery/NISSAN-LEAF-BATTERY.h | 8 + Software/src/datalayer/datalayer_extended.h | 12 + .../webserver/advanced_battery_html.cpp | 13 + Software/src/devboard/webserver/webserver.cpp | 10 + 5 files changed, 348 insertions(+), 1 deletion(-) diff --git a/Software/src/battery/NISSAN-LEAF-BATTERY.cpp b/Software/src/battery/NISSAN-LEAF-BATTERY.cpp index 67bb21f9..7c48fe37 100644 --- a/Software/src/battery/NISSAN-LEAF-BATTERY.cpp +++ b/Software/src/battery/NISSAN-LEAF-BATTERY.cpp @@ -174,6 +174,18 @@ static int16_t battery2_temp_polled_max = 0; static int16_t battery2_temp_polled_min = 0; #endif // DOUBLE_BATTERY +// Clear SOH values +static uint8_t stateMachineClearSOH = 0xFF; +static uint32_t incomingChallenge = 0xFFFFFFFF; +static uint8_t solvedChallenge[8]; +static bool challengeFailed = false; + +CAN_frame LEAF_CLEAR_SOH = {.FD = false, + .ext_ID = false, + .DLC = 8, + .ID = 0x79B, + .data = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}}; + void print_with_units(char* header, int value, char* units) { Serial.print(header); Serial.print(value); @@ -337,6 +349,18 @@ void update_values_battery() { /* This function maps all the values fetched via datalayer_extended.nissanleaf.HeatingStop = battery_Heating_Stop; datalayer_extended.nissanleaf.HeatingStart = battery_Heating_Start; datalayer_extended.nissanleaf.HeaterSendRequest = battery_Batt_Heater_Mail_Send_Request; + datalayer_extended.nissanleaf.CryptoChallenge = incomingChallenge; + datalayer_extended.nissanleaf.SolvedChallengeMSB = + ((solvedChallenge[7] << 24) | (solvedChallenge[6] << 16) | (solvedChallenge[5] << 8) | solvedChallenge[4]); + datalayer_extended.nissanleaf.SolvedChallengeLSB = + ((solvedChallenge[3] << 24) | (solvedChallenge[2] << 16) | (solvedChallenge[1] << 8) | solvedChallenge[0]); + + // Update requests from webserver datalayer + if (datalayer_extended.nissanleaf.UserRequestSOHreset) { + Serial.println("REQUEST FROM WEBSERVER"); + stateMachineClearSOH = 0; //Start the statemachine + datalayer_extended.nissanleaf.UserRequestSOHreset = false; + } /*Finally print out values to serial if configured to do so*/ #ifdef DEBUG_VIA_USB @@ -610,7 +634,7 @@ void receive_can_battery2(CAN_frame rx_frame) { } } - if (stop_battery_query) { //Leafspy is active, stop our own polling + if (stop_battery_query) { //Leafspy/Service request is active, stop our own polling break; } @@ -847,6 +871,34 @@ void receive_can_battery(CAN_frame rx_frame) { hold_off_with_polling_10seconds = 10; //Polling is paused for 100s break; case 0x7BB: + + // This section checks if we are doing a SOH reset towards BMS + if (stateMachineClearSOH < 255) { + //Intercept the messages based on state machine + if (rx_frame.data.u8[0] == 0x06) { // Incoming challenge data! + incomingChallenge = ((rx_frame.data.u8[3] << 24) | (rx_frame.data.u8[4] << 16) | (rx_frame.data.u8[5] << 8) | + rx_frame.data.u8[6]); + } + //Error checking + if ((rx_frame.data.u8[0] == 0x03) && (rx_frame.data.u8[1] == 0x7F)) { + challengeFailed = true; + Serial.print("Challenge solving failed"); + } + // All CAN messages recieved from BMS will be logged via serial during development of this function + Serial.print(millis()); // Example printout, time, ID, length, data: 7553 1DB 8 FF C0 B9 EA 0 0 2 5D + Serial.print(" "); + Serial.print(rx_frame.ID, HEX); + Serial.print(" "); + Serial.print(rx_frame.DLC); + Serial.print(" "); + for (int i = 0; i < rx_frame.DLC; ++i) { + Serial.print(rx_frame.data.u8[i], HEX); + Serial.print(" "); + } + Serial.println(""); + break; + } + //First check which group data we are getting if (rx_frame.data.u8[0] == 0x10) { //First message of a group group_7bb = rx_frame.data.u8[3]; @@ -1127,6 +1179,10 @@ void send_can_battery() { if (currentMillis - previousMillis100 >= INTERVAL_100_MS) { previousMillis100 = currentMillis; + if (stateMachineClearSOH < 255) { // Enter the ClearSOH statemachine only if we request it + clearSOH(); + } + //When battery requests heating pack status change, ack this if (battery_Batt_Heater_Mail_Send_Request) { LEAF_50B.data.u8[6] = 0x20; //Batt_Heater_Mail_Send_OK @@ -1230,6 +1286,254 @@ uint16_t Temp_fromRAW_to_F(uint16_t temperature) { //This function feels horrib return static_cast(1094 + (309 - temperature) * 2.5714285714285715); } +void clearSOH(void) { + + stop_battery_query = true; + hold_off_with_polling_10seconds = 10; // Active battery polling is paused for 100 seconds + + switch (stateMachineClearSOH) { + case 0: // Wait until polling actually stops + stateMachineClearSOH = 1; + break; + case 1: // Set CAN_PROCESS_FLAG to 0xC0 + LEAF_CLEAR_SOH.data.u8[0] = 0x02; + LEAF_CLEAR_SOH.data.u8[1] = 0x10; + LEAF_CLEAR_SOH.data.u8[2] = 0xC0; + LEAF_CLEAR_SOH.data.u8[3] = 0x00; + LEAF_CLEAR_SOH.data.u8[4] = 0x00; + LEAF_CLEAR_SOH.data.u8[5] = 0x00; + LEAF_CLEAR_SOH.data.u8[6] = 0x00; + LEAF_CLEAR_SOH.data.u8[7] = 0x00; + transmit_can(&LEAF_CLEAR_SOH, can_config.battery); + // BMS should reply 02 50 C0 FF FF FF FF FF + stateMachineClearSOH = 2; + break; + case 2: // Set something ? + LEAF_CLEAR_SOH.data.u8[0] = 0x02; + LEAF_CLEAR_SOH.data.u8[1] = 0x3E; + LEAF_CLEAR_SOH.data.u8[2] = 0x01; + LEAF_CLEAR_SOH.data.u8[3] = 0x00; + LEAF_CLEAR_SOH.data.u8[4] = 0x00; + LEAF_CLEAR_SOH.data.u8[5] = 0x00; + LEAF_CLEAR_SOH.data.u8[6] = 0x00; + LEAF_CLEAR_SOH.data.u8[7] = 0x00; + transmit_can(&LEAF_CLEAR_SOH, can_config.battery); + // BMS should reply 7E FF FF FF FF FF FF + stateMachineClearSOH = 3; + break; + case 3: // Request challenge to solve + LEAF_CLEAR_SOH.data.u8[0] = 0x02; + LEAF_CLEAR_SOH.data.u8[1] = 0x27; + LEAF_CLEAR_SOH.data.u8[2] = 0x65; + LEAF_CLEAR_SOH.data.u8[3] = 0x00; + LEAF_CLEAR_SOH.data.u8[4] = 0x00; + LEAF_CLEAR_SOH.data.u8[5] = 0x00; + LEAF_CLEAR_SOH.data.u8[6] = 0x00; + LEAF_CLEAR_SOH.data.u8[7] = 0x00; + transmit_can(&LEAF_CLEAR_SOH, can_config.battery); + stateMachineClearSOH = 4; + break; + case 4: // Send back decoded challenge data + decodeChallengeData(incomingChallenge, solvedChallenge); + LEAF_CLEAR_SOH.data.u8[0] = 0x10; + LEAF_CLEAR_SOH.data.u8[1] = 0x0A; + LEAF_CLEAR_SOH.data.u8[2] = 0x27; + LEAF_CLEAR_SOH.data.u8[3] = 0x66; + LEAF_CLEAR_SOH.data.u8[4] = 0x77; + LEAF_CLEAR_SOH.data.u8[5] = solvedChallenge[0]; + LEAF_CLEAR_SOH.data.u8[6] = solvedChallenge[1]; + LEAF_CLEAR_SOH.data.u8[7] = solvedChallenge[2]; + transmit_can(&LEAF_CLEAR_SOH, can_config.battery); + // BMS should reply 7BB 8 30 01 00 FF FF FF FF FF // Proceed with more data (PID ACK) + stateMachineClearSOH = 5; + break; + case 5: // Reply with even more decoded challenge data + LEAF_CLEAR_SOH.data.u8[0] = solvedChallenge[3]; + LEAF_CLEAR_SOH.data.u8[1] = solvedChallenge[4]; + LEAF_CLEAR_SOH.data.u8[2] = solvedChallenge[5]; + LEAF_CLEAR_SOH.data.u8[3] = solvedChallenge[6]; + LEAF_CLEAR_SOH.data.u8[4] = solvedChallenge[7]; + LEAF_CLEAR_SOH.data.u8[5] = 0x00; + LEAF_CLEAR_SOH.data.u8[6] = 0x00; + LEAF_CLEAR_SOH.data.u8[7] = 0x00; + transmit_can(&LEAF_CLEAR_SOH, can_config.battery); + // BMS should reply 02 67 66 FF FF FF FF FF // Thank you for the data + stateMachineClearSOH = 6; + break; + case 6: // Check if solved data was OK + LEAF_CLEAR_SOH.data.u8[0] = 0x03; + LEAF_CLEAR_SOH.data.u8[1] = 0x31; + LEAF_CLEAR_SOH.data.u8[2] = 0x03; + LEAF_CLEAR_SOH.data.u8[3] = 0x00; + LEAF_CLEAR_SOH.data.u8[4] = 0x00; + LEAF_CLEAR_SOH.data.u8[5] = 0x00; + LEAF_CLEAR_SOH.data.u8[6] = 0x00; + LEAF_CLEAR_SOH.data.u8[7] = 0x00; + transmit_can(&LEAF_CLEAR_SOH, can_config.battery); + //7BB 8 03 71 03 01 FF FF FF FF // If all is well, BMS replies with 03 71 03 01. + //Incase you sent wrong challenge, you get 03 7f 31 12 + stateMachineClearSOH = 7; + break; + case 7: // Reset SOH% request + LEAF_CLEAR_SOH.data.u8[0] = 0x03; + LEAF_CLEAR_SOH.data.u8[1] = 0x31; + LEAF_CLEAR_SOH.data.u8[2] = 0x03; + LEAF_CLEAR_SOH.data.u8[3] = 0x01; + LEAF_CLEAR_SOH.data.u8[4] = 0x00; + LEAF_CLEAR_SOH.data.u8[5] = 0x00; + LEAF_CLEAR_SOH.data.u8[6] = 0x00; + LEAF_CLEAR_SOH.data.u8[7] = 0x00; + transmit_can(&LEAF_CLEAR_SOH, can_config.battery); + //7BB 8 03 71 03 02 FF FF FF FF // 03 71 03 02 means that BMS accepted command. + //7BB 03 7f 31 12 means your challenge was wrong, so command ignored + stateMachineClearSOH = 8; + break; + case 8: // Please proceed with resetting SOH + LEAF_CLEAR_SOH.data.u8[0] = 0x02; + LEAF_CLEAR_SOH.data.u8[1] = 0x10; + LEAF_CLEAR_SOH.data.u8[2] = 0x81; + LEAF_CLEAR_SOH.data.u8[3] = 0x00; + LEAF_CLEAR_SOH.data.u8[4] = 0x00; + LEAF_CLEAR_SOH.data.u8[5] = 0x00; + LEAF_CLEAR_SOH.data.u8[6] = 0x00; + LEAF_CLEAR_SOH.data.u8[7] = 0x00; + transmit_can(&LEAF_CLEAR_SOH, can_config.battery); + // 7BB 8 02 50 81 FF FF FF FF FF // SOH reset OK + stateMachineClearSOH = 255; + break; + default: + break; + } +} + +uint32_t CyclicXorHash16Bit(uint32_t param_1, uint32_t param_2) { + bool bVar1; + uint32_t uVar2; + uint32_t uVar3; + uint32_t uVar4; + uint32_t uVar5; + uint32_t uVar6; + uint32_t uVar7; + uint32_t uVar8; + uint32_t uVar9; + uint32_t uVar10; + uint32_t uVar11; + uint32_t iVar12; + + param_1 = param_1 & 0xffff; + param_2 = param_2 & 0xffff; + uVar10 = 0xffff; + iVar12 = 2; + do { + uVar2 = param_2; + if ((param_1 & 1) == 1) { + uVar2 = param_1 >> 1; + } + uVar3 = param_2; + if ((param_1 >> 1 & 1) == 1) { + uVar3 = param_1 >> 2; + } + uVar4 = param_2; + if ((param_1 >> 2 & 1) == 1) { + uVar4 = param_1 >> 3; + } + uVar5 = param_2; + if ((param_1 >> 3 & 1) == 1) { + uVar5 = param_1 >> 4; + } + uVar6 = param_2; + if ((param_1 >> 4 & 1) == 1) { + uVar6 = param_1 >> 5; + } + uVar7 = param_2; + if ((param_1 >> 5 & 1) == 1) { + uVar7 = param_1 >> 6; + } + uVar11 = param_1 >> 7; + uVar8 = param_2; + if ((param_1 >> 6 & 1) == 1) { + uVar8 = uVar11; + } + param_1 = param_1 >> 8; + uVar9 = param_2; + if ((uVar11 & 1) == 1) { + uVar9 = param_1; + } + uVar10 = + (((((((((((((((uVar10 & 0x7fff) << 1 ^ uVar2) & 0x7fff) << 1 ^ uVar3) & 0x7fff) << 1 ^ uVar4) & 0x7fff) << 1 ^ + uVar5) & + 0x7fff) + << 1 ^ + uVar6) & + 0x7fff) + << 1 ^ + uVar7) & + 0x7fff) + << 1 ^ + uVar8) & + 0x7fff) + << 1 ^ + uVar9; + bVar1 = iVar12 != 1; + iVar12 = iVar12 + -1; + } while (bVar1); + return uVar10; +} + +uint32_t ComputeMaskedXorProduct(uint32_t param_1, uint32_t param_2, uint32_t param_3) { + return (param_3 ^ 0x780 | param_2 ^ 0x116) * ((param_1 & 0xffff) >> 8 ^ param_1 & 0xff) & 0xffff; +} + +short ShortMaskedSumAndProduct(short param_1, short param_2) { + unsigned short uVar1; + + uVar1 = param_2 + param_1 * 0x5ba & 0xff; + return (uVar1 + param_1) * (uVar1 + param_2); +} + +uint32_t MaskedBitwiseRotateMultiply(uint32_t param_1, uint32_t param_2) { + uint32_t uVar1; + + param_1 = param_1 & 0xffff; + param_2 = param_2 & 0xffff; + uVar1 = param_2 & (param_1 | 0x5ba) & 0xf; + return ((uint32_t)param_1 >> uVar1 | param_1 << (0x10 - uVar1 & 0x1f)) * + (param_2 << uVar1 | (uint32_t)param_2 >> (0x10 - uVar1 & 0x1f)) & + 0xffff; +} + +uint32_t CryptAlgo(uint32_t param_1, uint32_t param_2, uint32_t param_3) { + uint32_t uVar1; + uint32_t uVar2; + uint32_t iVar3; + uint32_t iVar4; + + uVar1 = MaskedBitwiseRotateMultiply(param_2, param_3); + uVar2 = ShortMaskedSumAndProduct(param_2, param_3); + uVar1 = ComputeMaskedXorProduct(param_1, uVar1, uVar2); + uVar2 = ComputeMaskedXorProduct(param_1, uVar2, uVar1); + iVar3 = CyclicXorHash16Bit(uVar1, 0xffc4); + iVar4 = CyclicXorHash16Bit(uVar2, 0xffc4); + return iVar4 + iVar3 * 0x10000; +} + +void decodeChallengeData(uint32_t incomingChallenge, unsigned char* solvedChallenge) { + uint32_t uVar1; + uint32_t uVar2; + + uVar1 = CryptAlgo(0x609, 0xDD2, incomingChallenge >> 0x10); + uVar2 = CryptAlgo(incomingChallenge & 0xffff, incomingChallenge >> 0x10, 0x609); + *solvedChallenge = (unsigned char)uVar1; + solvedChallenge[1] = (unsigned char)uVar2; + solvedChallenge[2] = (unsigned char)((uint32_t)uVar2 >> 8); + solvedChallenge[3] = (unsigned char)((uint32_t)uVar1 >> 8); + solvedChallenge[4] = (unsigned char)((uint32_t)uVar2 >> 16); + solvedChallenge[5] = (unsigned char)((uint32_t)uVar1 >> 16); + solvedChallenge[6] = (unsigned char)((uint32_t)uVar2 >> 24); + solvedChallenge[7] = (unsigned char)((uint32_t)uVar1 >> 24); + return; +} + void setup_battery(void) { // Performs one time setup at startup #ifdef DEBUG_VIA_USB Serial.println("Nissan LEAF battery selected"); diff --git a/Software/src/battery/NISSAN-LEAF-BATTERY.h b/Software/src/battery/NISSAN-LEAF-BATTERY.h index 89ff4443..2d29c0d7 100644 --- a/Software/src/battery/NISSAN-LEAF-BATTERY.h +++ b/Software/src/battery/NISSAN-LEAF-BATTERY.h @@ -14,5 +14,13 @@ uint16_t Temp_fromRAW_to_F(uint16_t temperature); bool is_message_corrupt(CAN_frame rx_frame); void setup_battery(void); void transmit_can(CAN_frame* tx_frame, int interface); +void clearSOH(void); +//Cryptographic functions +void decodeChallengeData(uint32_t incomingChallenge, unsigned char* solvedChallenge); +uint32_t CyclicXorHash16Bit(uint32_t param_1, uint32_t param_2); +uint32_t ComputeMaskedXorProduct(uint32_t param_1, uint32_t param_2, uint32_t param_3); +short ShortMaskedSumAndProduct(short param_1, short param_2); +uint32_t MaskedBitwiseRotateMultiply(uint32_t param_1, uint32_t param_2); +uint32_t CryptAlgo(uint32_t param_1, uint32_t param_2, uint32_t param_3); #endif diff --git a/Software/src/datalayer/datalayer_extended.h b/Software/src/datalayer/datalayer_extended.h index e1ed206c..fb4df944 100644 --- a/Software/src/datalayer/datalayer_extended.h +++ b/Software/src/datalayer/datalayer_extended.h @@ -198,6 +198,18 @@ typedef struct { /** bool */ /** Heat request sent*/ bool HeaterSendRequest = false; + /** bool */ + /** User requesting SOH reset via WebUI*/ + bool UserRequestSOHreset = false; + /** uint32_t */ + /** Cryptographic challenge to be solved */ + uint32_t CryptoChallenge = 0; + /** uint32_t */ + /** Solution for crypto challenge, MSBs */ + uint32_t SolvedChallengeMSB = 0; + /** uint32_t */ + /** Solution for crypto challenge, LSBs */ + uint32_t SolvedChallengeLSB = 0; } DATALAYER_INFO_NISSAN_LEAF; diff --git a/Software/src/devboard/webserver/advanced_battery_html.cpp b/Software/src/devboard/webserver/advanced_battery_html.cpp index 9718f61e..40b86f01 100644 --- a/Software/src/devboard/webserver/advanced_battery_html.cpp +++ b/Software/src/devboard/webserver/advanced_battery_html.cpp @@ -292,6 +292,10 @@ String advanced_battery_processor(const String& var) { content += "

Heating stopped: " + String(datalayer_extended.nissanleaf.HeatingStop) + "

"; content += "

Heating started: " + String(datalayer_extended.nissanleaf.HeatingStart) + "

"; content += "

Heating requested: " + String(datalayer_extended.nissanleaf.HeaterSendRequest) + "

"; + content += ""; + content += "

CryptoChallenge: " + String(datalayer_extended.nissanleaf.CryptoChallenge) + "

"; + content += "

SolvedChallenge: " + String(datalayer_extended.nissanleaf.SolvedChallengeMSB) + + String(datalayer_extended.nissanleaf.SolvedChallengeLSB) + "

"; #endif #if !defined(TESLA_BATTERY) && !defined(NISSAN_LEAF_BATTERY) && !defined(BMW_I3_BATTERY) && \ @@ -302,6 +306,15 @@ String advanced_battery_processor(const String& var) { content += "
"; content += ""; return content; diff --git a/Software/src/devboard/webserver/webserver.cpp b/Software/src/devboard/webserver/webserver.cpp index 52568271..63cd2571 100644 --- a/Software/src/devboard/webserver/webserver.cpp +++ b/Software/src/devboard/webserver/webserver.cpp @@ -1,6 +1,7 @@ #include "webserver.h" #include #include "../../datalayer/datalayer.h" +#include "../../datalayer/datalayer_extended.h" #include "../../lib/bblanchon-ArduinoJson/ArduinoJson.h" #include "../utils/events.h" #include "../utils/led_handler.h" @@ -231,6 +232,15 @@ void init_webserver() { } }); + // Route for resetting SOH on Nissan LEAF batteries + server.on("/resetSOH", HTTP_GET, [](AsyncWebServerRequest* request) { + if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) { + return request->requestAuthentication(); + } + datalayer_extended.nissanleaf.UserRequestSOHreset = true; + request->send(200, "text/plain", "Updated successfully"); + }); + #ifdef TEST_FAKE_BATTERY // Route for editing FakeBatteryVoltage server.on("/updateFakeBatteryVoltage", HTTP_GET, [](AsyncWebServerRequest* request) { From f85c9bc0cc9331c909de7c0947955cd5f5e6b5eb Mon Sep 17 00:00:00 2001 From: mathl79 Date: Sun, 3 Nov 2024 13:22:39 +0100 Subject: [PATCH 097/210] pre-commit updated formatting --- Software/Software.ino | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Software/Software.ino b/Software/Software.ino index ecaa687f..17dad3e9 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -85,8 +85,8 @@ uint16_t mbPV[MB_RTU_NUM_VALUES]; // Process variable memory // Create a ModbusRTU server instance listening on Serial2 with 2000ms timeout ModbusServerRTU MBserver(Serial2, 2000); #endif -#if defined(SERIAL_LINK_RECEIVER) || defined(SERIAL_LINK_TRANSMITTER) -#define SERIAL_LINK_BAUDRATE 112500 +#if defined(SERIAL_LINK_RECEIVER) || defined(SERIAL_LINK_TRANSMITTER) +#define SERIAL_LINK_BAUDRATE 112500 #endif // Common charger parameters From 300652f19f26f9a853b75e8e7c28e5fbf25c037d Mon Sep 17 00:00:00 2001 From: rha Date: Sun, 3 Nov 2024 15:59:47 +0200 Subject: [PATCH 098/210] Rename frame1 to battery_info & manufactured epoch time --- Software/src/inverter/KOSTAL-RS485.cpp | 27 ++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/Software/src/inverter/KOSTAL-RS485.cpp b/Software/src/inverter/KOSTAL-RS485.cpp index 9dc59c55..18a10205 100644 --- a/Software/src/inverter/KOSTAL-RS485.cpp +++ b/Software/src/inverter/KOSTAL-RS485.cpp @@ -29,14 +29,21 @@ union f32b { byte b[4]; }; -uint8_t frame1[40] = {0x06, 0xE2, 0xFF, 0x02, 0xFF, 0x29, // Frame header + + + +uint8_t BATTERY_INFO[40] = {0x06, 0xE2, 0xFF, 0x02, 0xFF, 0x29, // Frame header 0x01, 0x08, 0x80, 0x43, // 256.063 Nominal voltage / 5*51.2=256 first byte 0x01 or 0x04 - 0xE4, 0x70, 0x8A, 0x5C, // These might be Umin & Unax, Uint16 - 0xB5, 0x02, 0xD3, 0x01, // Battery Serial number? Modbus register 527 - 0x01, 0x05, 0xC8, 0x41, // 25.0024 ? - 0xC2, 0x18, // Battery Firmware, modbus register 586 - 0x01, 0x03, 0x59, 0x42, // 0x00005942 = 54.25 ?? - 0x01, 0x01, 0x01, 0x02, 0x05, 0x02, 0xA0, 0x01, 0x01, 0x02, + 0xE4, 0x70, 0x8A, 0x5C, // Manufacture date (Epoch time) (BYD: GetBatteryInfo this[0x10ac]) + 0xB5, 0x02, 0xD3, 0x01, // Battery Serial number? Modbus register 527 - 0x10b0 + 0x01, 0x05, 0xC8, 0x41, // 0x10b4 + 0xC2, 0x18, // Battery Firmware, modbus register 586 (0x10b8) + 0x01, // Static (BYD: GetBatteryInfo this[0x10ba]) + 0x03, // ? + 0x59, 0x42, // Static (BYD: GetBatteryInfo this[0x10bc]) + 0x01, 0x01, // Static (BYD: GetBatteryInfo this[0x10be]) + 0x01, 0x02, + 0x05, 0x02, 0xA0, 0x01, 0x01, 0x02, 0x4D, // CRC 0x00}; // @@ -207,7 +214,7 @@ void update_RS485_registers_inverter() { nominal_voltage_dV = (((datalayer.battery.info.max_design_voltage_dV - datalayer.battery.info.min_design_voltage_dV) / 2) + datalayer.battery.info.min_design_voltage_dV); - float2frameMSB(frame1, (float)nominal_voltage_dV / 10, 8); + float2frameMSB(BATTERY_INFO, (float)nominal_voltage_dV / 10, 8); float2frameMSB(frame2, (float)datalayer.battery.info.max_design_voltage_dV / 10, 12); @@ -250,7 +257,7 @@ void update_RS485_registers_inverter() { register_content_ok = true; - frame1[38] = calculate_frame1_crc(frame1, 38); + BATTERY_INFO[38] = calculate_frame1_crc(BATTERY_INFO, 38); if (incoming_message_counter > 0) { incoming_message_counter--; @@ -336,7 +343,7 @@ void receive_RS485() // Runs as fast as possible to handle the serial stream } if (headerA && (RS485_RXFRAME[6] == 0x4A) && (RS485_RXFRAME[7] == 0x08)) { // "frame 1" - send_kostal(frame1, 40); + send_kostal(BATTERY_INFO, 40); if (!startupMillis) { startupMillis = currentMillis; } From 190d60ed9f9599b55296ff77a7dfd91be1d3703f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Sun, 3 Nov 2024 17:13:27 +0200 Subject: [PATCH 099/210] Update Software.ino --- Software/Software.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Software/Software.ino b/Software/Software.ino index 17dad3e9..32532bcc 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -53,7 +53,7 @@ Preferences settings; // Store user settings // The current software version, shown on webserver -const char* version_number = "7.6.dev"; +const char* version_number = "7.6.0"; // Interval settings uint16_t intervalUpdateValues = INTERVAL_1_S; // Interval at which to update inverter values / Modbus registers From e21001ae5b108b0d676b73bbf65f12de611b5a7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Sun, 3 Nov 2024 18:08:29 +0200 Subject: [PATCH 100/210] Update Software.ino --- Software/Software.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Software/Software.ino b/Software/Software.ino index 32532bcc..c627f90f 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -53,7 +53,7 @@ Preferences settings; // Store user settings // The current software version, shown on webserver -const char* version_number = "7.6.0"; +const char* version_number = "7.7.dev"; // Interval settings uint16_t intervalUpdateValues = INTERVAL_1_S; // Interval at which to update inverter values / Modbus registers From a2deca0f20ade19e79ce9170c4ae079d4b63dbb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Sun, 3 Nov 2024 19:36:52 +0200 Subject: [PATCH 101/210] Added info to webserver incase challenge failed --- Software/src/battery/NISSAN-LEAF-BATTERY.cpp | 3 ++- Software/src/datalayer/datalayer_extended.h | 3 +++ Software/src/devboard/webserver/advanced_battery_html.cpp | 1 + 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Software/src/battery/NISSAN-LEAF-BATTERY.cpp b/Software/src/battery/NISSAN-LEAF-BATTERY.cpp index 7c48fe37..5c39dfd9 100644 --- a/Software/src/battery/NISSAN-LEAF-BATTERY.cpp +++ b/Software/src/battery/NISSAN-LEAF-BATTERY.cpp @@ -354,6 +354,7 @@ void update_values_battery() { /* This function maps all the values fetched via ((solvedChallenge[7] << 24) | (solvedChallenge[6] << 16) | (solvedChallenge[5] << 8) | solvedChallenge[4]); datalayer_extended.nissanleaf.SolvedChallengeLSB = ((solvedChallenge[3] << 24) | (solvedChallenge[2] << 16) | (solvedChallenge[1] << 8) | solvedChallenge[0]); + datalayer_extended.nissanleaf.challengeFailed = challengeFailed; // Update requests from webserver datalayer if (datalayer_extended.nissanleaf.UserRequestSOHreset) { @@ -1287,7 +1288,7 @@ uint16_t Temp_fromRAW_to_F(uint16_t temperature) { //This function feels horrib } void clearSOH(void) { - + challengeFailed = false; stop_battery_query = true; hold_off_with_polling_10seconds = 10; // Active battery polling is paused for 100 seconds diff --git a/Software/src/datalayer/datalayer_extended.h b/Software/src/datalayer/datalayer_extended.h index fb4df944..626cd8db 100644 --- a/Software/src/datalayer/datalayer_extended.h +++ b/Software/src/datalayer/datalayer_extended.h @@ -201,6 +201,9 @@ typedef struct { /** bool */ /** User requesting SOH reset via WebUI*/ bool UserRequestSOHreset = false; + /** bool */ + /** True if the crypto challenge response from BMS is signalling a failed attempt*/ + bool challengeFailed = false; /** uint32_t */ /** Cryptographic challenge to be solved */ uint32_t CryptoChallenge = 0; diff --git a/Software/src/devboard/webserver/advanced_battery_html.cpp b/Software/src/devboard/webserver/advanced_battery_html.cpp index 40b86f01..d15654b8 100644 --- a/Software/src/devboard/webserver/advanced_battery_html.cpp +++ b/Software/src/devboard/webserver/advanced_battery_html.cpp @@ -296,6 +296,7 @@ String advanced_battery_processor(const String& var) { content += "

CryptoChallenge: " + String(datalayer_extended.nissanleaf.CryptoChallenge) + "

"; content += "

SolvedChallenge: " + String(datalayer_extended.nissanleaf.SolvedChallengeMSB) + String(datalayer_extended.nissanleaf.SolvedChallengeLSB) + "

"; + content += "

Challenge failed: " + String(datalayer_extended.nissanleaf.challengeFailed) + "

"; #endif #if !defined(TESLA_BATTERY) && !defined(NISSAN_LEAF_BATTERY) && !defined(BMW_I3_BATTERY) && \ From 3a9426dcf25c75f211fbd51ee3496fd4bf49f6e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Sun, 3 Nov 2024 20:03:11 +0200 Subject: [PATCH 102/210] chage from 66 to 65 PID --- Software/src/battery/NISSAN-LEAF-BATTERY.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Software/src/battery/NISSAN-LEAF-BATTERY.cpp b/Software/src/battery/NISSAN-LEAF-BATTERY.cpp index 5c39dfd9..dd0822d7 100644 --- a/Software/src/battery/NISSAN-LEAF-BATTERY.cpp +++ b/Software/src/battery/NISSAN-LEAF-BATTERY.cpp @@ -1325,7 +1325,7 @@ void clearSOH(void) { case 3: // Request challenge to solve LEAF_CLEAR_SOH.data.u8[0] = 0x02; LEAF_CLEAR_SOH.data.u8[1] = 0x27; - LEAF_CLEAR_SOH.data.u8[2] = 0x65; + LEAF_CLEAR_SOH.data.u8[2] = 0x65; // 0x66 on 24kWh? LEAF_CLEAR_SOH.data.u8[3] = 0x00; LEAF_CLEAR_SOH.data.u8[4] = 0x00; LEAF_CLEAR_SOH.data.u8[5] = 0x00; @@ -1339,7 +1339,7 @@ void clearSOH(void) { LEAF_CLEAR_SOH.data.u8[0] = 0x10; LEAF_CLEAR_SOH.data.u8[1] = 0x0A; LEAF_CLEAR_SOH.data.u8[2] = 0x27; - LEAF_CLEAR_SOH.data.u8[3] = 0x66; + LEAF_CLEAR_SOH.data.u8[3] = 0x65; // 0x66 on 24kWh? LEAF_CLEAR_SOH.data.u8[4] = 0x77; LEAF_CLEAR_SOH.data.u8[5] = solvedChallenge[0]; LEAF_CLEAR_SOH.data.u8[6] = solvedChallenge[1]; From ecc71eec60b1007e50f96d52cfc024e29b4bb613 Mon Sep 17 00:00:00 2001 From: amarofarinha <151563493+amarofarinha@users.noreply.github.com> Date: Sun, 3 Nov 2024 18:30:56 +0000 Subject: [PATCH 103/210] Bug Fix for Precharge Contactor Handling in SHUTDOWN_REQUESTED State and Web Interface Enhancement for Contactor Status Display --- Software/Software.ino | 1 + Software/src/devboard/webserver/webserver.cpp | 21 +++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/Software/Software.ino b/Software/Software.ino index c627f90f..588aa5e0 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -744,6 +744,7 @@ void handle_contactors() { if (timeSpentInFaultedMode > MAX_ALLOWED_FAULT_TICKS || (datalayer.system.settings.equipment_stop_active && contactorStatus != SHUTDOWN_REQUESTED)) { contactorStatus = SHUTDOWN_REQUESTED; + datalayer.system.settings.equipment_stop_active = true; } if (contactorStatus == SHUTDOWN_REQUESTED && !datalayer.system.settings.equipment_stop_active) { contactorStatus = DISCONNECTED; diff --git a/Software/src/devboard/webserver/webserver.cpp b/Software/src/devboard/webserver/webserver.cpp index 52568271..beae4c47 100644 --- a/Software/src/devboard/webserver/webserver.cpp +++ b/Software/src/devboard/webserver/webserver.cpp @@ -719,6 +719,27 @@ String processor(const String& var) { content += "OFF"; } content += ""; + + content += "

Pre Charge: "; + if (digitalRead(PRECHARGE_PIN) == HIGH) { + content += ""; + } else { + content += ""; + } + content += " Cont. Neg.: "; + if (digitalRead(NEGATIVE_CONTACTOR_PIN) == HIGH) { + content += ""; + } else { + content += ""; + } + + content += " Cont. Pos.: "; + if (digitalRead(POSITIVE_CONTACTOR_PIN) == HIGH) { + content += ""; + } else { + content += ""; + } + content += "

"; #endif // Close the block From 9e3df933afdef1f93bd4401e26615471d7b7088e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Sun, 3 Nov 2024 21:08:56 +0200 Subject: [PATCH 104/210] Add writing WWUP_PIN off when paused --- Software/src/battery/BMW-I3-BATTERY.cpp | 6 ++++++ Software/src/datalayer/datalayer.h | 3 +++ Software/src/devboard/safety/safety.cpp | 2 ++ 3 files changed, 11 insertions(+) diff --git a/Software/src/battery/BMW-I3-BATTERY.cpp b/Software/src/battery/BMW-I3-BATTERY.cpp index 2b1f68fa..b4b0ac61 100644 --- a/Software/src/battery/BMW-I3-BATTERY.cpp +++ b/Software/src/battery/BMW-I3-BATTERY.cpp @@ -430,6 +430,12 @@ void update_values_battery2() { //This function maps all the values fetched via } void update_values_battery() { //This function maps all the values fetched via CAN to the battery datalayer + if (datalayer.system.status.pause_active == true) { + digitalWrite(WUP_PIN, LOW); // Turn off WUP_PIN + } else { + digitalWrite(WUP_PIN, HIGH); // Wake up the battery + } + if (!battery_awake) { return; } diff --git a/Software/src/datalayer/datalayer.h b/Software/src/datalayer/datalayer.h index d0b6e840..a2d34b4e 100644 --- a/Software/src/datalayer/datalayer.h +++ b/Software/src/datalayer/datalayer.h @@ -177,6 +177,9 @@ typedef struct { * we report the inverter as missing entirely on the CAN bus. */ uint8_t CAN_inverter_still_alive = CAN_STILL_ALIVE; + + /** True if a pause is active, requested via webserver */ + bool pause_active = false; /** True if the battery allows for the contactors to close */ bool battery_allows_contactor_closing = false; /** True if the second battery allows for the contactors to close */ diff --git a/Software/src/devboard/safety/safety.cpp b/Software/src/devboard/safety/safety.cpp index 4825444e..0657a42a 100644 --- a/Software/src/devboard/safety/safety.cpp +++ b/Software/src/devboard/safety/safety.cpp @@ -257,6 +257,7 @@ void setBatteryPause(bool pause_battery, bool pause_CAN, bool equipment_stop, bo set_event(EVENT_PAUSE_BEGIN, 1); emulator_pause_request_ON = true; emulator_pause_status = PAUSING; + datalayer.system.status.pause_active = true; datalayer.battery.status.max_discharge_power_W = 0; datalayer.battery.status.max_charge_power_W = 0; #ifdef DOUBLE_BATTERY @@ -270,6 +271,7 @@ void setBatteryPause(bool pause_battery, bool pause_CAN, bool equipment_stop, bo emulator_pause_request_ON = false; emulator_pause_CAN_send_ON = false; emulator_pause_status = RESUMING; + datalayer.system.status.pause_active = false; clear_event(EVENT_PAUSE_END); } From 12103aaebc37d07a6e5d4af18102f242970d3d3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Sun, 3 Nov 2024 21:14:27 +0200 Subject: [PATCH 105/210] WUP_PIN now reacts on equipment stop --- Software/src/battery/BMW-I3-BATTERY.cpp | 2 +- Software/src/datalayer/datalayer.h | 3 --- Software/src/devboard/safety/safety.cpp | 2 -- 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/Software/src/battery/BMW-I3-BATTERY.cpp b/Software/src/battery/BMW-I3-BATTERY.cpp index b4b0ac61..7a5dde11 100644 --- a/Software/src/battery/BMW-I3-BATTERY.cpp +++ b/Software/src/battery/BMW-I3-BATTERY.cpp @@ -430,7 +430,7 @@ void update_values_battery2() { //This function maps all the values fetched via } void update_values_battery() { //This function maps all the values fetched via CAN to the battery datalayer - if (datalayer.system.status.pause_active == true) { + if (datalayer.system.settings.equipment_stop_active == true) { digitalWrite(WUP_PIN, LOW); // Turn off WUP_PIN } else { digitalWrite(WUP_PIN, HIGH); // Wake up the battery diff --git a/Software/src/datalayer/datalayer.h b/Software/src/datalayer/datalayer.h index a2d34b4e..d0b6e840 100644 --- a/Software/src/datalayer/datalayer.h +++ b/Software/src/datalayer/datalayer.h @@ -177,9 +177,6 @@ typedef struct { * we report the inverter as missing entirely on the CAN bus. */ uint8_t CAN_inverter_still_alive = CAN_STILL_ALIVE; - - /** True if a pause is active, requested via webserver */ - bool pause_active = false; /** True if the battery allows for the contactors to close */ bool battery_allows_contactor_closing = false; /** True if the second battery allows for the contactors to close */ diff --git a/Software/src/devboard/safety/safety.cpp b/Software/src/devboard/safety/safety.cpp index 0657a42a..4825444e 100644 --- a/Software/src/devboard/safety/safety.cpp +++ b/Software/src/devboard/safety/safety.cpp @@ -257,7 +257,6 @@ void setBatteryPause(bool pause_battery, bool pause_CAN, bool equipment_stop, bo set_event(EVENT_PAUSE_BEGIN, 1); emulator_pause_request_ON = true; emulator_pause_status = PAUSING; - datalayer.system.status.pause_active = true; datalayer.battery.status.max_discharge_power_W = 0; datalayer.battery.status.max_charge_power_W = 0; #ifdef DOUBLE_BATTERY @@ -271,7 +270,6 @@ void setBatteryPause(bool pause_battery, bool pause_CAN, bool equipment_stop, bo emulator_pause_request_ON = false; emulator_pause_CAN_send_ON = false; emulator_pause_status = RESUMING; - datalayer.system.status.pause_active = false; clear_event(EVENT_PAUSE_END); } From 362a57b6dc88d819f2cd78873309d7dcb19c0129 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Sun, 3 Nov 2024 22:22:59 +0200 Subject: [PATCH 106/210] Added logging for CAN sending --- Software/src/battery/NISSAN-LEAF-BATTERY.cpp | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/Software/src/battery/NISSAN-LEAF-BATTERY.cpp b/Software/src/battery/NISSAN-LEAF-BATTERY.cpp index dd0822d7..76bf7e13 100644 --- a/Software/src/battery/NISSAN-LEAF-BATTERY.cpp +++ b/Software/src/battery/NISSAN-LEAF-BATTERY.cpp @@ -1288,12 +1288,12 @@ uint16_t Temp_fromRAW_to_F(uint16_t temperature) { //This function feels horrib } void clearSOH(void) { - challengeFailed = false; stop_battery_query = true; hold_off_with_polling_10seconds = 10; // Active battery polling is paused for 100 seconds switch (stateMachineClearSOH) { case 0: // Wait until polling actually stops + challengeFailed = false; stateMachineClearSOH = 1; break; case 1: // Set CAN_PROCESS_FLAG to 0xC0 @@ -1325,7 +1325,7 @@ void clearSOH(void) { case 3: // Request challenge to solve LEAF_CLEAR_SOH.data.u8[0] = 0x02; LEAF_CLEAR_SOH.data.u8[1] = 0x27; - LEAF_CLEAR_SOH.data.u8[2] = 0x65; // 0x66 on 24kWh? + LEAF_CLEAR_SOH.data.u8[2] = 0x65; LEAF_CLEAR_SOH.data.u8[3] = 0x00; LEAF_CLEAR_SOH.data.u8[4] = 0x00; LEAF_CLEAR_SOH.data.u8[5] = 0x00; @@ -1339,7 +1339,7 @@ void clearSOH(void) { LEAF_CLEAR_SOH.data.u8[0] = 0x10; LEAF_CLEAR_SOH.data.u8[1] = 0x0A; LEAF_CLEAR_SOH.data.u8[2] = 0x27; - LEAF_CLEAR_SOH.data.u8[3] = 0x65; // 0x66 on 24kWh? + LEAF_CLEAR_SOH.data.u8[3] = 0x66; LEAF_CLEAR_SOH.data.u8[4] = 0x77; LEAF_CLEAR_SOH.data.u8[5] = solvedChallenge[0]; LEAF_CLEAR_SOH.data.u8[6] = solvedChallenge[1]; @@ -1405,6 +1405,18 @@ void clearSOH(void) { default: break; } + // All CAN messages semt will be logged via serial during development of this function + Serial.print(millis()); // Example printout, time, ID, length, data: 7553 7B9 8 FF C0 B9 EA 0 0 2 5D + Serial.print(" "); + Serial.print(LEAF_CLEAR_SOH.ID, HEX); + Serial.print(" "); + Serial.print(LEAF_CLEAR_SOH.DLC); + Serial.print(" "); + for (int i = 0; i < LEAF_CLEAR_SOH.DLC; ++i) { + Serial.print(LEAF_CLEAR_SOH.data.u8[i], HEX); + Serial.print(" "); + } + Serial.println(""); } uint32_t CyclicXorHash16Bit(uint32_t param_1, uint32_t param_2) { From 0cc490481b00ab7ab9da3f403de6e96b50cda897 Mon Sep 17 00:00:00 2001 From: wjcloudy <56305354+wjcloudy@users.noreply.github.com> Date: Sun, 3 Nov 2024 21:25:26 +0000 Subject: [PATCH 107/210] 0.3alpha - Update BMW-IX-BATTERY.h Added more values including pack min/max. min/max cell voltage is now monitored for ensure it's changing (an extra safety check in case it goes stale) --- Software/src/battery/BMW-IX-BATTERY.h | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Software/src/battery/BMW-IX-BATTERY.h b/Software/src/battery/BMW-IX-BATTERY.h index 46ea9820..657811c8 100644 --- a/Software/src/battery/BMW-IX-BATTERY.h +++ b/Software/src/battery/BMW-IX-BATTERY.h @@ -6,11 +6,15 @@ #define BATTERY_SELECTED //#define WUP_PIN 25 //Not used -#define MAX_PACK_VOLTAGE_DV 4600 //4600 = 460.0V +#define MAX_PACK_VOLTAGE_DV 4650 //4650 = 465.0V #define MIN_PACK_VOLTAGE_DV 3000 -#define MAX_CELL_DEVIATION_MV 500 -#define MAX_CELL_VOLTAGE_MV 4250 //Battery is put into emergency stop if one cell goes over this value -#define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value +#define MAX_CELL_DEVIATION_MV 250 +#define MAX_CELL_VOLTAGE_MV 4300 //Battery is put into emergency stop if one cell goes over this value +#define MIN_CELL_VOLTAGE_MV 2800 //Battery is put into emergency stop if one cell goes below this value +#define MAX_CHARGE_POWER_ALLOWED_W 5000 +#define MAX_CHARGE_POWER_WHEN_TOPBALANCING_W 500 +#define RAMPDOWN_SOC 9000 // (90.00) SOC% to start ramping down from max charge power towards 0 at 100.00% +#define STALE_PERIOD_CONFIG 180000; //Number of milliseconds before critical values are classed as stale/stuck 180000 = 180 seconds void setup_battery(void); void transmit_can(CAN_frame* tx_frame, int interface); From f1491dbe8177173195270818710e580bab939828 Mon Sep 17 00:00:00 2001 From: wjcloudy <56305354+wjcloudy@users.noreply.github.com> Date: Sun, 3 Nov 2024 21:26:02 +0000 Subject: [PATCH 108/210] 0.3alpha - Update BMW-IX-BATTERY.cpp Added more values including pack min/max. min/max cell voltage is now monitored for ensure it's changing (an extra safety check in case it goes stale) --- Software/src/battery/BMW-IX-BATTERY.cpp | 153 ++++++++++++++++++------ 1 file changed, 117 insertions(+), 36 deletions(-) diff --git a/Software/src/battery/BMW-IX-BATTERY.cpp b/Software/src/battery/BMW-IX-BATTERY.cpp index 4f4627f8..f7569277 100644 --- a/Software/src/battery/BMW-IX-BATTERY.cpp +++ b/Software/src/battery/BMW-IX-BATTERY.cpp @@ -56,16 +56,11 @@ No vehicle log available, SME asks for: 0xAA (EME2) 0x?? Suspect there is a drive mode flag somewhere - balancing might only be active in some modes - - TODO - - Request batt serial number on F1 8C (already parsing RX) -- Check PWM required from ACSM -- Use voltage qualifier for extended data? +- Check PWM state required from ACSM - More Balancing values -- Check for stale min/max values -- Prevent fault state on SME reset +- MIN /max cell voltag e- implement safety limits? */ @@ -119,10 +114,12 @@ CAN_frame BMWiX_6F4_REQUEST_UPTIME = {.FD = true, .ext_ID = false, .DLC = 5, .ID CAN_frame BMWiX_6F4_REQUEST_HVIL = {.FD = true, .ext_ID = false, .DLC = 5, .ID = 0x6F4, .data = {0x07, 0x03, 0x22, 0xE5, 0x69}}; // Request HVIL State CAN_frame BMWiX_6F4_REQUEST_BALANCINGSTATUS = {.FD = true, .ext_ID = false, .DLC = 5, .ID = 0x6F4, .data = {0x07, 0x03, 0x22, 0xE4, 0xCA}}; // Request Balancing Data CAN_frame BMWiX_6F4_REQUEST_MAX_CHARGE_DISCHARGE_AMPS = {.FD = true, .ext_ID = false, .DLC = 5, .ID = 0x6F4, .data = {0x07, 0x03, 0x22, 0xE5, 0x62}}; // Request allowable charge discharge amps -CAN_frame BMWiX_6F4_REQUEST_QUALIFIER_CHECK = {.FD = true, .ext_ID = false, .DLC = 5, .ID = 0x6F4, .data = {0x07, 0x03, 0x22, 0xE5, 0x4B}}; // Request HV Voltage Qualifier +CAN_frame BMWiX_6F4_REQUEST_VOLTAGE_QUALIFIER_CHECK = {.FD = true, .ext_ID = false, .DLC = 5, .ID = 0x6F4, .data = {0x07, 0x03, 0x22, 0xE5, 0x4B}}; // Request HV Voltage Qualifier CAN_frame BMWiX_6F4_REQUEST_CONTACTORS_CLOSE = {.FD = true, .ext_ID = false, .DLC = 6, .ID = 0x6F4, .data = {0x07, 0x03, 0x22, 0xE5, 0x51, 0x01}}; // Request Contactors Close - Unconfirmed CAN_frame BMWiX_6F4_REQUEST_CONTACTORS_OPEN = {.FD = true, .ext_ID = false, .DLC = 6, .ID = 0x6F4, .data = {0x07, 0x03, 0x22, 0xE5, 0x51, 0x01}}; // Request Contactors Open - Unconfirmed CAN_frame BMWiX_6F4_REQUEST_BALANCING_START = {.FD = true, .ext_ID = false, .DLC = 6, .ID = 0x6F4, .data = {0xF4, 0x04, 0x71, 0x01, 0xAE, 0x77}}; // Request Balancing command? +CAN_frame BMWiX_6F4_REQUEST_PACK_VOLTAGE_LIMITS = {.FD = true, .ext_ID = false, .DLC = 5, .ID = 0x6F4, .data = {0x07, 0x03, 0x22, 0xE5, 0x4C}}; // Request pack voltage limits + CAN_frame BMWiX_6F4_CONTINUE_DATA = {.FD = true, .ext_ID = false, .DLC = 4, .ID = 0x6F4, .data = {0x07, 0x30, 0x00, 0x02}}; @@ -152,11 +149,13 @@ CAN_frame* UDS_REQUESTS100MS[] = { &BMWiX_6F4_REQUEST_EOL_ISO, &BMWiX_6F4_REQUEST_HVIL, &BMWiX_6F4_REQUEST_MAX_CHARGE_DISCHARGE_AMPS, - &BMWiX_6F4_REQUEST_BALANCINGSTATUS + &BMWiX_6F4_REQUEST_BALANCINGSTATUS, + &BMWiX_6F4_REQUEST_PACK_VOLTAGE_LIMITS }; int numUDSreqs = sizeof(UDS_REQUESTS100MS) / sizeof(UDS_REQUESTS100MS[0]); // Number of elements in the array //iX Intermediate vars +static bool battery_info_available = false; static uint32_t battery_serial_number = 0; static int32_t battery_current = 0; static int16_t battery_voltage = 370; @@ -168,8 +167,8 @@ static int16_t max_soc_state = 50; static int16_t min_soh_state = 99;// Uses E5 45, also available in 78 73 static int16_t avg_soh_state = 99;// Uses E5 45, also available in 78 73 static int16_t max_soh_state = 99;// Uses E5 45, also available in 78 73 -static uint16_t battery_max_charge_voltage = 0; -static uint16_t battery_min_discharge_voltage = 0; +static uint16_t max_design_voltage = 0; +static uint16_t min_design_voltage = 0; static int32_t remaining_capacity = 0; static int32_t max_capacity = 0; static int16_t min_battery_temperature = 0; @@ -178,6 +177,8 @@ static int16_t max_battery_temperature = 0; static int16_t main_contactor_temperature = 0; static int16_t min_cell_voltage = 0; static int16_t max_cell_voltage = 0; +static unsigned long min_cell_voltage_lastchanged = 0; +static unsigned long max_cell_voltage_lastchanged = 0; static unsigned min_cell_voltage_lastreceived = 0; static unsigned max_cell_voltage_lastreceived = 0; static int16_t battery_power = 0; @@ -200,6 +201,9 @@ static uint8_t pyro_status_pss1 = 0; //Using AC 93 static uint8_t pyro_status_pss4 = 0; //Using AC 93 static uint8_t pyro_status_pss6 = 0; //Using AC 93 static uint8_t uds_req_id_counter = 0; +const unsigned long STALE_PERIOD = STALE_PERIOD_CONFIG ; // Time in milliseconds to check for staleness (e.g., 5000 ms = 5 seconds) + + static byte iX_0C0_counter = 0xF0; // Initialize to 0xF0 @@ -207,6 +211,22 @@ static byte iX_0C0_counter = 0xF0; // Initialize to 0xF0 static uint8_t current_cell_polled = 0; +// Function to check if a value has gone stale over a specified time period +bool isStale(int16_t currentValue, uint16_t &lastValue, unsigned long &lastChangeTime) { + unsigned long currentTime = millis(); + + // Check if the value has changed + if (currentValue != lastValue) { + // Update the last change time and value + lastChangeTime = currentTime; + lastValue = currentValue; + return false; // Value is fresh because it has changed + } + + // Check if the value has stayed the same for the specified staleness period + return (currentTime - lastChangeTime >= STALE_PERIOD); +} + static uint8_t increment_uds_req_id_counter(uint8_t index) { index++; if (index >= numUDSreqs) { @@ -246,9 +266,23 @@ void update_values_battery() { //This function maps all the values fetched via datalayer.battery.status.soh_pptt = min_soh_state; - datalayer.battery.status.max_discharge_power_W = 10000; //Aux HV Port has 100A Fuse + datalayer.battery.status.max_discharge_power_W = 3200; //10000; //Aux HV Port has 100A Fuse + + //datalayer.battery.status.max_charge_power_W = 3200; //10000; //Aux HV Port has 100A Fuse Moved to Ramping + + + // Charge power is set in .h file + if (datalayer.battery.status.real_soc > 9900) { + datalayer.battery.status.max_charge_power_W = MAX_CHARGE_POWER_WHEN_TOPBALANCING_W; + } else if (datalayer.battery.status.real_soc > RAMPDOWN_SOC) { + // When real SOC is between RAMPDOWN_SOC-99%, ramp the value between Max<->0 + datalayer.battery.status.max_charge_power_W = + MAX_CHARGE_POWER_ALLOWED_W * + (1 - (datalayer.battery.status.real_soc - RAMPDOWN_SOC) / (10000.0 - RAMPDOWN_SOC)); + } else { // No limits, max charging power allowed + datalayer.battery.status.max_charge_power_W = MAX_CHARGE_POWER_ALLOWED_W; + } - datalayer.battery.status.max_charge_power_W = 10000; //Aux HV Port has 100A Fuse battery_power = (datalayer.battery.status.current_dA * (datalayer.battery.status.voltage_dV / 100)); @@ -258,11 +292,31 @@ void update_values_battery() { //This function maps all the values fetched via datalayer.battery.status.temperature_max_dC = max_battery_temperature; - datalayer.battery.status.cell_min_voltage_mV = min_cell_voltage; - - datalayer.battery.status.cell_max_voltage_mV = max_cell_voltage; - datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + + if (isStale(min_cell_voltage, datalayer.battery.status.cell_min_voltage_mV, min_cell_voltage_lastchanged)) {//TODO prevent flipflop after error + Serial.println("min_cell_voltage has gone stale."); + datalayer.battery.status.cell_min_voltage_mV = 9999; //Stale values force stop + set_event(EVENT_CAN_RX_FAILURE,0); + } else { + datalayer.battery.status.cell_min_voltage_mV = min_cell_voltage; //Value is alive + } + + if (isStale(max_cell_voltage, datalayer.battery.status.cell_max_voltage_mV, max_cell_voltage_lastchanged)) { //TODO prevent flipflop after error + Serial.println("max_cell_voltage has gone stale."); + datalayer.battery.status.cell_max_voltage_mV = 9999; //Stale values force stop + set_event(EVENT_CAN_RX_FAILURE,0); + } else { + datalayer.battery.status.cell_max_voltage_mV = max_cell_voltage; //Value is alive + } + + datalayer_extended.bmwix.min_cell_voltage_data_age = (millis() - min_cell_voltage_lastchanged); + + datalayer_extended.bmwix.max_cell_voltage_data_age = (millis() - max_cell_voltage_lastchanged); + + datalayer.battery.info.max_design_voltage_dV = max_design_voltage; + + datalayer.battery.info.min_design_voltage_dV = min_design_voltage; datalayer.battery.info.number_of_cells = 108; //init with 108S before autodetection @@ -282,7 +336,7 @@ void update_values_battery() { //This function maps all the values fetched via datalayer_extended.bmwix.iso_safety_negative = iso_safety_negative; - datalayer_extended.bmwix.iso_safety_parallel= iso_safety_parallel; + datalayer_extended.bmwix.iso_safety_parallel = iso_safety_parallel; datalayer_extended.bmwix.allowable_charge_amps = allowable_charge_amps; @@ -290,6 +344,20 @@ void update_values_battery() { //This function maps all the values fetched via datalayer_extended.bmwix.balancing_status = balancing_status; + datalayer_extended.bmwix.battery_voltage_after_contactor = battery_voltage_after_contactor; + + + + + + if (battery_info_available) { + // If we have data from battery - override the defaults to suit + datalayer.battery.info.max_design_voltage_dV = max_design_voltage; + datalayer.battery.info.min_design_voltage_dV = min_design_voltage; + datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV; + datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV; + } + } void receive_can_battery(CAN_frame rx_frame) { @@ -366,15 +434,15 @@ void receive_can_battery(CAN_frame rx_frame) { } if (rx_frame.DLC = 7 && rx_frame.data.u8[4] == 0x4A) { //Main Battery Voltage (After Contactor) - battery_voltage_after_contactor = (rx_frame.data.u8[5] <<8 | rx_frame.data.u8[6]); + battery_voltage_after_contactor = (rx_frame.data.u8[5] <<8 | rx_frame.data.u8[6])/10; } if (rx_frame.DLC = 12 && rx_frame.data.u8[4] == 0xE5 && rx_frame.data.u8[5] == 0x61) { //Current amps 32bit signed MSB. dA . negative is discharge - battery_current = (int32_t)((rx_frame.data.u8[5] << 24) | - (rx_frame.data.u8[6] << 16) | - (rx_frame.data.u8[7] << 8) | - rx_frame.data.u8[8]); + battery_current = ((int32_t)((rx_frame.data.u8[6] << 24) | + (rx_frame.data.u8[7] << 16) | + (rx_frame.data.u8[8] << 8) | + rx_frame.data.u8[9]))*0.1; } if (rx_frame.DLC = 64 && rx_frame.data.u8[4] == 0xE4 && rx_frame.data.u8[5] == 0xCA) { //Balancing Data @@ -398,8 +466,8 @@ void receive_can_battery(CAN_frame rx_frame) { } if (rx_frame.DLC = 10 && rx_frame.data.u8[4] == 0xE5 && rx_frame.data.u8[5] == 0x62) { //Max allowed charge and discharge current - Signed 16bit - allowable_charge_amps = (int16_t)((rx_frame.data.u8[6] <<8 | rx_frame.data.u8[7])); - allowable_discharge_amps = (int16_t)((rx_frame.data.u8[8] <<8 | rx_frame.data.u8[9])); + allowable_charge_amps = (int16_t)((rx_frame.data.u8[6] <<8 | rx_frame.data.u8[7]))/10; + allowable_discharge_amps = (int16_t)((rx_frame.data.u8[8] <<8 | rx_frame.data.u8[9]))/10; } if (rx_frame.DLC = 9 && rx_frame.data.u8[4] == 0xE5 && rx_frame.data.u8[5] == 0x4B) { //Max allowed charge and discharge current - Signed 16bit @@ -424,6 +492,9 @@ void receive_can_battery(CAN_frame rx_frame) { } if (rx_frame.DLC = 12 && rx_frame.data.u8[4] == 0xE5 && rx_frame.data.u8[5] == 0x53) { //Min and max cell voltage 10V = Qualifier Invalid + + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; //This is the most important safety values, if we receive this we reset CAN alive counter. + if((rx_frame.data.u8[6] <<8 | rx_frame.data.u8[7]) == 10000 && (rx_frame.data.u8[8] <<8 | rx_frame.data.u8[9]) == 10000){ //Qualifier Invalid Mode - Request Reboot #ifdef DEBUG_VIA_USB Serial.println("Cell MinMax Qualifier Invalid - Requesting BMS Reset"); @@ -447,9 +518,18 @@ void receive_can_battery(CAN_frame rx_frame) { if (rx_frame.DLC = 7 && rx_frame.data.u8[4] == 0xA7) { //Terminal 30 Voltage (12V SME supply) terminal30_12v_voltage = (rx_frame.data.u8[5] <<8 | rx_frame.data.u8[6]); } - if (rx_frame.DLC = 6 && rx_frame.data.u8[3] == 0xE5 && rx_frame.data.u8[4] == 0x69) { //HVIL Status + if (rx_frame.DLC = 6 && rx_frame.data.u8[0] == 0xF4 && rx_frame.data.u8[1] == 0x04 && rx_frame.data.u8[2] == 0x62 && rx_frame.data.u8[3] == 0xE5 && rx_frame.data.u8[4] == 0x69) { //HVIL Status hvil_status = ( rx_frame.data.u8[5]); } + + if (rx_frame.DLC = 12 && rx_frame.data.u8[2] == 0x07 && rx_frame.data.u8[3] == 0x62 && rx_frame.data.u8[4] == 0xE5 && rx_frame.data.u8[5] == 0x4C) { //Pack Voltage Limits + if ((rx_frame.data.u8[6] <<8 | rx_frame.data.u8[7]) < 4700 && (rx_frame.data.u8[8] <<8 | rx_frame.data.u8[9]) > 2600) { //Make sure values are plausible + battery_info_available = true; + max_design_voltage = (rx_frame.data.u8[6] <<8 | rx_frame.data.u8[7]); + min_design_voltage = (rx_frame.data.u8[8] <<8 | rx_frame.data.u8[9]); + } + } + if (rx_frame.DLC = 16 && rx_frame.data.u8[3] == 0xF1 && rx_frame.data.u8[4] == 0x8C ) { //Battery Serial Number //Convert hex bytes to ASCII characters and combine them into a string char numberString[11]; // 10 characters + null terminator @@ -549,24 +629,25 @@ void setup_battery(void) { // Performs one time setup at startup //Before we have started up and detected which battery is in use, use 108S values datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV; + datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV; + datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV; datalayer.battery.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV; datalayer.system.status.battery_allows_contactor_closing = true; - //pinMode(WUP_PIN, OUTPUT); // Not needed - can hold WUP pin High with iX BMS - //digitalWrite(WUP_PIN, HIGH); // Wake up the battery // Not needed - can hold WUP pin High with iX BMS + //pinMode(WUP_PIN, OUTPUT); // Not needed - can hold WUP pin High with iX BMS + //digitalWrite(WUP_PIN, HIGH); // Wake up the battery // Not needed - can hold WUP pin High with iX BMS - //Wake Battery - //Send SME Keep alive values 100ms + + //Send SME Keep alive values 100ms transmit_can(&BMWiX_510, can_config.battery); - //Send SME Keep alive values 200ms + //Send SME Keep alive values 200ms BMWiX_0C0.data.u8[0] = increment_0C0_counter(BMWiX_0C0.data.u8[0]); //Keep Alive 1 transmit_can(&BMWiX_0C0, can_config.battery); - - //Send SME Keep alive values 1000ms - //test disable transmit_can(&BMWiX_06D, can_config.battery); - //test disable transmit_can(&BMWiX_2F1, can_config.battery); - //test disable transmit_can(&BMWiX_439, can_config.battery); + //Send SME Keep alive values 1000ms + //Not needed transmit_can(&BMWiX_06D, can_config.battery); + //Not needed transmit_can(&BMWiX_2F1, can_config.battery); + //Not needed transmit_can(&BMWiX_439, can_config.battery); } #endif From ed05b7602ab35eb616a82e424c52458810f0ed0f Mon Sep 17 00:00:00 2001 From: wjcloudy <56305354+wjcloudy@users.noreply.github.com> Date: Sun, 3 Nov 2024 21:26:33 +0000 Subject: [PATCH 109/210] 0.3alpha - Update datalayer_extended.h Added more values including pack min/max. min/max cell voltage is now monitored for ensure it's changing (an extra safety check in case it goes stale) --- Software/src/datalayer/datalayer_extended.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Software/src/datalayer/datalayer_extended.h b/Software/src/datalayer/datalayer_extended.h index 18fd8f4e..0608b2cc 100644 --- a/Software/src/datalayer/datalayer_extended.h +++ b/Software/src/datalayer/datalayer_extended.h @@ -22,6 +22,9 @@ typedef struct { int32_t allowable_charge_amps = 0; int32_t allowable_discharge_amps = 0; int16_t balancing_status = 0; + int16_t battery_voltage_after_contactor = 0; + unsigned long min_cell_voltage_data_age = 0; + unsigned long max_cell_voltage_data_age = 0; } DATALAYER_INFO_BMWIX; From 724efbc5c5ac0272170c04235a04da1d5a435515 Mon Sep 17 00:00:00 2001 From: wjcloudy <56305354+wjcloudy@users.noreply.github.com> Date: Sun, 3 Nov 2024 21:27:15 +0000 Subject: [PATCH 110/210] 0.3alpha - Update advanced_battery_html.cpp Added more values including pack min/max. min/max cell voltage is now monitored for ensure it's changing (an extra safety check in case it goes stale) --- .../webserver/advanced_battery_html.cpp | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/Software/src/devboard/webserver/advanced_battery_html.cpp b/Software/src/devboard/webserver/advanced_battery_html.cpp index 8a018ce7..eff6900e 100644 --- a/Software/src/devboard/webserver/advanced_battery_html.cpp +++ b/Software/src/devboard/webserver/advanced_battery_html.cpp @@ -18,7 +18,16 @@ String advanced_battery_processor(const String& var) { #ifdef BMW_IX_BATTERY - content += "

T30 Terminal Voltage: " + String(datalayer_extended.bmwix.T30_Voltage) + " mV

"; + content += "

Battery Voltage after Contactor: " + String(datalayer_extended.bmwix.battery_voltage_after_contactor) + " dV

"; + content += "

Max Design Voltage: " + String(datalayer.battery.info.max_design_voltage_dV) + " dV

"; + content += "

Min Design Voltage: " + String(datalayer.battery.info.min_design_voltage_dV) + " dV

"; + content += "

Max Cell Design Voltage: " + String(datalayer.battery.info.max_cell_voltage_mV) + " mV

"; + content += "

Min Cell Design Voltage: " + String(datalayer.battery.info.min_cell_voltage_mV) + " mV

"; + content += "

Min Cell Voltage Data Age: " + String(datalayer_extended.bmwix.min_cell_voltage_data_age) + " ms

"; + content += "

Max Cell Voltage Data Age: " + String(datalayer_extended.bmwix.max_cell_voltage_data_age) + " ms

"; + content += "

Currently allowed Discharge Power: " + String(datalayer.battery.status.max_discharge_power_W) + " W

"; + content += "

Currently allowed Charge Power: " + String(datalayer.battery.status.max_charge_power_W) + " W

"; + content += "

T30 Terminal Voltage: " + String(datalayer_extended.bmwix.T30_Voltage) + " mV

"; content += "

Detected Cell Count: " + String(datalayer.battery.info.number_of_cells) + "

"; static const char* balanceText[5] = {"0 No balancing mode active", "1 Voltage-Controlled Balancing Mode", @@ -38,9 +47,15 @@ String advanced_battery_processor(const String& var) { content += "

Isolation Positive: " + String(datalayer_extended.bmwix.iso_safety_positive) + " kOhm

"; content += "

Isolation Negative: " + String(datalayer_extended.bmwix.iso_safety_negative) + " kOhm

"; content += "

Isolation Parallel: " + String(datalayer_extended.bmwix.iso_safety_parallel) + " kOhm

"; - content += "

Pyro Status PSS1: " + String(datalayer_extended.bmwix.pyro_status_pss1) + "

"; - content += "

Pyro Status PSS4: " + String(datalayer_extended.bmwix.pyro_status_pss4) + "

"; - content += "

Pyro Status PSS6: " + String(datalayer_extended.bmwix.pyro_status_pss6) + "

"; + static const char* pyroText[5] = {"0 Value Invalid", + "1 Successfully Blown", + "2 Disconnected" , + "3 Not Activated - Pyro Intact" , + "4 Unknown" + }; + content += "

Pyro Status PSS1: " + String((pyroText[datalayer_extended.bmwix.pyro_status_pss1])) + "

"; + content += "

Pyro Status PSS4: " + String((pyroText[datalayer_extended.bmwix.pyro_status_pss4])) + "

"; + content += "

Pyro Status PSS6: " + String((pyroText[datalayer_extended.bmwix.pyro_status_pss6])) + "

"; #endif //BMW_IX_BATTERY #ifdef BMW_I3_BATTERY From 2792e7370ed09d8e8063f4bdfbd69a5e4ad501b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Mon, 4 Nov 2024 08:50:40 +0200 Subject: [PATCH 111/210] Fix current datatype, and polling SOC --- Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.cpp b/Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.cpp index 0f3995f9..150da7bb 100644 --- a/Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.cpp +++ b/Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.cpp @@ -25,7 +25,7 @@ static uint16_t battery_max_temp = 920; static uint16_t battery_max_power = 0; static uint16_t battery_interlock = 0; static uint16_t battery_kwh = 0; -static uint16_t battery_current = 32640; +static int32_t battery_current = 32640; static uint16_t battery_current_offset = 0; static uint16_t battery_max_generated = 0; static uint16_t battery_max_available = 0; @@ -87,7 +87,9 @@ CAN_frame ZOE_SLEEP_2_18DADBF1 = {.FD = false, .ID = 0x18DADBF1, .data = {0x04, 0x2E, 0x92, 0x81, 0x01, 0xAA, 0xAA, 0xAA}}; -const uint16_t poll_commands[41] = {POLL_SOH, +const uint16_t poll_commands[41] = {POLL_SOC, + POLL_USABLE_SOC, + POLL_SOH, POLL_PACK_VOLTAGE, POLL_MAX_CELL_VOLTAGE, POLL_MIN_CELL_VOLTAGE, From d91cea64382523c8a771d2a2e2979f093f946563 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Mon, 4 Nov 2024 08:55:50 +0200 Subject: [PATCH 112/210] Add TODO to top of code --- Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.cpp b/Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.cpp index 150da7bb..38c875b1 100644 --- a/Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.cpp +++ b/Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.cpp @@ -5,6 +5,17 @@ #include "../devboard/utils/events.h" #include "RENAULT-ZOE-GEN2-BATTERY.h" +/* TODO +- Add //NVROL Reset +- Add //Enable temporisation before sleep (see ljames28 repo) + +"If the pack is in a state where it is confused about the time, you may need to reset it's NVROL memory. +However, if the power is later power cycled, it will revert back to his previous confused state. +Therefore, after resetting the NVROL you must enable "temporisation before sleep", and then stop streaming 373. +It will then save the data and go to sleep. When the pack is confused, the state of charge may reset back to incorrect value +every time the power is reset which can be dangerous. In this state, the voltage will still be accurate" +*/ + /* Information in this file is based on: https://github.com/openvehicles/Open-Vehicle-Monitoring-System-3/blob/master/vehicle/OVMS.V3/components/vehicle_renaultzoe_ph2_obd/src/vehicle_renaultzoe_ph2_obd.cpp https://github.com/ljames28/Renault-Zoe-PH2-ZE50-Canbus-LBC-Information?tab=readme-ov-file From 1f96681e943ab2e70f1dad02826f86772d7af924 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Mon, 4 Nov 2024 10:17:55 +0200 Subject: [PATCH 113/210] Replace faulty cap check with welded contactor check --- Software/src/battery/BMW-I3-BATTERY.cpp | 12 ++++++------ Software/src/devboard/utils/events.cpp | 3 +++ Software/src/devboard/utils/events.h | 3 ++- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/Software/src/battery/BMW-I3-BATTERY.cpp b/Software/src/battery/BMW-I3-BATTERY.cpp index 7a5dde11..c02a112e 100644 --- a/Software/src/battery/BMW-I3-BATTERY.cpp +++ b/Software/src/battery/BMW-I3-BATTERY.cpp @@ -422,10 +422,10 @@ void update_values_battery2() { //This function maps all the values fetched via } else { clear_event(EVENT_HVIL_FAILURE); } - if (battery2_status_precharge_locked == 2) { // Capacitor seated? - set_event(EVENT_PRECHARGE_FAILURE, 2); + if (battery2_status_error_disconnecting_switch > 0) { // Check if contactors are sticking / welded + set_event(EVENT_CONTACTOR_WELDED, 0); } else { - clear_event(EVENT_PRECHARGE_FAILURE); + clear_event(EVENT_CONTACTOR_WELDED); } } @@ -490,10 +490,10 @@ void update_values_battery() { //This function maps all the values fetched via } else { clear_event(EVENT_HVIL_FAILURE); } - if (battery_status_precharge_locked == 2) { // Capacitor seated? - set_event(EVENT_PRECHARGE_FAILURE, 0); + if (battery_status_error_disconnecting_switch > 0) { // Check if contactors are sticking / welded + set_event(EVENT_CONTACTOR_WELDED, 0); } else { - clear_event(EVENT_PRECHARGE_FAILURE); + clear_event(EVENT_CONTACTOR_WELDED); } // Update webserver datalayer diff --git a/Software/src/devboard/utils/events.cpp b/Software/src/devboard/utils/events.cpp index 8bdf710b..79584885 100644 --- a/Software/src/devboard/utils/events.cpp +++ b/Software/src/devboard/utils/events.cpp @@ -150,6 +150,7 @@ void init_events(void) { events.entries[EVENT_CAN_RX_WARNING].level = EVENT_LEVEL_WARNING; events.entries[EVENT_CAN_TX_FAILURE].level = EVENT_LEVEL_ERROR; events.entries[EVENT_CAN_INVERTER_MISSING].level = EVENT_LEVEL_WARNING; + events.entries[EVENT_CONTACTOR_WELDED].level = EVENT_LEVEL_WARNING; events.entries[EVENT_WATER_INGRESS].level = EVENT_LEVEL_ERROR; events.entries[EVENT_CHARGE_LIMIT_EXCEEDED].level = EVENT_LEVEL_INFO; events.entries[EVENT_DISCHARGE_LIMIT_EXCEEDED].level = EVENT_LEVEL_INFO; @@ -281,6 +282,8 @@ const char* get_event_message_string(EVENTS_ENUM_TYPE event) { return "ERROR: CAN messages failed to transmit, or no one on the bus to ACK the message!"; case EVENT_CAN_INVERTER_MISSING: return "Warning: Inverter not sending messages on CAN bus. Check wiring!"; + case EVENT_CONTACTOR_WELDED: + return "Warning: Contactors sticking/welded. Inspect battery with caution!"; case EVENT_CHARGE_LIMIT_EXCEEDED: return "Info: Inverter is charging faster than battery is allowing."; case EVENT_DISCHARGE_LIMIT_EXCEEDED: diff --git a/Software/src/devboard/utils/events.h b/Software/src/devboard/utils/events.h index 157d0595..dcdcfd6a 100644 --- a/Software/src/devboard/utils/events.h +++ b/Software/src/devboard/utils/events.h @@ -6,7 +6,7 @@ // #define INCLUDE_EVENTS_TEST // Enable to run an event test loop, see events_test_on_target.cpp -#define EE_MAGIC_HEADER_VALUE 0x0016 // 0x0000 to 0xFFFF +#define EE_MAGIC_HEADER_VALUE 0x0017 // 0x0000 to 0xFFFF #define GENERATE_ENUM(ENUM) ENUM, #define GENERATE_STRING(STRING) #STRING, @@ -37,6 +37,7 @@ XX(EVENT_CAN_TX_FAILURE) \ XX(EVENT_CAN_INVERTER_MISSING) \ XX(EVENT_CHARGE_LIMIT_EXCEEDED) \ + XX(EVENT_CONTACTOR_WELDED) \ XX(EVENT_DISCHARGE_LIMIT_EXCEEDED) \ XX(EVENT_WATER_INGRESS) \ XX(EVENT_12V_LOW) \ From 683e576d9957009b35761ae72bd02e9c32006ce3 Mon Sep 17 00:00:00 2001 From: amarofarinha <151563493+amarofarinha@users.noreply.github.com> Date: Mon, 4 Nov 2024 18:33:32 +0000 Subject: [PATCH 114/210] Add Support for Double-Battery in MQTT Integration --- Software/src/devboard/mqtt/mqtt.cpp | 174 +++++++++++++++++++++------- 1 file changed, 134 insertions(+), 40 deletions(-) diff --git a/Software/src/devboard/mqtt/mqtt.cpp b/Software/src/devboard/mqtt/mqtt.cpp index df0863d6..95f5444a 100644 --- a/Software/src/devboard/mqtt/mqtt.cpp +++ b/Software/src/devboard/mqtt/mqtt.cpp @@ -66,15 +66,35 @@ SensorConfig sensorConfigs[] = { {"max_charge_power", "Battery Max Charge Power", "{{ value_json.max_charge_power }}", "W", "power"}, {"bms_status", "BMS Status", "{{ value_json.bms_status }}", "", ""}, {"pause_status", "Pause Status", "{{ value_json.pause_status }}", "", ""}, - +#ifdef DOUBLE_BATTERY + {"SOC_2", "SOC 2 (scaled)", "{{ value_json.SOC_2 }}", "%", "battery"}, + {"SOC_real_2", "SOC 2 (real)", "{{ value_json.SOC_real_2 }}", "%", "battery"}, + {"state_of_health_2", "State Of Health 2", "{{ value_json.state_of_health_2 }}", "%", "battery"}, + {"temperature_min_2", "Temperature Min 2", "{{ value_json.temperature_min_2 }}", "°C", "temperature"}, + {"temperature_max_2", "Temperature Max 2", "{{ value_json.temperature_max_2 }}", "°C", "temperature"}, + {"stat_batt_power_2", "Stat Batt Power 2", "{{ value_json.stat_batt_power_2 }}", "W", "power"}, + {"battery_current_2", "Battery 2 Current", "{{ value_json.battery_current_2 }}", "A", "current"}, + {"cell_max_voltage_2", "Cell Max Voltage 2", "{{ value_json.cell_max_voltage_2 }}", "V", "voltage"}, + {"cell_min_voltage_2", "Cell Min Voltage 2", "{{ value_json.cell_min_voltage_2 }}", "V", "voltage"}, + {"battery_voltage_2", "Battery 2 Voltage", "{{ value_json.battery_voltage_2 }}", "V", "voltage"}, + {"total_capacity_2", "Battery 2 Total Capacity", "{{ value_json.total_capacity_2 }}", "Wh", "energy"}, + {"remaining_capacity_2", "Battery 2 Remaining Capacity (scaled)", "{{ value_json.remaining_capacity_2 }}", "Wh", + "energy"}, + {"remaining_capacity_real_2", "Battery 2 Remaining Capacity (real)", "{{ value_json.remaining_capacity_real_2 }}", + "Wh", "energy"}, + {"max_discharge_power_2", "Battery 2 Max Discharge Power", "{{ value_json.max_discharge_power_2 }}", "W", "power"}, + {"max_charge_power_2", "Battery 2 Max Charge Power", "{{ value_json.max_charge_power_2 }}", "W", "power"}, + {"bms_status_2", "BMS 2 Status", "{{ value_json.bms_status_2 }}", "", ""}, + {"pause_status_2", "Pause Status 2", "{{ value_json.pause_status_2 }}", "", ""}, +#endif // DOUBLE_BATTERY }; static String generateCommonInfoAutoConfigTopic(const char* object_id) { return "homeassistant/sensor/" + topic_name + "/" + String(object_id) + "/config"; } -static String generateCellVoltageAutoConfigTopic(int cell_number) { - return "homeassistant/sensor/" + topic_name + "/cell_voltage" + String(cell_number) + "/config"; +static String generateCellVoltageAutoConfigTopic(int cell_number, String battery_suffix) { + return "homeassistant/sensor/" + topic_name + "/cell_voltage" + battery_suffix + String(cell_number) + "/config"; } static String generateEventsAutoConfigTopic(const char* object_id) { @@ -148,7 +168,30 @@ static void publish_common_info(void) { doc["max_discharge_power"] = ((float)datalayer.battery.status.max_discharge_power_W); doc["max_charge_power"] = ((float)datalayer.battery.status.max_charge_power_W); } - +#ifdef DOUBLE_BATTERY + //only publish these values if BMS is active and we are comunication with the battery (can send CAN messages to the battery) + if (datalayer.battery2.status.CAN_battery_still_alive && allowed_to_send_CAN && millis() > BOOTUP_TIME) { + doc["SOC_2"] = ((float)datalayer.battery2.status.reported_soc) / 100.0; + doc["SOC_real_2"] = ((float)datalayer.battery2.status.real_soc) / 100.0; + doc["state_of_health_2"] = ((float)datalayer.battery2.status.soh_pptt) / 100.0; + doc["temperature_min_2"] = ((float)((int16_t)datalayer.battery2.status.temperature_min_dC)) / 10.0; + doc["temperature_max_2"] = ((float)((int16_t)datalayer.battery2.status.temperature_max_dC)) / 10.0; + doc["stat_batt_power_2"] = ((float)((int32_t)datalayer.battery2.status.active_power_W)); + doc["battery_current_2"] = ((float)((int16_t)datalayer.battery2.status.current_dA)) / 10.0; + doc["battery_voltage_2"] = ((float)datalayer.battery2.status.voltage_dV) / 10.0; + // publish only if cell voltages have been populated... + if (datalayer.battery2.info.number_of_cells != 0u && + datalayer.battery2.status.cell_voltages_mV[datalayer.battery2.info.number_of_cells - 1] != 0u) { + doc["cell_max_voltage_2"] = ((float)datalayer.battery2.status.cell_max_voltage_mV) / 1000.0; + doc["cell_min_voltage_2"] = ((float)datalayer.battery2.status.cell_min_voltage_mV) / 1000.0; + } + doc["total_capacity_2"] = ((float)datalayer.battery2.info.total_capacity_Wh); + doc["remaining_capacity_real_2"] = ((float)datalayer.battery2.status.remaining_capacity_Wh); + doc["remaining_capacity_2"] = ((float)datalayer.battery2.status.reported_remaining_capacity_Wh); + doc["max_discharge_power_2"] = ((float)datalayer.battery2.status.max_discharge_power_W); + doc["max_charge_power_2"] = ((float)datalayer.battery2.status.max_charge_power_W); + } +#endif // DOUBLE_BATTERY serializeJson(doc, mqtt_msg); if (!mqtt_publish(state_topic.c_str(), mqtt_msg, false)) { #ifdef DEBUG_VIA_USB @@ -167,47 +210,79 @@ static void publish_cell_voltages(void) { #endif // HA_AUTODISCOVERY static JsonDocument doc; static String state_topic = topic_name + "/spec_data"; +#ifdef DOUBLE_BATTERY + static String state_topic_2 = topic_name + "/spec_data_2"; + +#endif // DOUBLE_BATTERY - // If the cell voltage number isn't initialized... - if (datalayer.battery.info.number_of_cells == 0u) { - return; - } #ifdef HA_AUTODISCOVERY if (mqtt_first_transmission == true) { mqtt_first_transmission = false; - String topic = "homeassistant/sensor/battery-emulator/cell_voltage"; - - for (int i = 0; i < datalayer.battery.info.number_of_cells; i++) { - int cellNumber = i + 1; - doc["name"] = "Battery Cell Voltage " + String(cellNumber); - doc["object_id"] = object_id_prefix + "battery_voltage_cell" + String(cellNumber); - doc["unique_id"] = topic_name + "_battery_voltage_cell" + String(cellNumber); - doc["device_class"] = "voltage"; - doc["state_class"] = "measurement"; - doc["state_topic"] = state_topic; - doc["unit_of_measurement"] = "V"; - 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"]["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, sizeof(mqtt_msg)); - mqtt_publish(generateCellVoltageAutoConfigTopic(cellNumber).c_str(), mqtt_msg, true); + // If the cell voltage number isn't initialized... + if (datalayer.battery.info.number_of_cells != 0u) { + + for (int i = 0; i < datalayer.battery.info.number_of_cells; i++) { + int cellNumber = i + 1; + doc["name"] = "Battery Cell Voltage " + String(cellNumber); + doc["object_id"] = object_id_prefix + "battery_voltage_cell" + String(cellNumber); + doc["unique_id"] = topic_name + "_battery_voltage_cell" + String(cellNumber); + doc["device_class"] = "voltage"; + doc["state_class"] = "measurement"; + doc["state_topic"] = state_topic; + doc["unit_of_measurement"] = "V"; + 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"]["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, sizeof(mqtt_msg)); + mqtt_publish(generateCellVoltageAutoConfigTopic(cellNumber, "").c_str(), mqtt_msg, true); + } + doc.clear(); // clear after sending autoconfig } - doc.clear(); // clear after sending autoconfig - } else { -#endif // HA_AUTODISCOVERY - // If cell voltages haven't been populated... - if (datalayer.battery.info.number_of_cells == 0u || - datalayer.battery.status.cell_voltages_mV[datalayer.battery.info.number_of_cells - 1] == 0u) { - return; +#ifdef DOUBLE_BATTERY + // If the cell voltage number isn't initialized... + if (datalayer.battery2.info.number_of_cells != 0u) { + + for (int i = 0; i < datalayer.battery.info.number_of_cells; i++) { + int cellNumber = i + 1; + doc["name"] = "Battery 2 Cell Voltage " + String(cellNumber); + doc["object_id"] = object_id_prefix + "battery_2_voltage_cell" + String(cellNumber); + doc["unique_id"] = topic_name + "_battery_2_voltage_cell" + String(cellNumber); + doc["device_class"] = "voltage"; + doc["state_class"] = "measurement"; + doc["state_topic"] = state_topic_2; + doc["unit_of_measurement"] = "V"; + 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"]["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, sizeof(mqtt_msg)); + mqtt_publish(generateCellVoltageAutoConfigTopic(cellNumber, "_2_").c_str(), mqtt_msg, true); + } + doc.clear(); // clear after sending autoconfig } +#endif // DOUBLE_BATTERY + } +#endif // HA_AUTODISCOVERY + + // If cell voltages have been populated... + if (datalayer.battery.info.number_of_cells != 0u && + datalayer.battery.status.cell_voltages_mV[datalayer.battery.info.number_of_cells - 1] != 0u) { JsonArray cell_voltages = doc["cell_voltages"].to(); for (size_t i = 0; i < datalayer.battery.info.number_of_cells; ++i) { @@ -222,9 +297,28 @@ static void publish_cell_voltages(void) { #endif // DEBUG_VIA_USB } doc.clear(); -#ifdef HA_AUTODISCOVERY } -#endif // HA_AUTODISCOVERY + +#ifdef DOUBLE_BATTERY + // If cell voltages have been populated... + if (datalayer.battery2.info.number_of_cells != 0u && + datalayer.battery2.status.cell_voltages_mV[datalayer.battery2.info.number_of_cells - 1] != 0u) { + + JsonArray cell_voltages = doc["cell_voltages"].to(); + for (size_t i = 0; i < datalayer.battery2.info.number_of_cells; ++i) { + cell_voltages.add(((float)datalayer.battery2.status.cell_voltages_mV[i]) / 1000.0); + } + + serializeJson(doc, mqtt_msg, sizeof(mqtt_msg)); + + if (!mqtt_publish(state_topic_2.c_str(), mqtt_msg, false)) { +#ifdef DEBUG_VIA_USB + Serial.println("Cell voltage MQTT msg could not be sent"); +#endif // DEBUG_VIA_USB + } + doc.clear(); + } +#endif // DOUBLE_BATTERY } void publish_events() { From eb64a2f302716ac29e949ea4e794df739788a69f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Tue, 5 Nov 2024 18:50:27 +0200 Subject: [PATCH 115/210] Add remaining capacity writing for Pylon --- Software/src/battery/PYLON-BATTERY.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Software/src/battery/PYLON-BATTERY.cpp b/Software/src/battery/PYLON-BATTERY.cpp index 0df16c77..068a12b7 100644 --- a/Software/src/battery/PYLON-BATTERY.cpp +++ b/Software/src/battery/PYLON-BATTERY.cpp @@ -67,6 +67,9 @@ void update_values_battery() { datalayer.battery.status.max_discharge_power_W = (-max_discharge_current * (voltage_dV / 10)); + datalayer.battery.status.remaining_capacity_Wh = static_cast( + (static_cast(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh); + datalayer.battery.status.cell_max_voltage_mV = cellvoltage_max_mV; datalayer.battery.status.cell_min_voltage_mV = cellvoltage_min_mV; From deb7a46d16beb929eb90adc7e6e87fe998e6d4f0 Mon Sep 17 00:00:00 2001 From: amarofarinha <151563493+amarofarinha@users.noreply.github.com> Date: Wed, 6 Nov 2024 11:21:36 +0000 Subject: [PATCH 116/210] add double battery support for TEST-FAKE-BATTERY --- Software/src/battery/TEST-FAKE-BATTERY.cpp | 96 +++++++++++++++++++--- 1 file changed, 84 insertions(+), 12 deletions(-) diff --git a/Software/src/battery/TEST-FAKE-BATTERY.cpp b/Software/src/battery/TEST-FAKE-BATTERY.cpp index 82249ad2..0faff90b 100644 --- a/Software/src/battery/TEST-FAKE-BATTERY.cpp +++ b/Software/src/battery/TEST-FAKE-BATTERY.cpp @@ -71,21 +71,93 @@ void update_values_battery() { /* This function puts fake values onto the parame #endif } -void receive_can_battery(CAN_frame rx_frame) { - datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; - // All CAN messages recieved will be logged via serial - Serial.print(millis()); // Example printout, time, ID, length, data: 7553 1DB 8 FF C0 B9 EA 0 0 2 5D - Serial.print(" "); - Serial.print(rx_frame.ID, HEX); - Serial.print(" "); - Serial.print(rx_frame.DLC); - Serial.print(" "); - for (int i = 0; i < rx_frame.DLC; ++i) { - Serial.print(rx_frame.data.u8[i], HEX); - Serial.print(" "); +#ifdef DOUBLE_BATTERY + +void update_values_battery2() { // Handle the values coming in from battery #2 + + datalayer.battery2.status.real_soc = 5000; // 50.00% + + datalayer.battery2.status.soh_pptt = 9900; // 99.00% + + //datalayer.battery.status.voltage_dV = 3700; // 370.0V , value set in startup in .ino file, editable via webUI + + datalayer.battery2.status.current_dA = 0; // 0 A + + datalayer.battery2.info.total_capacity_Wh = 30000; // 30kWh + + datalayer.battery2.status.remaining_capacity_Wh = 15000; // 15kWh + + datalayer.battery2.status.cell_max_voltage_mV = 3596; + + datalayer.battery2.status.cell_min_voltage_mV = 3500; + + datalayer.battery2.status.active_power_W = 0; // 0W + + datalayer.battery2.status.temperature_min_dC = 50; // 5.0*C + + datalayer.battery2.status.temperature_max_dC = 60; // 6.0*C + + datalayer.battery2.status.max_discharge_power_W = 5000; // 5kW + + datalayer.battery2.status.max_charge_power_W = 5000; // 5kW + + for (int i = 0; i < 97; ++i) { + datalayer.battery2.status.cell_voltages_mV[i] = 3500 + i; } + + //Fake that we get CAN messages + datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + +/*Finally print out values to serial if configured to do so*/ +#ifdef DEBUG_VIA_USB + Serial.println("FAKE Values battery 2 going to inverter"); + print_units("SOH 2 %: ", (datalayer.battery2.status.soh_pptt * 0.01), "% "); + print_units(", SOC 2 %: ", (datalayer.battery2.status.reported_soc * 0.01), "% "); + print_units(", Voltage 2: ", (datalayer.battery2.status.voltage_dV * 0.1), "V "); + print_units(", Max discharge power 2: ", datalayer.battery2.status.max_discharge_power_W, "W "); + print_units(", Max charge power 2: ", datalayer.battery2.status.max_charge_power_W, "W "); + print_units(", Max temp 2: ", (datalayer.battery2.status.temperature_max_dC * 0.1), "°C "); + print_units(", Min temp 2: ", (datalayer.battery2.status.temperature_min_dC * 0.1), "°C "); + print_units(", Max cell voltage 2: ", datalayer.battery2.status.cell_max_voltage_mV, "mV "); + print_units(", Min cell voltage 2: ", datalayer.battery2.status.cell_min_voltage_mV, "mV "); Serial.println(""); +#endif + } + + void receive_can_battery2(CAN_frame rx_frame) { + datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + // All CAN messages recieved will be logged via serial + Serial.print(millis()); // Example printout, time, ID, length, data: 7553 1DB 8 FF C0 B9 EA 0 0 2 5D + Serial.print(" "); + Serial.print(rx_frame.ID, HEX); + Serial.print(" "); + Serial.print(rx_frame.DLC); + Serial.print(" "); + for (int i = 0; i < rx_frame.DLC; ++i) { + Serial.print(rx_frame.data.u8[i], HEX); + Serial.print(" "); + } + Serial.println(""); + } +#endif // DOUBLE_BATTERY + + + void receive_can_battery(CAN_frame rx_frame) { + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + // All CAN messages recieved will be logged via serial + Serial.print(millis()); // Example printout, time, ID, length, data: 7553 1DB 8 FF C0 B9 EA 0 0 2 5D + Serial.print(" "); + Serial.print(rx_frame.ID, HEX); + Serial.print(" "); + Serial.print(rx_frame.DLC); + Serial.print(" "); + for (int i = 0; i < rx_frame.DLC; ++i) { + Serial.print(rx_frame.data.u8[i], HEX); + Serial.print(" "); + } + Serial.println(""); + } void send_can_battery() { unsigned long currentMillis = millis(); // Send 100ms CAN Message From 56fb9616b78c47de25b39db70d4231ca73d17fa1 Mon Sep 17 00:00:00 2001 From: amarofarinha <151563493+amarofarinha@users.noreply.github.com> Date: Wed, 6 Nov 2024 11:35:21 +0000 Subject: [PATCH 117/210] pre-commit fix --- Software/src/battery/TEST-FAKE-BATTERY.cpp | 62 +++++++++++----------- 1 file changed, 30 insertions(+), 32 deletions(-) diff --git a/Software/src/battery/TEST-FAKE-BATTERY.cpp b/Software/src/battery/TEST-FAKE-BATTERY.cpp index 0faff90b..6e52bd70 100644 --- a/Software/src/battery/TEST-FAKE-BATTERY.cpp +++ b/Software/src/battery/TEST-FAKE-BATTERY.cpp @@ -122,42 +122,40 @@ void update_values_battery2() { // Handle the values coming in from battery #2 print_units(", Min cell voltage 2: ", datalayer.battery2.status.cell_min_voltage_mV, "mV "); Serial.println(""); #endif - } - void receive_can_battery2(CAN_frame rx_frame) { - datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE; - // All CAN messages recieved will be logged via serial - Serial.print(millis()); // Example printout, time, ID, length, data: 7553 1DB 8 FF C0 B9 EA 0 0 2 5D - Serial.print(" "); - Serial.print(rx_frame.ID, HEX); - Serial.print(" "); - Serial.print(rx_frame.DLC); - Serial.print(" "); - for (int i = 0; i < rx_frame.DLC; ++i) { - Serial.print(rx_frame.data.u8[i], HEX); - Serial.print(" "); - } - Serial.println(""); +void receive_can_battery2(CAN_frame rx_frame) { + datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + // All CAN messages recieved will be logged via serial + Serial.print(millis()); // Example printout, time, ID, length, data: 7553 1DB 8 FF C0 B9 EA 0 0 2 5D + Serial.print(" "); + Serial.print(rx_frame.ID, HEX); + Serial.print(" "); + Serial.print(rx_frame.DLC); + Serial.print(" "); + for (int i = 0; i < rx_frame.DLC; ++i) { + Serial.print(rx_frame.data.u8[i], HEX); + Serial.print(" "); } -#endif // DOUBLE_BATTERY - - - void receive_can_battery(CAN_frame rx_frame) { - datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; - // All CAN messages recieved will be logged via serial - Serial.print(millis()); // Example printout, time, ID, length, data: 7553 1DB 8 FF C0 B9 EA 0 0 2 5D - Serial.print(" "); - Serial.print(rx_frame.ID, HEX); - Serial.print(" "); - Serial.print(rx_frame.DLC); - Serial.print(" "); - for (int i = 0; i < rx_frame.DLC; ++i) { - Serial.print(rx_frame.data.u8[i], HEX); - Serial.print(" "); - } - Serial.println(""); + Serial.println(""); +} +#endif // DOUBLE_BATTERY + +void receive_can_battery(CAN_frame rx_frame) { + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + // All CAN messages recieved will be logged via serial + Serial.print(millis()); // Example printout, time, ID, length, data: 7553 1DB 8 FF C0 B9 EA 0 0 2 5D + Serial.print(" "); + Serial.print(rx_frame.ID, HEX); + Serial.print(" "); + Serial.print(rx_frame.DLC); + Serial.print(" "); + for (int i = 0; i < rx_frame.DLC; ++i) { + Serial.print(rx_frame.data.u8[i], HEX); + Serial.print(" "); } + Serial.println(""); +} void send_can_battery() { unsigned long currentMillis = millis(); // Send 100ms CAN Message From 9051293c43887253f7f8fec256e9bc7460c7864a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Wed, 6 Nov 2024 15:57:20 +0200 Subject: [PATCH 118/210] Update BYD FW to 3.29 --- Software/src/inverter/BYD-CAN.cpp | 14 +++++++------- Software/src/inverter/BYD-CAN.h | 2 ++ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/Software/src/inverter/BYD-CAN.cpp b/Software/src/inverter/BYD-CAN.cpp index 82527469..a2cf2d44 100644 --- a/Software/src/inverter/BYD-CAN.cpp +++ b/Software/src/inverter/BYD-CAN.cpp @@ -15,13 +15,13 @@ static uint8_t char5_151 = 0; static uint8_t char6_151 = 0; static uint8_t char7_151 = 0; -CAN_frame BYD_250 = { - .FD = false, - .ext_ID = false, - .DLC = 8, - .ID = 0x250, - .data = {0x03, 0x16, 0x00, 0x66, (uint8_t)((BATTERY_WH_MAX / 100) >> 8), (uint8_t)(BATTERY_WH_MAX / 100), 0x02, - 0x09}}; //3.16 FW , Capacity kWh byte4&5 (example 24kWh = 240) +CAN_frame BYD_250 = {.FD = false, + .ext_ID = false, + .DLC = 8, + .ID = 0x250, + .data = {FW_MAJOR_VERSION, FW_MINOR_VERSION, 0x00, 0x66, (uint8_t)((BATTERY_WH_MAX / 100) >> 8), + (uint8_t)(BATTERY_WH_MAX / 100), 0x02, + 0x09}}; //0-1 FW version , Capacity kWh byte4&5 (example 24kWh = 240) CAN_frame BYD_290 = {.FD = false, .ext_ID = false, .DLC = 8, diff --git a/Software/src/inverter/BYD-CAN.h b/Software/src/inverter/BYD-CAN.h index 198f10bb..6a45317b 100644 --- a/Software/src/inverter/BYD-CAN.h +++ b/Software/src/inverter/BYD-CAN.h @@ -3,6 +3,8 @@ #include "../include.h" #define CAN_INVERTER_SELECTED +#define FW_MAJOR_VERSION 0x03 +#define FW_MINOR_VERSION 0x29 void send_intial_data(); void transmit_can(CAN_frame* tx_frame, int interface); From f68539e9e29cb7f32a05a8531d409b4d60b78aaf Mon Sep 17 00:00:00 2001 From: amarofarinha <151563493+amarofarinha@users.noreply.github.com> Date: Thu, 7 Nov 2024 09:34:29 +0000 Subject: [PATCH 119/210] Fake Battery and Web Server Adjustments --- Software/Software.ino | 23 ++++++++++-- Software/src/battery/TEST-FAKE-BATTERY.cpp | 10 ++++-- Software/src/devboard/webserver/webserver.cpp | 35 +++++++++++++++---- 3 files changed, 58 insertions(+), 10 deletions(-) diff --git a/Software/Software.ino b/Software/Software.ino index 588aa5e0..588f28cf 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -860,6 +860,8 @@ void update_scaled_values() { * Before we use real_soc, we must make sure that it's within the range of min_percentage and max_percentage. */ uint32_t calc_soc; + uint32_t calc_max_capacity; + uint32_t calc_reserved_capacity; // Make sure that the SOC starts out between min and max percentages calc_soc = CONSTRAIN(datalayer.battery.status.real_soc, datalayer.battery.settings.min_percentage, datalayer.battery.settings.max_percentage); @@ -870,8 +872,6 @@ void update_scaled_values() { // Calculate the scaled remaining capacity in Wh if (datalayer.battery.info.total_capacity_Wh > 0 && datalayer.battery.status.real_soc > 0) { - uint32_t calc_max_capacity; - uint32_t calc_reserved_capacity; calc_max_capacity = (datalayer.battery.status.remaining_capacity_Wh * 10000 / datalayer.battery.status.real_soc); calc_reserved_capacity = calc_max_capacity * datalayer.battery.settings.min_percentage / 10000; // remove % capacity reserved in min_percentage to total_capacity_Wh @@ -881,9 +881,28 @@ void update_scaled_values() { datalayer.battery.status.reported_remaining_capacity_Wh = datalayer.battery.status.remaining_capacity_Wh; } +#ifdef DOUBLE_BATTERY + + // Calculate the scaled remaining capacity in Wh + if (datalayer.battery2.info.total_capacity_Wh > 0 && datalayer.battery2.status.real_soc > 0) { + calc_max_capacity = + (datalayer.battery2.status.remaining_capacity_Wh * 10000 / datalayer.battery2.status.real_soc); + calc_reserved_capacity = calc_max_capacity * datalayer.battery2.settings.min_percentage / 10000; + // remove % capacity reserved in min_percentage to total_capacity_Wh + datalayer.battery2.status.reported_remaining_capacity_Wh = + datalayer.battery2.status.remaining_capacity_Wh - calc_reserved_capacity; + } else { + datalayer.battery2.status.reported_remaining_capacity_Wh = datalayer.battery2.status.remaining_capacity_Wh; + } +#endif + } else { // No SOC window wanted. Set scaled to same as real. datalayer.battery.status.reported_soc = datalayer.battery.status.real_soc; datalayer.battery.status.reported_remaining_capacity_Wh = datalayer.battery.status.remaining_capacity_Wh; +#ifdef DOUBLE_BATTERY + datalayer.battery2.status.reported_soc = datalayer.battery2.status.real_soc; + datalayer.battery2.status.reported_remaining_capacity_Wh = datalayer.battery2.status.remaining_capacity_Wh; +#endif } #ifdef DOUBLE_BATTERY // Perform extra SOC sanity checks on double battery setups diff --git a/Software/src/battery/TEST-FAKE-BATTERY.cpp b/Software/src/battery/TEST-FAKE-BATTERY.cpp index 6e52bd70..630fa536 100644 --- a/Software/src/battery/TEST-FAKE-BATTERY.cpp +++ b/Software/src/battery/TEST-FAKE-BATTERY.cpp @@ -22,6 +22,8 @@ void print_units(char* header, int value, char* units) { void update_values_battery() { /* This function puts fake values onto the parameters sent towards the inverter */ + datalayer.battery.info.number_of_cells = 96; + datalayer.battery.status.real_soc = 5000; // 50.00% datalayer.battery.status.soh_pptt = 9900; // 99.00% @@ -49,7 +51,7 @@ void update_values_battery() { /* This function puts fake values onto the parame datalayer.battery.status.max_charge_power_W = 5000; // 5kW for (int i = 0; i < 97; ++i) { - datalayer.battery.status.cell_voltages_mV[i] = 3500 + i; + datalayer.battery.status.cell_voltages_mV[i] = 3700 + random(-20, 21); } //Fake that we get CAN messages @@ -75,6 +77,8 @@ void update_values_battery() { /* This function puts fake values onto the parame void update_values_battery2() { // Handle the values coming in from battery #2 + datalayer.battery2.info.number_of_cells = 96; + datalayer.battery2.status.real_soc = 5000; // 50.00% datalayer.battery2.status.soh_pptt = 9900; // 99.00% @@ -102,7 +106,7 @@ void update_values_battery2() { // Handle the values coming in from battery #2 datalayer.battery2.status.max_charge_power_W = 5000; // 5kW for (int i = 0; i < 97; ++i) { - datalayer.battery2.status.cell_voltages_mV[i] = 3500 + i; + datalayer.battery2.status.cell_voltages_mV[i] = 3700 + random(-20, 21); } //Fake that we get CAN messages @@ -167,6 +171,8 @@ void send_can_battery() { } void setup_battery(void) { // Performs one time setup at startup + randomSeed(analogRead(0)); + #ifdef DEBUG_VIA_USB Serial.println("Test mode with fake battery selected"); #endif diff --git a/Software/src/devboard/webserver/webserver.cpp b/Software/src/devboard/webserver/webserver.cpp index 274f2bf0..3f6bfbf5 100644 --- a/Software/src/devboard/webserver/webserver.cpp +++ b/Software/src/devboard/webserver/webserver.cpp @@ -785,7 +785,9 @@ String processor(const String& var) { content += "

Current: " + String(currentFloat, 1) + " A

"; content += formatPowerValue("Power", powerFloat, "", 1); content += formatPowerValue("Total capacity", datalayer.battery2.info.total_capacity_Wh, "h", 0); - content += formatPowerValue("Remaining capacity", datalayer.battery2.status.remaining_capacity_Wh, "h", 1); + content += formatPowerValue("Real Remaining capacity", datalayer.battery2.status.remaining_capacity_Wh, "h", 1); + content += + formatPowerValue("Scaled Remaining capacity", datalayer.battery2.status.reported_remaining_capacity_Wh, "h", 1); content += formatPowerValue("Max discharge power", datalayer.battery2.status.max_discharge_power_W, "", 1); content += formatPowerValue("Max charge power", datalayer.battery2.status.max_charge_power_W, "", 1); content += "

Cell max: " + String(datalayer.battery2.status.cell_max_voltage_mV) + " mV

"; @@ -827,6 +829,11 @@ String processor(const String& var) { content += ""; } + if (emulator_pause_status == NORMAL) + content += "

Power status: " + String(get_emulator_pause_status().c_str()) + "

"; + else + content += "

Power status: " + String(get_emulator_pause_status().c_str()) + "

"; + #ifdef CONTACTOR_CONTROL content += "

Contactors controlled by Battery-Emulator: "; if (datalayer.system.status.contactor_control_closed) { @@ -835,12 +842,28 @@ String processor(const String& var) { content += "OFF"; } content += "

"; -#endif - if (emulator_pause_status == NORMAL) - content += "

Pause status: " + String(get_emulator_pause_status().c_str()) + "

"; - else - content += "

Pause status: " + String(get_emulator_pause_status().c_str()) + "

"; + content += "

Pre Charge: "; + if (digitalRead(PRECHARGE_PIN) == HIGH) { + content += ""; + } else { + content += ""; + } + content += " Cont. Neg.: "; + if (digitalRead(NEGATIVE_CONTACTOR_PIN) == HIGH) { + content += ""; + } else { + content += ""; + } + + content += " Cont. Pos.: "; + if (digitalRead(POSITIVE_CONTACTOR_PIN) == HIGH) { + content += ""; + } else { + content += ""; + } + content += "

"; +#endif content += "
"; content += ""; From 6015ce272a509bfa34ae86c68532f6a9dafeca53 Mon Sep 17 00:00:00 2001 From: rha Date: Thu, 7 Nov 2024 20:47:39 +0200 Subject: [PATCH 120/210] receive_RS485 rewritten, Fixed CRC calculation and added byte scrambling --- Software/src/inverter/KOSTAL-RS485.cpp | 244 +++++++++++-------------- 1 file changed, 111 insertions(+), 133 deletions(-) diff --git a/Software/src/inverter/KOSTAL-RS485.cpp b/Software/src/inverter/KOSTAL-RS485.cpp index 18a10205..83e08ff3 100644 --- a/Software/src/inverter/KOSTAL-RS485.cpp +++ b/Software/src/inverter/KOSTAL-RS485.cpp @@ -29,66 +29,49 @@ union f32b { byte b[4]; }; - - - -uint8_t BATTERY_INFO[40] = {0x06, 0xE2, 0xFF, 0x02, 0xFF, 0x29, // Frame header - 0x01, 0x08, 0x80, 0x43, // 256.063 Nominal voltage / 5*51.2=256 first byte 0x01 or 0x04 +uint8_t BATTERY_INFO[40] = {0x00, 0xE2, 0xFF, 0x02, 0xFF, 0x29, // Frame header + 0x00, 0x00, 0x80, 0x43, // 256.063 Nominal voltage / 5*51.2=256 first byte 0x01 or 0x04 0xE4, 0x70, 0x8A, 0x5C, // Manufacture date (Epoch time) (BYD: GetBatteryInfo this[0x10ac]) - 0xB5, 0x02, 0xD3, 0x01, // Battery Serial number? Modbus register 527 - 0x10b0 - 0x01, 0x05, 0xC8, 0x41, // 0x10b4 + 0xB5, 0x00, 0xD3, 0x00, // Battery Serial number? Modbus register 527 - 0x10b0 + 0x00, 0x00, 0xC8, 0x41, // 0x10b4 0xC2, 0x18, // Battery Firmware, modbus register 586 (0x10b8) - 0x01, // Static (BYD: GetBatteryInfo this[0x10ba]) - 0x03, // ? + 0x00, // Static (BYD: GetBatteryInfo this[0x10ba]) + 0x00, // ? 0x59, 0x42, // Static (BYD: GetBatteryInfo this[0x10bc]) - 0x01, 0x01, // Static (BYD: GetBatteryInfo this[0x10be]) - 0x01, 0x02, - 0x05, 0x02, 0xA0, 0x01, 0x01, 0x02, + 0x00, 0x00, // Static (BYD: GetBatteryInfo this[0x10be]) + 0x00, 0x00, + 0x05, 0x00, 0xA0, 0x00, 0x00, 0x00, 0x4D, // CRC 0x00}; // -// values in frame2 will be overwritten at update_modbus_registers_inverter() +// values in CyclicData will be overwritten at update_modbus_registers_inverter() -uint8_t frame2[64] = { - 0x0A, // This may also been 0x06, seen at startup when live values not valid, but also occasionally single frames. +uint8_t CyclicData[64] = { + 0x00, // First zero byte pointer 0xE2, 0xFF, 0x02, 0xFF, 0x29, // frame Header - 0x1D, 0x5A, 0x85, 0x43, // Current Voltage (float) Modbus register 216, Bytes 6-9 - 0x01, 0x03, // Unknown, 0x03 seen also 0x0F, 0x07, might hava something to do with current - 0x8D, 0x43, // Max Voltage (2 byte float), Bytes 12-13 - 0x01, 0x03, 0xAC, 0x41, // BAttery Temperature (2 byte float) Modbus register 214, Bytes 16-17 - 0x01, 0x01, 0x01, - 0x01, // Peak Current (1s period?), Bytes 18-21 - Communication fault seen with some values (>10A?) - 0x01, 0x01, 0x01, - 0x01, // Avg current (1s period?), Bytes 22-25 - Communication fault seen with some values (>10A?) - - 0x01, 0x03, 0x48, 0x42, // Max discharge current (2 byte float), Bit 26-29, + 0x00, 0x00, 0x8D, 0x43, // Max Voltage (2 byte float), Bytes 12-13 + 0x00, 0x00, 0xAC, 0x41, // BAttery Temperature (2 byte float) Modbus register 214, Bytes 16-17 + 0x00, 0x00, 0x00, 0x00, // Peak Current (1s period?), Bytes 18-21 - Communication fault seen with some values (>10A?) + 0x00, 0x00, 0x00, 0x00, // Avg current (1s period?), Bytes 22-25 - Communication fault seen with some values (>10A?) + 0x00, 0x00, 0x48, 0x42, // Max discharge current (2 byte float), Bit 26-29, // Sunspec: ADisChaMax - - 0x01, 0x03, // Unknown - 0xC8, 0x41, // Battery gross capacity, Ah (2 byte float) , Bytes 30-33, Modbus 512 - - 0x01, // Unknown - 0x16, // This seems to have something to do with cell temperatures - - 0xA0, 0x41, // Max charge current (2 byte float) Bit 36-37, ZERO WHEN SOC=100 - // Sunspec: AChaMax - + 0x00, 0x00, 0xC8, 0x41, // Battery gross capacity, Ah (2 byte float) , Bytes 30-33, Modbus 512 + 0x00, 0x00, 0xA0, 0x41, // Max charge current (2 byte float) Bit 36-37, ZERO WHEN SOC=100 + // Sunspec: AChaMax 0xCD, 0xCC, 0xB4, 0x41, // MaxCellTemp (4 byte float) Bit 38-41 - 0x01, 0x0C, 0xA4, 0x41, // MinCellTemp (4 byte float) Bit 42-45 - + 0x00, 0x00, 0xA4, 0x41, // MinCellTemp (4 byte float) Bit 42-45 0xA4, 0x70, 0x55, 0x40, // MaxCellVolt (float), Bit 46-49 0x7D, 0x3F, 0x55, 0x40, // MinCellVolt (float), Bit 50-53 - 0xFE, // Cylce count , Bit 54 - 0x04, // Cycle count? , Bit 55 - 0x01, // Byte 56 - 0x40, // When SOC=100 Byte57=0x40, at startup 0x03 (about 7 times), otherwise 0x02 - 0x64, // SOC , Bit 58 - 0x01, // Unknown, when byte 57 = 0x03, this 0x02, otherwise 0x01 - 0x01, // Unknown, Seen only 0x01 - 0x02, // Unknown, Mostly 0x02. seen also 0x01 - 0x00, // CRC (inverted sum of bytes 1-62 + 0xC0), Bit 62 + 0xFE, 0x04, // Cycle count, + 0x00, // Byte 56 + 0x00, // When SOC=100 Byte57=0x40, at startup 0x03 (about 7 times), otherwise 0x02 + 0x64, // SOC , Bit 58 + 0x00, // Unknown, + 0x00, // Unknown, + 0x00, // Unknown, + 0x00, // CRC (inverted sum of bytes 1-62 + 0xC0), Bit 62 0x00}; // FE 04 01 40 xx 01 01 02 yy (fully charged) @@ -101,10 +84,8 @@ uint8_t frame3[9] = { 0x00 //endbyte }; -uint8_t frame4[8] = {0x07, 0xE3, 0xFF, 0x02, 0xFF, 0x29, 0xF4, 0x00}; - +uint8_t frame4[8] = {0x07, 0xE3, 0xFF, 0x02, 0xFF, 0x29, 0xF4, 0x00}; uint8_t frameB1[10] = {0x07, 0x63, 0xFF, 0x02, 0xFF, 0x29, 0x5E, 0x02, 0x16, 0x00}; -uint8_t frameB1b[8] = {0x07, 0xE3, 0xFF, 0x02, 0xFF, 0x29, 0xF4, 0x00}; uint8_t RS485_RXFRAME[10]; @@ -119,13 +100,6 @@ void float2frame(byte* arr, float value, byte framepointer) { arr[framepointer + 3] = g.b[3]; } -void float2frameMSB(byte* arr, float value, byte framepointer) { - f32b g; - g.f = value; - arr[framepointer + 0] = g.b[2]; - arr[framepointer + 1] = g.b[3]; -} - void send_kostal(byte* arr, int alen) { #ifdef DEBUG_KOSTAL_RS485_DATA Serial.print("TX: "); @@ -141,12 +115,25 @@ void send_kostal(byte* arr, int alen) { Serial2.write(arr, alen); } -byte calculate_longframe_crc(byte* lfc, int lastbyte) { +void scramble_null_bytes(byte *lfc, int len) { + int last_null_byte = 0; + for (int i = 0; i < len; i++) { + if (lfc[i] == '\0') { + lfc[last_null_byte] = (byte) (i - last_null_byte); + last_null_byte = i; + } + } +} + +byte calculate_kostal_crc(byte *lfc, int len) { unsigned int sum = 0; - for (int i = 0; i < lastbyte; ++i) { - sum += lfc[i]; + if (lfc[0] != 0) { + printf("WARNING: first byte should be 0, but is 0x%02x\n", lfc[0]); + } + for (int i = 1; i < len; i++) { + sum += lfc[i]; } - return ((byte) ~(sum + 0xc0) & 0xff); + return (byte) (-sum & 0xff); } byte calculate_frame1_crc(byte* lfc, int lastbyte) { @@ -204,60 +191,46 @@ void update_RS485_registers_inverter() { } if (datalayer.system.status.battery_allows_contactor_closing & datalayer.system.status.inverter_allows_contactor_closing ) { - float2frame(frame2, (float)datalayer.battery.status.voltage_dV / 10, 6); // Confirmed OK mapping - frame2[0] = 0x0A; + float2frame(CyclicData, (float)datalayer.battery.status.voltage_dV / 10, 6); // Confirmed OK mapping } else { - frame2[0] = 0x06; - float2frame(frame2, 0.0, 6); + float2frame(CyclicData, 0.0, 6); } // Set nominal voltage to value between min and max voltage set by battery (Example 400 and 300 results in 350V) nominal_voltage_dV = (((datalayer.battery.info.max_design_voltage_dV - datalayer.battery.info.min_design_voltage_dV) / 2) + datalayer.battery.info.min_design_voltage_dV); - float2frameMSB(BATTERY_INFO, (float)nominal_voltage_dV / 10, 8); + float2frame(BATTERY_INFO, (float)nominal_voltage_dV / 10, 8); - float2frameMSB(frame2, (float)datalayer.battery.info.max_design_voltage_dV / 10, 12); + float2frame(CyclicData, (float)datalayer.battery.info.max_design_voltage_dV / 10, 12); - float2frameMSB(frame2, (float)average_temperature_dC / 10, 16); + float2frame(CyclicData, (float)average_temperature_dC / 10, 16); // Some current values causes communication error, must be resolved, why. - // float2frameMSB(frame2, (float)datalayer.battery.status.current_dA / 10, 20); // Peak discharge? current (2 byte float) - // float2frameMSB(frame2, (float)datalayer.battery.status.current_dA / 10, 24); + float2frame(CyclicData, (float)datalayer.battery.status.current_dA / 10, 20); // Peak discharge? current (2 byte float) + float2frame(CyclicData, (float)datalayer.battery.status.current_dA / 10, 24); - float2frameMSB(frame2, (float)discharge_current_dA / 10, 28); // BAttery capacity Ah + float2frame(CyclicData, (float)discharge_current_dA / 10, 28); // BAttery capacity Ah - float2frameMSB(frame2, (float)discharge_current_dA / 10, 32); + float2frame(CyclicData, (float)discharge_current_dA / 10, 32); // When SOC = 100%, drop down allowed charge current down. if ((datalayer.battery.status.reported_soc / 100) < 100) { - float2frameMSB(frame2, (float)charge_current_dA / 10, 36); - frame2[57] = 0x02; - frame2[59] = 0x01; + float2frame(CyclicData, (float)charge_current_dA / 10, 36); } else { - float2frameMSB(frame2, 0.0, 36); - //frame2[57]=0x40; - frame2[57] = 0x02; - frame2[59] = 0x01; + float2frame(CyclicData, 0.0, 36); } - // On startup, byte 57 seems to be always 0x03 couple of frames,. - if (f2_startup_count < 14) { - frame2[57] = 0x03; - frame2[59] = 0x02; - } + float2frame(CyclicData, (float)datalayer.battery.status.temperature_max_dC / 10, 38); + float2frame(CyclicData, (float)datalayer.battery.status.temperature_min_dC / 10, 42); - float2frame(frame2, (float)datalayer.battery.status.temperature_max_dC / 10, 38); - float2frame(frame2, (float)datalayer.battery.status.temperature_min_dC / 10, 42); + float2frame(CyclicData, (float)datalayer.battery.status.cell_max_voltage_mV / 1000, 46); + float2frame(CyclicData, (float)datalayer.battery.status.cell_min_voltage_mV / 1000, 50); - float2frame(frame2, (float)datalayer.battery.status.cell_max_voltage_mV / 1000, 46); - float2frame(frame2, (float)datalayer.battery.status.cell_min_voltage_mV / 1000, 50); - - frame2[58] = (byte)(datalayer.battery.status.reported_soc / 100); // Confirmed OK mapping + CyclicData[58] = (byte)(datalayer.battery.status.reported_soc / 100); // Confirmed OK mapping register_content_ok = true; - BATTERY_INFO[38] = calculate_frame1_crc(BATTERY_INFO, 38); if (incoming_message_counter > 0) { incoming_message_counter--; @@ -298,7 +271,7 @@ void receive_RS485() // Runs as fast as possible to handle the serial stream if (B1_delay) { if ((currentMillis - B1_last_millis) > INTERVAL_1_S) { - send_kostal(frameB1b, 8); + send_kostal(frame4, 8); B1_delay = false; } } else if (Serial2.available()) { @@ -318,54 +291,59 @@ void receive_RS485() // Runs as fast as possible to handle the serial stream rx_index = 0; if (check_kostal_frame_crc()) { incoming_message_counter = RS485_HEALTHY; - bool headerA = true; - bool headerB = true; - for (uint8_t i = 0; i < 5; i++) { - if (RS485_RXFRAME[i + 1] != KOSTAL_FRAMEHEADER[i]) { - headerA = false; - } - if (RS485_RXFRAME[i + 1] != KOSTAL_FRAMEHEADER2[i]) { - headerB = false; - } - } - - // "frame B1", maybe reset request, seen after battery power on/partial data - if (headerB && (RS485_RXFRAME[6] == 0x5E) && (RS485_RXFRAME[7] == 0xFF)) { - send_kostal(frameB1, 10); - B1_delay = true; - B1_last_millis = currentMillis; - } - // "frame B1", maybe reset request, seen after battery power on/partial data - if (headerB && (RS485_RXFRAME[6] == 0x5E) && (RS485_RXFRAME[7] == 0x04)) { - send_kostal(frame4, 8); - // This needs more reverse engineering, disabled... - } - - if (headerA && (RS485_RXFRAME[6] == 0x4A) && (RS485_RXFRAME[7] == 0x08)) { // "frame 1" - send_kostal(BATTERY_INFO, 40); - if (!startupMillis) { - startupMillis = currentMillis; + if (RS485_RXFRAME[1] == 'c') { + if (RS485_RXFRAME[6] == 0x47) { + // Set time function - Do nothing. + send_kostal(frame4, 8); // ACK + } + if (RS485_RXFRAME[6] == 0x5E) { + // Set State function + if (RS485_RXFRAME[7] == 0x01) { + // State X + } + else if (RS485_RXFRAME[7] == 0x04) { + // INVALID + } + else { + // State Y + } + send_kostal(frame4, 8); // ACK } } - if (headerA && (RS485_RXFRAME[6] == 0x4A) && (RS485_RXFRAME[7] == 0x04)) { // "frame 2" - update_values_battery(); - update_RS485_registers_inverter(); - if (f2_startup_count < 15) { - f2_startup_count++; + else if (RS485_RXFRAME[1] == 'b') { + if (RS485_RXFRAME[6] == 0x50) { + //Reverse polarity, do nothing } - byte tmpframe[64]; //copy values to prevent data manipulation during rewrite/crc calculation - memcpy(tmpframe, frame2, 64); - for (int i = 1; i < 63; i++) { - if (tmpframe[i] == 0x00) { - tmpframe[i] = 0x01; + else { + int code=RS485_RXFRAME[6] + RS485_RXFRAME[7]*0x100; + if (code == 0x44a) { + //Send cyclic data + update_values_battery(); + update_RS485_registers_inverter(); + if (f2_startup_count < 15) { + f2_startup_count++; + } + byte tmpframe[64]; //copy values to prevent data manipulation during rewrite/crc calculation + memcpy(tmpframe, CyclicData, 64); + tmpframe[62] = calculate_kostal_crc(tmpframe, 62); + scramble_null_bytes(tmpframe,64); + send_kostal(tmpframe, 64); + } + if (code == 0x84a) { + //Send battery info + BATTERY_INFO[38] = calculate_kostal_crc(BATTERY_INFO, 38); + scramble_null_bytes(BATTERY_INFO,40); + send_kostal(BATTERY_INFO, 40); + if (!startupMillis) { + startupMillis = currentMillis; + } + } + if (code == 0x353) { + //Send battery error + send_kostal(frame3, 9); } } - tmpframe[62] = calculate_longframe_crc(tmpframe, 62); - send_kostal(tmpframe, 64); - } - if (headerA && (RS485_RXFRAME[6] == 0x53) && (RS485_RXFRAME[7] == 0x03)) { // "frame 3" - send_kostal(frame3, 9); } } } From e293d5dcce9145a6650080fee82870b046fe6e50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Fri, 8 Nov 2024 10:11:31 +0200 Subject: [PATCH 121/210] Add initial HAL for v0.1 3LB --- Software/USER_SETTINGS.h | 1 + Software/src/devboard/hal/hal.h | 4 +- Software/src/devboard/hal/hw_3LB.h | 106 ++++++++++++++++++ Software/src/devboard/webserver/webserver.cpp | 3 + 4 files changed, 112 insertions(+), 2 deletions(-) create mode 100644 Software/src/devboard/hal/hw_3LB.h diff --git a/Software/USER_SETTINGS.h b/Software/USER_SETTINGS.h index 242c1264..8085999f 100644 --- a/Software/USER_SETTINGS.h +++ b/Software/USER_SETTINGS.h @@ -49,6 +49,7 @@ /* Select hardware used for Battery-Emulator */ #define HW_LILYGO //#define HW_STARK +//#define HW_3LB /* Other options */ //#define DEBUG_VIA_USB //Enable this line to have the USB port output serial diagnostic data while program runs (WARNING, raises CPU load, do not use for production) diff --git a/Software/src/devboard/hal/hal.h b/Software/src/devboard/hal/hal.h index 5e904d89..dc3240ce 100644 --- a/Software/src/devboard/hal/hal.h +++ b/Software/src/devboard/hal/hal.h @@ -7,8 +7,8 @@ #include "hw_lilygo.h" #elif defined(HW_STARK) #include "hw_stark.h" -#elif defined(HW_SJB_V1) -#include "hw_sjb_v1.h" +#elif defined(HW_3LB) +#include "hw_3LB.h" #endif #endif diff --git a/Software/src/devboard/hal/hw_3LB.h b/Software/src/devboard/hal/hw_3LB.h new file mode 100644 index 00000000..137d83f3 --- /dev/null +++ b/Software/src/devboard/hal/hw_3LB.h @@ -0,0 +1,106 @@ +#ifndef __HW_3LB_H__ +#define __HW_3LB_H__ + +// Board boot-up time +#define BOOTUP_TIME 1000 // Time in ms it takes before system is considered fully started up + +// Core assignment +#define CORE_FUNCTION_CORE 1 +#define MODBUS_CORE 0 +#define WIFI_CORE 0 + +// RS485 +//#define PIN_5V_EN 16 +//#define RS485_EN_PIN 17 // 17 /RE +#define RS485_TX_PIN 1 // 21 +#define RS485_RX_PIN 3 // 22 +//#define RS485_SE_PIN 19 // 22 /SHDN + +// CAN settings. CAN_2 is not defined as it can be either MCP2515 or MCP2517, defined by the user settings +#define CAN_1_TYPE ESP32CAN + +// CAN1 PIN mappings, do not change these unless you are adding on extra hardware to the PCB +#define CAN_TX_PIN GPIO_NUM_27 +#define CAN_RX_PIN GPIO_NUM_26 +//#define CAN_SE_PIN 23 + +// CAN2 defines below + +// DUAL_CAN defines +#define MCP2515_SCK 12 // SCK input of MCP2515 +#define MCP2515_MOSI 5 // SDI input of MCP2515 +#define MCP2515_MISO 34 // SDO output of MCP2515 | Pin 34 is input only, without pullup/down resistors +#define MCP2515_CS 18 // CS input of MCP2515 +#define MCP2515_INT 35 // INT output of MCP2515 | | Pin 35 is input only, without pullup/down resistors + +// CAN_FD defines +#define MCP2517_SCK 17 // SCK input of MCP2517 +#define MCP2517_SDI 23 // SDI input of MCP2517 +#define MCP2517_SDO 39 // SDO output of MCP2517 +#define MCP2517_CS 21 // CS input of MCP2517 //21 or 22 +#define MCP2517_INT 34 // INT output of MCP2517 //34 or 35 + +// CHAdeMO support pin dependencies +#define CHADEMO_PIN_2 12 +#define CHADEMO_PIN_10 5 +#define CHADEMO_PIN_7 34 +#define CHADEMO_PIN_4 35 +#define CHADEMO_LOCK 18 + +// Contactor handling +#define POSITIVE_CONTACTOR_PIN 32 +#define NEGATIVE_CONTACTOR_PIN 33 +#define PRECHARGE_PIN 25 + +#define 2ND_POSITIVE_CONTACTOR_PIN 13 +#define 2ND_NEGATIVE_CONTACTOR_PIN 16 +#define 2ND_PRECHARGE_PIN 18 + +// SMA CAN contactor pins +#define INVERTER_CONTACTOR_ENABLE_PIN 36 + +// SD card +//#define SD_MISO_PIN 2 +//#define SD_MOSI_PIN 15 +//#define SD_SCLK_PIN 14 +//#define SD_CS_PIN 13 + +// LED +#define LED_PIN 4 +#define LED_MAX_BRIGHTNESS 40 + +// Equipment stop pin +#define EQUIPMENT_STOP_PIN 35 + +/* ----- Error checks below, don't change (can't be moved to separate file) ----- */ +#ifndef HW_CONFIGURED +#define HW_CONFIGURED +#else +#error Multiple HW defined! Please select a single HW +#endif + +#ifdef CHADEMO_BATTERY +#ifdef DUAL_CAN +#error CHADEMO and DUAL_CAN cannot coexist due to overlapping GPIO pin usage +#endif +#endif + +#ifdef EQUIPMENT_STOP_BUTTON +#ifdef DUAL_CAN +#error EQUIPMENT_STOP_BUTTON and DUAL_CAN cannot coexist due to overlapping GPIO pin usage +#endif +#ifdef CAN_FD +#error EQUIPMENT_STOP_BUTTON and CAN_FD cannot coexist due to overlapping GPIO pin usage +#endif +#ifdef CHADEMO_BATTERY +#error EQUIPMENT_STOP_BUTTON and CHADEMO_BATTERY cannot coexist due to overlapping GPIO pin usage +#endif +#endif + +#ifdef BMW_I3_BATTERY +#ifdef CONTACTOR_CONTROL +#error GPIO PIN 25 cannot be used for both BMWi3 Wakeup and contactor control. Disable CONTACTOR_CONTROL +#endif +#endif + +#endif diff --git a/Software/src/devboard/webserver/webserver.cpp b/Software/src/devboard/webserver/webserver.cpp index 3f6bfbf5..cf33bbf0 100644 --- a/Software/src/devboard/webserver/webserver.cpp +++ b/Software/src/devboard/webserver/webserver.cpp @@ -415,6 +415,9 @@ String get_firmware_info_processor(const String& var) { #ifdef HW_STARK doc["hardware"] = "Stark CMR Module"; #endif // HW_STARK +#ifdef HW_3LB + doc["hardware"] = "3LB board"; +#endif // HW_STARK doc["firmware"] = String(version_number); serializeJson(doc, content); From 5dd30f7d33cf97cfd3ea8983572f08f743b63f1d Mon Sep 17 00:00:00 2001 From: wjcloudy <56305354+wjcloudy@users.noreply.github.com> Date: Fri, 8 Nov 2024 10:55:07 +0000 Subject: [PATCH 122/210] Update advanced_battery_html.cpp --- .../webserver/advanced_battery_html.cpp | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/Software/src/devboard/webserver/advanced_battery_html.cpp b/Software/src/devboard/webserver/advanced_battery_html.cpp index 5e6557e8..ddad0304 100644 --- a/Software/src/devboard/webserver/advanced_battery_html.cpp +++ b/Software/src/devboard/webserver/advanced_battery_html.cpp @@ -16,6 +16,47 @@ String advanced_battery_processor(const String& var) { // Start a new block with a specific background color content += "
"; +#ifdef BMW_IX_BATTERY + content += "

Battery Voltage after Contactor: " + String(datalayer_extended.bmwix.battery_voltage_after_contactor) + " dV

"; + content += "

Max Design Voltage: " + String(datalayer.battery.info.max_design_voltage_dV) + " dV

"; + content += "

Min Design Voltage: " + String(datalayer.battery.info.min_design_voltage_dV) + " dV

"; + content += "

Max Cell Design Voltage: " + String(datalayer.battery.info.max_cell_voltage_mV) + " mV

"; + content += "

Min Cell Design Voltage: " + String(datalayer.battery.info.min_cell_voltage_mV) + " mV

"; + content += "

Min Cell Voltage Data Age: " + String(datalayer_extended.bmwix.min_cell_voltage_data_age) + " ms

"; + content += "

Max Cell Voltage Data Age: " + String(datalayer_extended.bmwix.max_cell_voltage_data_age) + " ms

"; + content += "

Allowed Discharge Power: " + String(datalayer.battery.status.max_discharge_power_W) + " W

"; + content += "

Allowed Charge Power: " + String(datalayer.battery.status.max_charge_power_W) + " W

"; + content += "

T30 Terminal Voltage: " + String(datalayer_extended.bmwix.T30_Voltage) + " mV

"; + content += "

Detected Cell Count: " + String(datalayer.battery.info.number_of_cells) + "

"; + static const char* balanceText[5] = {"0 No balancing mode active", + "1 Voltage-Controlled Balancing Mode", + "2 Time-Controlled Balancing Mode with Demand Calculation at End of Charging" , + "3 Time-Controlled Balancing Mode with Demand Calculation at Resting Voltage" , + "4 No balancing mode active, qualifier invalid" + }; + content += "

Balancing: " + String((balanceText[datalayer_extended.bmwix.balancing_status])) + "

"; + static const char* hvilText[2] = {"Error (Loop Open)", + "OK (Loop Closed)"}; + content += "

HVIL Status: " + String(hvilText[datalayer_extended.bmwix.hvil_status]) + "

"; + content += "

BMS Uptime: " + String(datalayer_extended.bmwix.bms_uptime) + " seconds

"; + content += "

BMS Allowed Charge Amps: " + String(datalayer_extended.bmwix.allowable_charge_amps) + " A

"; + content += "

BMS Allowed Disharge Amps: " + String(datalayer_extended.bmwix.allowable_discharge_amps) + " A

"; + content += "
"; + content += "

HV Isolation (2147483647kOhm = maximum/invalid)

"; + content += "

Isolation Positive: " + String(datalayer_extended.bmwix.iso_safety_positive) + " kOhm

"; + content += "

Isolation Negative: " + String(datalayer_extended.bmwix.iso_safety_negative) + " kOhm

"; + content += "

Isolation Parallel: " + String(datalayer_extended.bmwix.iso_safety_parallel) + " kOhm

"; + static const char* pyroText[5] = {"0 Value Invalid", + "1 Successfully Blown", + "2 Disconnected" , + "3 Not Activated - Pyro Intact" , + "4 Unknown" + }; + content += "

Pyro Status PSS1: " + String((pyroText[datalayer_extended.bmwix.pyro_status_pss1])) + "

"; + content += "

Pyro Status PSS4: " + String((pyroText[datalayer_extended.bmwix.pyro_status_pss4])) + "

"; + content += "

Pyro Status PSS6: " + String((pyroText[datalayer_extended.bmwix.pyro_status_pss6])) + "

"; +#endif //BMW_IX_BATTERY + #ifdef BMW_I3_BATTERY content += "

SOC raw: " + String(datalayer_extended.bmwi3.SOC_raw) + "

"; content += "

SOC dash: " + String(datalayer_extended.bmwi3.SOC_dash) + "

"; From f22806fd23720c161864ffc3bc9235ed2e48c933 Mon Sep 17 00:00:00 2001 From: wjcloudy <56305354+wjcloudy@users.noreply.github.com> Date: Fri, 8 Nov 2024 11:04:53 +0000 Subject: [PATCH 123/210] Update .gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index f4dd4582..76d87ea2 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,5 @@ compile.bat # Ignore binary files *.bin +Software/USER_SETTINGS.h +Software/USER_SETTINGS.cpp From c50fd918c12a6608f7abe2be9ec6059074afcfca Mon Sep 17 00:00:00 2001 From: wjcloudy <56305354+wjcloudy@users.noreply.github.com> Date: Fri, 8 Nov 2024 11:07:30 +0000 Subject: [PATCH 124/210] Update .gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 76d87ea2..ea0bcd5a 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,5 @@ compile.bat *.bin Software/USER_SETTINGS.h Software/USER_SETTINGS.cpp +Software/USER_SETTINGS.h +Software/USER_SETTINGS.cpp From 23a6abfec20a247055f4d6c59062ce77ad3635cc Mon Sep 17 00:00:00 2001 From: wjcloudy <56305354+wjcloudy@users.noreply.github.com> Date: Fri, 8 Nov 2024 11:25:21 +0000 Subject: [PATCH 125/210] Update BMW-IX-BATTERY.cpp --- Software/src/battery/BMW-IX-BATTERY.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/Software/src/battery/BMW-IX-BATTERY.cpp b/Software/src/battery/BMW-IX-BATTERY.cpp index f7569277..c0880c2f 100644 --- a/Software/src/battery/BMW-IX-BATTERY.cpp +++ b/Software/src/battery/BMW-IX-BATTERY.cpp @@ -58,9 +58,6 @@ No vehicle log available, SME asks for: TODO - Request batt serial number on F1 8C (already parsing RX) -- Check PWM state required from ACSM -- More Balancing values -- MIN /max cell voltag e- implement safety limits? */ From ae50634ee8fd808e222225a60302acf9e0b9856a Mon Sep 17 00:00:00 2001 From: wjcloudy <56305354+wjcloudy@users.noreply.github.com> Date: Fri, 8 Nov 2024 11:29:44 +0000 Subject: [PATCH 126/210] Update .gitignore --- .gitignore | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.gitignore b/.gitignore index ea0bcd5a..f4dd4582 100644 --- a/.gitignore +++ b/.gitignore @@ -22,7 +22,3 @@ compile.bat # Ignore binary files *.bin -Software/USER_SETTINGS.h -Software/USER_SETTINGS.cpp -Software/USER_SETTINGS.h -Software/USER_SETTINGS.cpp From 271621f99763764c0db7364c9f27b487bc35f68a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Fri, 8 Nov 2024 13:31:24 +0200 Subject: [PATCH 127/210] Revise bit placement in challenge reply --- Software/src/battery/NISSAN-LEAF-BATTERY.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Software/src/battery/NISSAN-LEAF-BATTERY.cpp b/Software/src/battery/NISSAN-LEAF-BATTERY.cpp index 76bf7e13..10b34363 100644 --- a/Software/src/battery/NISSAN-LEAF-BATTERY.cpp +++ b/Software/src/battery/NISSAN-LEAF-BATTERY.cpp @@ -877,6 +877,7 @@ void receive_can_battery(CAN_frame rx_frame) { if (stateMachineClearSOH < 255) { //Intercept the messages based on state machine if (rx_frame.data.u8[0] == 0x06) { // Incoming challenge data! + // BMS should reply with (challenge) 06 67 65 (02 DD 86 43) FF incomingChallenge = ((rx_frame.data.u8[3] << 24) | (rx_frame.data.u8[4] << 16) | (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6]); } @@ -1332,6 +1333,7 @@ void clearSOH(void) { LEAF_CLEAR_SOH.data.u8[6] = 0x00; LEAF_CLEAR_SOH.data.u8[7] = 0x00; transmit_can(&LEAF_CLEAR_SOH, can_config.battery); + // BMS should reply with (challenge) 06 67 65 (02 DD 86 43) FF stateMachineClearSOH = 4; break; case 4: // Send back decoded challenge data @@ -1340,16 +1342,16 @@ void clearSOH(void) { LEAF_CLEAR_SOH.data.u8[1] = 0x0A; LEAF_CLEAR_SOH.data.u8[2] = 0x27; LEAF_CLEAR_SOH.data.u8[3] = 0x66; - LEAF_CLEAR_SOH.data.u8[4] = 0x77; - LEAF_CLEAR_SOH.data.u8[5] = solvedChallenge[0]; - LEAF_CLEAR_SOH.data.u8[6] = solvedChallenge[1]; - LEAF_CLEAR_SOH.data.u8[7] = solvedChallenge[2]; + LEAF_CLEAR_SOH.data.u8[4] = solvedChallenge[0]; + LEAF_CLEAR_SOH.data.u8[5] = solvedChallenge[1]; + LEAF_CLEAR_SOH.data.u8[6] = solvedChallenge[2]; + LEAF_CLEAR_SOH.data.u8[7] = solvedChallenge[3]; transmit_can(&LEAF_CLEAR_SOH, can_config.battery); // BMS should reply 7BB 8 30 01 00 FF FF FF FF FF // Proceed with more data (PID ACK) stateMachineClearSOH = 5; break; case 5: // Reply with even more decoded challenge data - LEAF_CLEAR_SOH.data.u8[0] = solvedChallenge[3]; + LEAF_CLEAR_SOH.data.u8[0] = 0x21; LEAF_CLEAR_SOH.data.u8[1] = solvedChallenge[4]; LEAF_CLEAR_SOH.data.u8[2] = solvedChallenge[5]; LEAF_CLEAR_SOH.data.u8[3] = solvedChallenge[6]; From 00f78ce76cf0682704779d57b3743709c8332ce5 Mon Sep 17 00:00:00 2001 From: wjcloudy <56305354+wjcloudy@users.noreply.github.com> Date: Fri, 8 Nov 2024 12:08:08 +0000 Subject: [PATCH 128/210] Update formatting --- Software/src/battery/BMW-IX-BATTERY.cpp | 923 ++++++++++++++---------- Software/src/battery/BMW-IX-BATTERY.h | 5 +- 2 files changed, 555 insertions(+), 373 deletions(-) diff --git a/Software/src/battery/BMW-IX-BATTERY.cpp b/Software/src/battery/BMW-IX-BATTERY.cpp index c0880c2f..37110fb7 100644 --- a/Software/src/battery/BMW-IX-BATTERY.cpp +++ b/Software/src/battery/BMW-IX-BATTERY.cpp @@ -62,94 +62,269 @@ TODO */ //Vehicle CAN START -CAN_frame BMWiX_06D = {.FD = true, .ext_ID = false, .DLC = 8, .ID = 0x06D, - .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0xFF}}; // 1000ms BDC Output - [0] static [1,2][3,4] counter x2. 3,4 is 9 higher than 1,2 is needed? [5-7] static - -CAN_frame BMWiX_0C0 = {.FD = true, .ext_ID = false, .DLC = 2, .ID = 0x0C0, - .data = {0xF0, 0x08}}; // Keep Alive 2 BDC>SME 200ms First byte cycles F0 > FE second byte 08 static - MINIMUM ID TO KEEP SME AWAKE - -CAN_frame BMWiX_276 = {.FD = true, .ext_ID = false, .DLC = 8, .ID = 0x476, - .data = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC}}; // 5000ms BDC Output - Suspected keep alive Static CONFIRM NEEDED - -CAN_frame BMWiX_2F1 = {.FD = true, .ext_ID = false, .DLC = 8, .ID = 0x2F1, - .data = {0xFF, 0xFF, 0xFF, 0xFF, 0x9B, 0x00, 0xF3, 0xFF}}; // 1000ms BDC Output - Static values - varies at startup - -CAN_frame BMWiX_439 = {.FD = true, .ext_ID = false, .DLC = 4, .ID = 0x439, - .data = {0xFF, 0xBF, 0xFF, 0xFF}}; // 1000ms BDC Output - Static values - -CAN_frame BMWiX_486 = {.FD = true, .ext_ID = false, .DLC = 48, .ID = 0x486, - .data = {0xFE, 0xFF , 0xFE , 0xFF , 0xFE , 0xFF , 0xFE , 0xFF , 0xFE , 0xFF , 0xFE , 0xFF , 0xFE , 0xFF , 0xFE , 0xFF , 0xFE , 0xFF , 0xFE , 0xFF , 0xFE , 0xFF , 0xFE , 0xFF , 0xFE , 0xFF , 0xFE , 0xFF , 0xFF , 0x7F , 0x33 , 0xFD , 0xFD , 0xFD , 0xFD , 0xC0 , 0x41 , 0xFF , 0xFF , 0xFF , 0xFF , 0xFF , 0xFF , 0xFF , 0xFF , 0xFF , 0xFF , 0xFF}}; // 1000ms BDC Output - Suspected keep alive Static CONFIRM NEEDED - -CAN_frame BMWiX_49C = {.FD = true, .ext_ID = false, .DLC = 4, .ID = 0x49C, - .data = {0xD2, 0xF2, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}}; // 1000ms BDC Output - Suspected keep alive Static CONFIRM NEEDED - -CAN_frame BMWiX_510 = {.FD = true, .ext_ID = false, .DLC = 8, .ID = 0x510, - .data = {0x40, 0x10, 0x40, 0x00, 0x6F, 0xDF, 0x19, 0x00}}; // 100ms BDC Output - Static values - -CAN_frame BMWiX_12B8D087 = {.FD = true, .ext_ID = true, .DLC = 2, .ID = 0x12B8D087, - .data = {0xFC, 0xFF}}; // 5000ms SME Output - Static values +CAN_frame BMWiX_06D = { + .FD = true, + .ext_ID = false, + .DLC = 8, + .ID = 0x06D, + .data = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, + 0xFF}}; // 1000ms BDC Output - [0] static [1,2][3,4] counter x2. 3,4 is 9 higher than 1,2 is needed? [5-7] static + +CAN_frame BMWiX_0C0 = { + .FD = true, + .ext_ID = false, + .DLC = 2, + .ID = 0x0C0, + .data = { + 0xF0, + 0x08}}; // Keep Alive 2 BDC>SME 200ms First byte cycles F0 > FE second byte 08 static - MINIMUM ID TO KEEP SME AWAKE + +CAN_frame BMWiX_276 = {.FD = true, + .ext_ID = false, + .DLC = 8, + .ID = 0x476, + .data = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFC}}; // 5000ms BDC Output - Suspected keep alive Static CONFIRM NEEDED + +CAN_frame BMWiX_2F1 = { + .FD = true, + .ext_ID = false, + .DLC = 8, + .ID = 0x2F1, + .data = {0xFF, 0xFF, 0xFF, 0xFF, 0x9B, 0x00, 0xF3, 0xFF}}; // 1000ms BDC Output - Static values - varies at startup + +CAN_frame BMWiX_439 = {.FD = true, + .ext_ID = false, + .DLC = 4, + .ID = 0x439, + .data = {0xFF, 0xBF, 0xFF, 0xFF}}; // 1000ms BDC Output - Static values + +CAN_frame + BMWiX_486 = + { + .FD = true, + .ext_ID = false, + .DLC = 48, + .ID = 0x486, + .data = + { + 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, + 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, + 0xFE, 0xFF, 0xFF, 0x7F, 0x33, 0xFD, 0xFD, 0xFD, 0xFD, 0xC0, 0x41, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}}; // 1000ms BDC Output - Suspected keep alive Static CONFIRM NEEDED + +CAN_frame BMWiX_49C = {.FD = true, + .ext_ID = false, + .DLC = 4, + .ID = 0x49C, + .data = {0xD2, 0xF2, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF}}; // 1000ms BDC Output - Suspected keep alive Static CONFIRM NEEDED + +CAN_frame BMWiX_510 = {.FD = true, + .ext_ID = false, + .DLC = 8, + .ID = 0x510, + .data = {0x40, 0x10, 0x40, 0x00, 0x6F, 0xDF, 0x19, 0x00}}; // 100ms BDC Output - Static values + +CAN_frame BMWiX_12B8D087 = {.FD = true, + .ext_ID = true, + .DLC = 2, + .ID = 0x12B8D087, + .data = {0xFC, 0xFF}}; // 5000ms SME Output - Static values //Vehicle CAN END //Request Data CAN START -CAN_frame BMWiX_6F4 = {.FD = true, .ext_ID = false, .DLC = 5, .ID = 0x6F4, .data = {0x07, 0x03, 0x22, 0xE5, 0xC7}}; // Generic UDS Request data from SME. byte 4 selects requested value -CAN_frame BMWiX_6F4_REQUEST_SLEEPMODE = {.FD = true, .ext_ID = false, .DLC = 4, .ID = 0x6F4, .data = {0x07, 0x02, 0x11, 0x04}}; // UDS Request Request BMS/SME goes to Sleep Mode -CAN_frame BMWiX_6F4_REQUEST_HARD_RESET = {.FD = true, .ext_ID = false, .DLC = 4, .ID = 0x6F4, .data = {0x07, 0x02, 0x11, 0x01}}; // UDS Request Hard reset of BMS/SME -CAN_frame BMWiX_6F4_REQUEST_CELL_TEMP = {.FD = true, .ext_ID = false, .DLC = 5, .ID = 0x6F4, .data = {0x07, 0x03, 0x22, 0xDD, 0xC0}}; // UDS Request Cell Temperatures -CAN_frame BMWiX_6F4_REQUEST_SOC = {.FD = true, .ext_ID = false, .DLC = 5, .ID = 0x6F4, .data = {0x07, 0x03, 0x22, 0xE5, 0xCE}}; // Min/Avg/Max SOC% -CAN_frame BMWiX_6F4_REQUEST_CAPACITY = {.FD = true, .ext_ID = false, .DLC = 5, .ID = 0x6F4, .data = {0x07, 0x03, 0x22, 0xE5, 0xC7}}; //Current and max capacity kWh. Stored in kWh as 0.01 scale with -50 bias -CAN_frame BMWiX_6F4_REQUEST_MINMAXCELLV = {.FD = true, .ext_ID = false, .DLC = 5, .ID = 0x6F4, .data = {0x07, 0x03, 0x22, 0xE5, 0x53}}; //Min and max cell voltage 10V = Qualifier Invalid -CAN_frame BMWiX_6F4_REQUEST_MAINVOLTAGE_POSTCONTACTOR = {.FD = true, .ext_ID = false, .DLC = 5, .ID = 0x6F4, .data = {0x07, 0x03, 0x22, 0xE5, 0x4A}}; //Main Battery Voltage (After Contactor) -CAN_frame BMWiX_6F4_REQUEST_MAINVOLTAGE_PRECONTACTOR = {.FD = true, .ext_ID = false, .DLC = 5, .ID = 0x6F4, .data = {0x07, 0x03, 0x22, 0xE5, 0x4D}}; //Main Battery Voltage (Pre Contactor) -CAN_frame BMWiX_6F4_REQUEST_BATTERYCURRENT = {.FD = true, .ext_ID = false, .DLC = 5, .ID = 0x6F4, .data = {0x07, 0x03, 0x22, 0xE5, 0x61}}; //Current amps 32bit signed MSB. dA . negative is discharge -CAN_frame BMWiX_6F4_REQUEST_CELL_VOLTAGE = {.FD = true, .ext_ID = false, .DLC = 5, .ID = 0x6F4, .data = {0x07, 0x03, 0x22, 0xE5, 0x54}}; //MultiFrameIndividual Cell Voltages -CAN_frame BMWiX_6F4_REQUEST_T30VOLTAGE = {.FD = true, .ext_ID = false, .DLC = 5, .ID = 0x6F4, .data = {0x07, 0x03, 0x22, 0xE5, 0xA7}}; //Terminal 30 Voltage (12V SME supply) -CAN_frame BMWiX_6F4_REQUEST_EOL_ISO = {.FD = true, .ext_ID = false, .DLC = 5, .ID = 0x6F4, .data = {0x07, 0x03, 0x22, 0xA8, 0x60}}; //Request EOL Reading including ISO -CAN_frame BMWiX_6F4_REQUEST_SOH = {.FD = true, .ext_ID = false, .DLC = 5, .ID = 0x6F4, .data = {0x07, 0x03, 0x22, 0xE5, 0x45}}; //SOH Max Min Mean Request -CAN_frame BMWiX_6F4_REQUEST_DATASUMMARY = {.FD = true, .ext_ID = false, .DLC = 5, .ID = 0x6F4, .data = {0x07, 0x03, 0x22, 0xE5, 0x45}}; //MultiFrame Summary Request, includes SOC/SOH/MinMax/MaxCapac/RemainCapac/max v and t at last charge. slow refreshrate -CAN_frame BMWiX_6F4_REQUEST_PYRO = {.FD = true, .ext_ID = false, .DLC = 5, .ID = 0x6F4, .data = {0x07, 0x03, 0x22, 0xAC, 0x93}}; //Pyro Status -CAN_frame BMWiX_6F4_REQUEST_UPTIME = {.FD = true, .ext_ID = false, .DLC = 5, .ID = 0x6F4, .data = {0x07, 0x03, 0x22, 0xE4, 0xC0}}; // Uptime and Vehicle Time Status -CAN_frame BMWiX_6F4_REQUEST_HVIL = {.FD = true, .ext_ID = false, .DLC = 5, .ID = 0x6F4, .data = {0x07, 0x03, 0x22, 0xE5, 0x69}}; // Request HVIL State -CAN_frame BMWiX_6F4_REQUEST_BALANCINGSTATUS = {.FD = true, .ext_ID = false, .DLC = 5, .ID = 0x6F4, .data = {0x07, 0x03, 0x22, 0xE4, 0xCA}}; // Request Balancing Data -CAN_frame BMWiX_6F4_REQUEST_MAX_CHARGE_DISCHARGE_AMPS = {.FD = true, .ext_ID = false, .DLC = 5, .ID = 0x6F4, .data = {0x07, 0x03, 0x22, 0xE5, 0x62}}; // Request allowable charge discharge amps -CAN_frame BMWiX_6F4_REQUEST_VOLTAGE_QUALIFIER_CHECK = {.FD = true, .ext_ID = false, .DLC = 5, .ID = 0x6F4, .data = {0x07, 0x03, 0x22, 0xE5, 0x4B}}; // Request HV Voltage Qualifier -CAN_frame BMWiX_6F4_REQUEST_CONTACTORS_CLOSE = {.FD = true, .ext_ID = false, .DLC = 6, .ID = 0x6F4, .data = {0x07, 0x03, 0x22, 0xE5, 0x51, 0x01}}; // Request Contactors Close - Unconfirmed -CAN_frame BMWiX_6F4_REQUEST_CONTACTORS_OPEN = {.FD = true, .ext_ID = false, .DLC = 6, .ID = 0x6F4, .data = {0x07, 0x03, 0x22, 0xE5, 0x51, 0x01}}; // Request Contactors Open - Unconfirmed -CAN_frame BMWiX_6F4_REQUEST_BALANCING_START = {.FD = true, .ext_ID = false, .DLC = 6, .ID = 0x6F4, .data = {0xF4, 0x04, 0x71, 0x01, 0xAE, 0x77}}; // Request Balancing command? -CAN_frame BMWiX_6F4_REQUEST_PACK_VOLTAGE_LIMITS = {.FD = true, .ext_ID = false, .DLC = 5, .ID = 0x6F4, .data = {0x07, 0x03, 0x22, 0xE5, 0x4C}}; // Request pack voltage limits - - -CAN_frame BMWiX_6F4_CONTINUE_DATA = {.FD = true, .ext_ID = false, .DLC = 4, .ID = 0x6F4, .data = {0x07, 0x30, 0x00, 0x02}}; +CAN_frame BMWiX_6F4 = { + .FD = true, + .ext_ID = false, + .DLC = 5, + .ID = 0x6F4, + .data = {0x07, 0x03, 0x22, 0xE5, 0xC7}}; // Generic UDS Request data from SME. byte 4 selects requested value +CAN_frame BMWiX_6F4_REQUEST_SLEEPMODE = { + .FD = true, + .ext_ID = false, + .DLC = 4, + .ID = 0x6F4, + .data = {0x07, 0x02, 0x11, 0x04}}; // UDS Request Request BMS/SME goes to Sleep Mode +CAN_frame BMWiX_6F4_REQUEST_HARD_RESET = {.FD = true, + .ext_ID = false, + .DLC = 4, + .ID = 0x6F4, + .data = {0x07, 0x02, 0x11, 0x01}}; // UDS Request Hard reset of BMS/SME +CAN_frame BMWiX_6F4_REQUEST_CELL_TEMP = {.FD = true, + .ext_ID = false, + .DLC = 5, + .ID = 0x6F4, + .data = {0x07, 0x03, 0x22, 0xDD, 0xC0}}; // UDS Request Cell Temperatures +CAN_frame BMWiX_6F4_REQUEST_SOC = {.FD = true, + .ext_ID = false, + .DLC = 5, + .ID = 0x6F4, + .data = {0x07, 0x03, 0x22, 0xE5, 0xCE}}; // Min/Avg/Max SOC% +CAN_frame BMWiX_6F4_REQUEST_CAPACITY = { + .FD = true, + .ext_ID = false, + .DLC = 5, + .ID = 0x6F4, + .data = {0x07, 0x03, 0x22, 0xE5, 0xC7}}; //Current and max capacity kWh. Stored in kWh as 0.01 scale with -50 bias +CAN_frame BMWiX_6F4_REQUEST_MINMAXCELLV = { + .FD = true, + .ext_ID = false, + .DLC = 5, + .ID = 0x6F4, + .data = {0x07, 0x03, 0x22, 0xE5, 0x53}}; //Min and max cell voltage 10V = Qualifier Invalid +CAN_frame BMWiX_6F4_REQUEST_MAINVOLTAGE_POSTCONTACTOR = { + .FD = true, + .ext_ID = false, + .DLC = 5, + .ID = 0x6F4, + .data = {0x07, 0x03, 0x22, 0xE5, 0x4A}}; //Main Battery Voltage (After Contactor) +CAN_frame BMWiX_6F4_REQUEST_MAINVOLTAGE_PRECONTACTOR = { + .FD = true, + .ext_ID = false, + .DLC = 5, + .ID = 0x6F4, + .data = {0x07, 0x03, 0x22, 0xE5, 0x4D}}; //Main Battery Voltage (Pre Contactor) +CAN_frame BMWiX_6F4_REQUEST_BATTERYCURRENT = { + .FD = true, + .ext_ID = false, + .DLC = 5, + .ID = 0x6F4, + .data = {0x07, 0x03, 0x22, 0xE5, 0x61}}; //Current amps 32bit signed MSB. dA . negative is discharge +CAN_frame BMWiX_6F4_REQUEST_CELL_VOLTAGE = { + .FD = true, + .ext_ID = false, + .DLC = 5, + .ID = 0x6F4, + .data = {0x07, 0x03, 0x22, 0xE5, 0x54}}; //MultiFrameIndividual Cell Voltages +CAN_frame BMWiX_6F4_REQUEST_T30VOLTAGE = { + .FD = true, + .ext_ID = false, + .DLC = 5, + .ID = 0x6F4, + .data = {0x07, 0x03, 0x22, 0xE5, 0xA7}}; //Terminal 30 Voltage (12V SME supply) +CAN_frame BMWiX_6F4_REQUEST_EOL_ISO = {.FD = true, + .ext_ID = false, + .DLC = 5, + .ID = 0x6F4, + .data = {0x07, 0x03, 0x22, 0xA8, 0x60}}; //Request EOL Reading including ISO +CAN_frame BMWiX_6F4_REQUEST_SOH = {.FD = true, + .ext_ID = false, + .DLC = 5, + .ID = 0x6F4, + .data = {0x07, 0x03, 0x22, 0xE5, 0x45}}; //SOH Max Min Mean Request +CAN_frame BMWiX_6F4_REQUEST_DATASUMMARY = { + .FD = true, + .ext_ID = false, + .DLC = 5, + .ID = 0x6F4, + .data = { + 0x07, 0x03, 0x22, 0xE5, + 0x45}}; //MultiFrame Summary Request, includes SOC/SOH/MinMax/MaxCapac/RemainCapac/max v and t at last charge. slow refreshrate +CAN_frame BMWiX_6F4_REQUEST_PYRO = {.FD = true, + .ext_ID = false, + .DLC = 5, + .ID = 0x6F4, + .data = {0x07, 0x03, 0x22, 0xAC, 0x93}}; //Pyro Status +CAN_frame BMWiX_6F4_REQUEST_UPTIME = {.FD = true, + .ext_ID = false, + .DLC = 5, + .ID = 0x6F4, + .data = {0x07, 0x03, 0x22, 0xE4, 0xC0}}; // Uptime and Vehicle Time Status +CAN_frame BMWiX_6F4_REQUEST_HVIL = {.FD = true, + .ext_ID = false, + .DLC = 5, + .ID = 0x6F4, + .data = {0x07, 0x03, 0x22, 0xE5, 0x69}}; // Request HVIL State +CAN_frame BMWiX_6F4_REQUEST_BALANCINGSTATUS = {.FD = true, + .ext_ID = false, + .DLC = 5, + .ID = 0x6F4, + .data = {0x07, 0x03, 0x22, 0xE4, 0xCA}}; // Request Balancing Data +CAN_frame BMWiX_6F4_REQUEST_MAX_CHARGE_DISCHARGE_AMPS = { + .FD = true, + .ext_ID = false, + .DLC = 5, + .ID = 0x6F4, + .data = {0x07, 0x03, 0x22, 0xE5, 0x62}}; // Request allowable charge discharge amps +CAN_frame BMWiX_6F4_REQUEST_VOLTAGE_QUALIFIER_CHECK = { + .FD = true, + .ext_ID = false, + .DLC = 5, + .ID = 0x6F4, + .data = {0x07, 0x03, 0x22, 0xE5, 0x4B}}; // Request HV Voltage Qualifier +CAN_frame BMWiX_6F4_REQUEST_CONTACTORS_CLOSE = { + .FD = true, + .ext_ID = false, + .DLC = 6, + .ID = 0x6F4, + .data = {0x07, 0x03, 0x22, 0xE5, 0x51, 0x01}}; // Request Contactors Close - Unconfirmed +CAN_frame BMWiX_6F4_REQUEST_CONTACTORS_OPEN = { + .FD = true, + .ext_ID = false, + .DLC = 6, + .ID = 0x6F4, + .data = {0x07, 0x03, 0x22, 0xE5, 0x51, 0x01}}; // Request Contactors Open - Unconfirmed +CAN_frame BMWiX_6F4_REQUEST_BALANCING_START = { + .FD = true, + .ext_ID = false, + .DLC = 6, + .ID = 0x6F4, + .data = {0xF4, 0x04, 0x71, 0x01, 0xAE, 0x77}}; // Request Balancing command? +CAN_frame BMWiX_6F4_REQUEST_PACK_VOLTAGE_LIMITS = { + .FD = true, + .ext_ID = false, + .DLC = 5, + .ID = 0x6F4, + .data = {0x07, 0x03, 0x22, 0xE5, 0x4C}}; // Request pack voltage limits + +CAN_frame BMWiX_6F4_CONTINUE_DATA = {.FD = true, + .ext_ID = false, + .DLC = 4, + .ID = 0x6F4, + .data = {0x07, 0x30, 0x00, 0x02}}; //Action Requests: -CAN_frame BMW_10B = {.FD = true, .ext_ID = false, .DLC = 3, .ID = 0x10B, .data = {0xCD, 0x00, 0xFC}}; // Contactor closing command? - -CAN_frame BMWiX_6F4_CELL_SOC = {.FD = true, .ext_ID = false, .DLC = 5, .ID = 0x6F4, .data = {0x07, 0x03, 0x22, 0xE5, 0x9A}}; -CAN_frame BMWiX_6F4_CELL_TEMP = {.FD = true, .ext_ID = false, .DLC = 5, .ID = 0x6F4, .data = {0x07, 0x03, 0x22, 0xE5, 0xCA}}; +CAN_frame BMW_10B = {.FD = true, + .ext_ID = false, + .DLC = 3, + .ID = 0x10B, + .data = {0xCD, 0x00, 0xFC}}; // Contactor closing command? + +CAN_frame BMWiX_6F4_CELL_SOC = {.FD = true, + .ext_ID = false, + .DLC = 5, + .ID = 0x6F4, + .data = {0x07, 0x03, 0x22, 0xE5, 0x9A}}; +CAN_frame BMWiX_6F4_CELL_TEMP = {.FD = true, + .ext_ID = false, + .DLC = 5, + .ID = 0x6F4, + .data = {0x07, 0x03, 0x22, 0xE5, 0xCA}}; //Request Data CAN End static bool battery_awake = false; //Setup UDS values to poll for -CAN_frame* UDS_REQUESTS100MS[] = { - &BMWiX_6F4_REQUEST_CELL_TEMP, - &BMWiX_6F4_REQUEST_SOC, - &BMWiX_6F4_REQUEST_CAPACITY, - &BMWiX_6F4_REQUEST_MINMAXCELLV, - &BMWiX_6F4_REQUEST_MAINVOLTAGE_POSTCONTACTOR, - &BMWiX_6F4_REQUEST_MAINVOLTAGE_PRECONTACTOR, - &BMWiX_6F4_REQUEST_BATTERYCURRENT, - &BMWiX_6F4_REQUEST_CELL_VOLTAGE, - &BMWiX_6F4_REQUEST_T30VOLTAGE, - &BMWiX_6F4_REQUEST_SOH, - &BMWiX_6F4_REQUEST_UPTIME, - &BMWiX_6F4_REQUEST_PYRO, - &BMWiX_6F4_REQUEST_EOL_ISO, - &BMWiX_6F4_REQUEST_HVIL, - &BMWiX_6F4_REQUEST_MAX_CHARGE_DISCHARGE_AMPS, - &BMWiX_6F4_REQUEST_BALANCINGSTATUS, - &BMWiX_6F4_REQUEST_PACK_VOLTAGE_LIMITS - }; -int numUDSreqs = sizeof(UDS_REQUESTS100MS) / sizeof(UDS_REQUESTS100MS[0]); // Number of elements in the array +CAN_frame* UDS_REQUESTS100MS[] = {&BMWiX_6F4_REQUEST_CELL_TEMP, + &BMWiX_6F4_REQUEST_SOC, + &BMWiX_6F4_REQUEST_CAPACITY, + &BMWiX_6F4_REQUEST_MINMAXCELLV, + &BMWiX_6F4_REQUEST_MAINVOLTAGE_POSTCONTACTOR, + &BMWiX_6F4_REQUEST_MAINVOLTAGE_PRECONTACTOR, + &BMWiX_6F4_REQUEST_BATTERYCURRENT, + &BMWiX_6F4_REQUEST_CELL_VOLTAGE, + &BMWiX_6F4_REQUEST_T30VOLTAGE, + &BMWiX_6F4_REQUEST_SOH, + &BMWiX_6F4_REQUEST_UPTIME, + &BMWiX_6F4_REQUEST_PYRO, + &BMWiX_6F4_REQUEST_EOL_ISO, + &BMWiX_6F4_REQUEST_HVIL, + &BMWiX_6F4_REQUEST_MAX_CHARGE_DISCHARGE_AMPS, + &BMWiX_6F4_REQUEST_BALANCINGSTATUS, + &BMWiX_6F4_REQUEST_PACK_VOLTAGE_LIMITS}; +int numUDSreqs = sizeof(UDS_REQUESTS100MS) / sizeof(UDS_REQUESTS100MS[0]); // Number of elements in the array //iX Intermediate vars static bool battery_info_available = false; @@ -161,9 +336,9 @@ static int16_t battery_voltage_after_contactor = 0; static int16_t min_soc_state = 50; static int16_t avg_soc_state = 50; static int16_t max_soc_state = 50; -static int16_t min_soh_state = 99;// Uses E5 45, also available in 78 73 -static int16_t avg_soh_state = 99;// Uses E5 45, also available in 78 73 -static int16_t max_soh_state = 99;// Uses E5 45, also available in 78 73 +static int16_t min_soh_state = 99; // Uses E5 45, also available in 78 73 +static int16_t avg_soh_state = 99; // Uses E5 45, also available in 78 73 +static int16_t max_soh_state = 99; // Uses E5 45, also available in 78 73 static uint16_t max_design_voltage = 0; static uint16_t min_design_voltage = 0; static int32_t remaining_capacity = 0; @@ -179,37 +354,36 @@ static unsigned long max_cell_voltage_lastchanged = 0; static unsigned min_cell_voltage_lastreceived = 0; static unsigned max_cell_voltage_lastreceived = 0; static int16_t battery_power = 0; -static uint32_t sme_uptime = 0; //Uses E4 C0 -static int16_t allowable_charge_amps = 0; //E5 62 -static int16_t allowable_discharge_amps = 0; //E5 62 -static int32_t iso_safety_positive = 0; //Uses A8 60 -static int32_t iso_safety_negative = 0; //Uses A8 60 -static int32_t iso_safety_parallel = 0; //Uses A8 60 -static int16_t count_full_charges = 0; //TODO 42 -static int16_t count_charges = 0; //TODO 42 -static int16_t hvil_status = 0; -static int16_t voltage_qualifier_status = 0; //0 = Valid, 1 = Invalid -static int16_t balancing_status = 0; //4 = not active -static uint8_t contactors_closed = 0; //TODO E5 BF or E5 51 -static uint8_t contactor_status_precharge = 0; //TODO E5 BF -static uint8_t contactor_status_negative = 0; //TODO E5 BF -static uint8_t contactor_status_positive = 0; //TODO E5 BF -static uint8_t pyro_status_pss1 = 0; //Using AC 93 -static uint8_t pyro_status_pss4 = 0; //Using AC 93 -static uint8_t pyro_status_pss6 = 0; //Using AC 93 +static uint32_t sme_uptime = 0; //Uses E4 C0 +static int16_t allowable_charge_amps = 0; //E5 62 +static int16_t allowable_discharge_amps = 0; //E5 62 +static int32_t iso_safety_positive = 0; //Uses A8 60 +static int32_t iso_safety_negative = 0; //Uses A8 60 +static int32_t iso_safety_parallel = 0; //Uses A8 60 +static int16_t count_full_charges = 0; //TODO 42 +static int16_t count_charges = 0; //TODO 42 +static int16_t hvil_status = 0; +static int16_t voltage_qualifier_status = 0; //0 = Valid, 1 = Invalid +static int16_t balancing_status = 0; //4 = not active +static uint8_t contactors_closed = 0; //TODO E5 BF or E5 51 +static uint8_t contactor_status_precharge = 0; //TODO E5 BF +static uint8_t contactor_status_negative = 0; //TODO E5 BF +static uint8_t contactor_status_positive = 0; //TODO E5 BF +static uint8_t pyro_status_pss1 = 0; //Using AC 93 +static uint8_t pyro_status_pss4 = 0; //Using AC 93 +static uint8_t pyro_status_pss6 = 0; //Using AC 93 static uint8_t uds_req_id_counter = 0; -const unsigned long STALE_PERIOD = STALE_PERIOD_CONFIG ; // Time in milliseconds to check for staleness (e.g., 5000 ms = 5 seconds) +const unsigned long STALE_PERIOD = + STALE_PERIOD_CONFIG; // Time in milliseconds to check for staleness (e.g., 5000 ms = 5 seconds) - - -static byte iX_0C0_counter = 0xF0; // Initialize to 0xF0 +static byte iX_0C0_counter = 0xF0; // Initialize to 0xF0 //End iX Intermediate vars static uint8_t current_cell_polled = 0; // Function to check if a value has gone stale over a specified time period -bool isStale(int16_t currentValue, uint16_t &lastValue, unsigned long &lastChangeTime) { +bool isStale(int16_t currentValue, uint16_t& lastValue, unsigned long& lastChangeTime) { unsigned long currentTime = millis(); // Check if the value has changed @@ -244,7 +418,7 @@ static byte increment_0C0_counter(byte counter) { counter++; // Reset to 0xF0 if it exceeds 0xFE if (counter > 0xFE) { - counter = 0xF0; + counter = 0xF0; } return counter; } @@ -263,12 +437,11 @@ void update_values_battery() { //This function maps all the values fetched via datalayer.battery.status.soh_pptt = min_soh_state; - datalayer.battery.status.max_discharge_power_W = 3200; //10000; //Aux HV Port has 100A Fuse + datalayer.battery.status.max_discharge_power_W = 3200; //10000; //Aux HV Port has 100A Fuse //datalayer.battery.status.max_charge_power_W = 3200; //10000; //Aux HV Port has 100A Fuse Moved to Ramping - - // Charge power is set in .h file + // Charge power is set in .h file if (datalayer.battery.status.real_soc > 9900) { datalayer.battery.status.max_charge_power_W = MAX_CHARGE_POWER_WHEN_TOPBALANCING_W; } else if (datalayer.battery.status.real_soc > RAMPDOWN_SOC) { @@ -280,7 +453,6 @@ void update_values_battery() { //This function maps all the values fetched via datalayer.battery.status.max_charge_power_W = MAX_CHARGE_POWER_ALLOWED_W; } - battery_power = (datalayer.battery.status.current_dA * (datalayer.battery.status.voltage_dV / 100)); datalayer.battery.status.active_power_W = battery_power; @@ -289,22 +461,22 @@ void update_values_battery() { //This function maps all the values fetched via datalayer.battery.status.temperature_max_dC = max_battery_temperature; - - - if (isStale(min_cell_voltage, datalayer.battery.status.cell_min_voltage_mV, min_cell_voltage_lastchanged)) {//TODO prevent flipflop after error + if (isStale(min_cell_voltage, datalayer.battery.status.cell_min_voltage_mV, + min_cell_voltage_lastchanged)) { //TODO prevent flipflop after error Serial.println("min_cell_voltage has gone stale."); - datalayer.battery.status.cell_min_voltage_mV = 9999; //Stale values force stop - set_event(EVENT_CAN_RX_FAILURE,0); + datalayer.battery.status.cell_min_voltage_mV = 9999; //Stale values force stop + set_event(EVENT_CAN_RX_FAILURE, 0); } else { - datalayer.battery.status.cell_min_voltage_mV = min_cell_voltage; //Value is alive + datalayer.battery.status.cell_min_voltage_mV = min_cell_voltage; //Value is alive } - if (isStale(max_cell_voltage, datalayer.battery.status.cell_max_voltage_mV, max_cell_voltage_lastchanged)) { //TODO prevent flipflop after error + if (isStale(max_cell_voltage, datalayer.battery.status.cell_max_voltage_mV, + max_cell_voltage_lastchanged)) { //TODO prevent flipflop after error Serial.println("max_cell_voltage has gone stale."); - datalayer.battery.status.cell_max_voltage_mV = 9999; //Stale values force stop - set_event(EVENT_CAN_RX_FAILURE,0); + datalayer.battery.status.cell_max_voltage_mV = 9999; //Stale values force stop + set_event(EVENT_CAN_RX_FAILURE, 0); } else { - datalayer.battery.status.cell_max_voltage_mV = max_cell_voltage; //Value is alive + datalayer.battery.status.cell_max_voltage_mV = max_cell_voltage; //Value is alive } datalayer_extended.bmwix.min_cell_voltage_data_age = (millis() - min_cell_voltage_lastchanged); @@ -315,11 +487,11 @@ void update_values_battery() { //This function maps all the values fetched via datalayer.battery.info.min_design_voltage_dV = min_design_voltage; - datalayer.battery.info.number_of_cells = 108; //init with 108S before autodetection + datalayer.battery.info.number_of_cells = 108; //init with 108S before autodetection datalayer_extended.bmwix.T30_Voltage = terminal30_12v_voltage; - datalayer_extended.bmwix.hvil_status= hvil_status; + datalayer_extended.bmwix.hvil_status = hvil_status; datalayer_extended.bmwix.bms_uptime = sme_uptime; @@ -337,25 +509,19 @@ void update_values_battery() { //This function maps all the values fetched via datalayer_extended.bmwix.allowable_charge_amps = allowable_charge_amps; - datalayer_extended.bmwix.allowable_discharge_amps= allowable_discharge_amps; + datalayer_extended.bmwix.allowable_discharge_amps = allowable_discharge_amps; datalayer_extended.bmwix.balancing_status = balancing_status; datalayer_extended.bmwix.battery_voltage_after_contactor = battery_voltage_after_contactor; - - - - if (battery_info_available) { // If we have data from battery - override the defaults to suit - datalayer.battery.info.max_design_voltage_dV = max_design_voltage; - datalayer.battery.info.min_design_voltage_dV = min_design_voltage; - datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV; - datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV; + datalayer.battery.info.max_design_voltage_dV = max_design_voltage; + datalayer.battery.info.min_design_voltage_dV = min_design_voltage; + datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV; + datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV; } - - } void receive_can_battery(CAN_frame rx_frame) { battery_awake = true; @@ -364,179 +530,198 @@ void receive_can_battery(CAN_frame rx_frame) { break; case 0x607: //SME responds to UDS requests on 0x607 - if (rx_frame.DLC > 6 && rx_frame.data.u8[0] == 0xF4 && rx_frame.data.u8[1] == 0x10 && rx_frame.data.u8[2] == 0xE3 && rx_frame.data.u8[3] == 0x62 && rx_frame.data.u8[4] == 0xE5){ - //First of multi frame data - Parse the first frame - if (rx_frame.DLC = 64 && rx_frame.data.u8[5] == 0x54) { //Individual Cell Voltages - First Frame - int start_index = 6; //Data starts here - int voltage_index = 0; //Start cell ID - int num_voltages = 29; // number of voltage readings to get - for (int i = start_index; i < (start_index + num_voltages * 2); i += 2) { - uint16_t voltage = (rx_frame.data.u8[i] <<8) | rx_frame.data.u8[i + 1]; - if (voltage < 10000) { //Check reading is plausible - otherwise ignore - datalayer.battery.status.cell_voltages_mV[voltage_index] = voltage; - } - voltage_index++; - } + if (rx_frame.DLC > 6 && rx_frame.data.u8[0] == 0xF4 && rx_frame.data.u8[1] == 0x10 && + rx_frame.data.u8[2] == 0xE3 && rx_frame.data.u8[3] == 0x62 && rx_frame.data.u8[4] == 0xE5) { + //First of multi frame data - Parse the first frame + if (rx_frame.DLC = 64 && rx_frame.data.u8[5] == 0x54) { //Individual Cell Voltages - First Frame + int start_index = 6; //Data starts here + int voltage_index = 0; //Start cell ID + int num_voltages = 29; // number of voltage readings to get + for (int i = start_index; i < (start_index + num_voltages * 2); i += 2) { + uint16_t voltage = (rx_frame.data.u8[i] << 8) | rx_frame.data.u8[i + 1]; + if (voltage < 10000) { //Check reading is plausible - otherwise ignore + datalayer.battery.status.cell_voltages_mV[voltage_index] = voltage; } - + voltage_index++; + } + } + //Frame has continued data - so request it transmit_can(&BMWiX_6F4_CONTINUE_DATA, can_config.battery); } - if (rx_frame.DLC = 64 && rx_frame.data.u8[0] == 0xF4 && rx_frame.data.u8[1] == 0x21){ //Individual Cell Voltages - 1st Continue frame - int start_index = 2; //Data starts here - int voltage_index = 29; //Start cell ID - int num_voltages = 31; // number of voltage readings to get - for (int i = start_index; i < (start_index + num_voltages * 2); i += 2) { - uint16_t voltage = (rx_frame.data.u8[i] <<8) | rx_frame.data.u8[i + 1]; - if (voltage < 10000) { //Check reading is plausible - otherwise ignore - datalayer.battery.status.cell_voltages_mV[voltage_index] = voltage; - } - voltage_index++; - } + if (rx_frame.DLC = 64 && rx_frame.data.u8[0] == 0xF4 && + rx_frame.data.u8[1] == 0x21) { //Individual Cell Voltages - 1st Continue frame + int start_index = 2; //Data starts here + int voltage_index = 29; //Start cell ID + int num_voltages = 31; // number of voltage readings to get + for (int i = start_index; i < (start_index + num_voltages * 2); i += 2) { + uint16_t voltage = (rx_frame.data.u8[i] << 8) | rx_frame.data.u8[i + 1]; + if (voltage < 10000) { //Check reading is plausible - otherwise ignore + datalayer.battery.status.cell_voltages_mV[voltage_index] = voltage; + } + voltage_index++; + } + } + + if (rx_frame.DLC = 64 && rx_frame.data.u8[0] == 0xF4 && + rx_frame.data.u8[1] == 0x22) { //Individual Cell Voltages - 2nd Continue frame + int start_index = 2; //Data starts here + int voltage_index = 60; //Start cell ID + int num_voltages = 31; // number of voltage readings to get + for (int i = start_index; i < (start_index + num_voltages * 2); i += 2) { + uint16_t voltage = (rx_frame.data.u8[i] << 8) | rx_frame.data.u8[i + 1]; + if (voltage < 10000) { //Check reading is plausible - otherwise ignore + datalayer.battery.status.cell_voltages_mV[voltage_index] = voltage; + } + voltage_index++; + } + } + + if (rx_frame.DLC = 64 && rx_frame.data.u8[0] == 0xF4 && + rx_frame.data.u8[1] == 0x23) { //Individual Cell Voltages - 3rd Continue frame + int start_index = 2; //Data starts here + int voltage_index = 91; //Start cell ID + int num_voltages; + if (rx_frame.data.u8[12] == 0xFF && rx_frame.data.u8[13] == 0xFF) { //97th cell is blank - assume 96S Battery + num_voltages = 5; // number of voltage readings to get - 6 more to get on 96S + datalayer.battery.info.number_of_cells = 96; + } else { //We have data in 97th cell, assume 108S Battery + num_voltages = 17; // number of voltage readings to get - 17 more to get on 108S + datalayer.battery.info.number_of_cells = 108; + } + + for (int i = start_index; i < (start_index + num_voltages * 2); i += 2) { + uint16_t voltage = (rx_frame.data.u8[i] << 8) | rx_frame.data.u8[i + 1]; + datalayer.battery.status.cell_voltages_mV[voltage_index++] = voltage; + } + } + if (rx_frame.DLC = 7 && rx_frame.data.u8[4] == 0x4D) { //Main Battery Voltage (Pre Contactor) + battery_voltage = (rx_frame.data.u8[5] << 8 | rx_frame.data.u8[6]) / 10; + } + + if (rx_frame.DLC = 7 && rx_frame.data.u8[4] == 0x4A) { //Main Battery Voltage (After Contactor) + battery_voltage_after_contactor = (rx_frame.data.u8[5] << 8 | rx_frame.data.u8[6]) / 10; + } + + if (rx_frame.DLC = 12 && rx_frame.data.u8[4] == 0xE5 && + rx_frame.data.u8[5] == 0x61) { //Current amps 32bit signed MSB. dA . negative is discharge + battery_current = ((int32_t)((rx_frame.data.u8[6] << 24) | (rx_frame.data.u8[7] << 16) | + (rx_frame.data.u8[8] << 8) | rx_frame.data.u8[9])) * + 0.1; + } + + if (rx_frame.DLC = 64 && rx_frame.data.u8[4] == 0xE4 && rx_frame.data.u8[5] == 0xCA) { //Balancing Data + balancing_status = (rx_frame.data.u8[6]); //4 = No symmetry mode active, invalid qualifier + } + if (rx_frame.DLC = 7 && rx_frame.data.u8[4] == 0xE5 && rx_frame.data.u8[5] == 0xCE) { //Min/Avg/Max SOC% + min_soc_state = (rx_frame.data.u8[8] << 8 | rx_frame.data.u8[9]); + avg_soc_state = (rx_frame.data.u8[6] << 8 | rx_frame.data.u8[7]); + max_soc_state = (rx_frame.data.u8[10] << 8 | rx_frame.data.u8[11]); + } + + if (rx_frame.DLC = + 12 && rx_frame.data.u8[4] == 0xE5 && + rx_frame.data.u8[5] == 0xC7) { //Current and max capacity kWh. Stored in kWh as 0.01 scale with -50 bias + remaining_capacity = ((rx_frame.data.u8[6] << 8 | rx_frame.data.u8[7]) * 10) - 50000; + max_capacity = ((rx_frame.data.u8[8] << 8 | rx_frame.data.u8[9]) * 10) - 50000; + } + + if (rx_frame.DLC = 20 && rx_frame.data.u8[4] == 0xE5 && rx_frame.data.u8[5] == 0x45) { //SOH Max Min Mean Request + min_soh_state = ((rx_frame.data.u8[8] << 8 | rx_frame.data.u8[9])); + avg_soh_state = ((rx_frame.data.u8[10] << 8 | rx_frame.data.u8[11])); + max_soh_state = ((rx_frame.data.u8[12] << 8 | rx_frame.data.u8[13])); + } + + if (rx_frame.DLC = 10 && rx_frame.data.u8[4] == 0xE5 && + rx_frame.data.u8[5] == 0x62) { //Max allowed charge and discharge current - Signed 16bit + allowable_charge_amps = (int16_t)((rx_frame.data.u8[6] << 8 | rx_frame.data.u8[7])) / 10; + allowable_discharge_amps = (int16_t)((rx_frame.data.u8[8] << 8 | rx_frame.data.u8[9])) / 10; } - if (rx_frame.DLC = 64 && rx_frame.data.u8[0] == 0xF4 && rx_frame.data.u8[1] == 0x22){ //Individual Cell Voltages - 2nd Continue frame - int start_index = 2; //Data starts here - int voltage_index = 60; //Start cell ID - int num_voltages = 31; // number of voltage readings to get - for (int i = start_index; i < (start_index + num_voltages * 2); i += 2) { - uint16_t voltage = (rx_frame.data.u8[i] <<8) | rx_frame.data.u8[i + 1]; - if (voltage < 10000) { //Check reading is plausible - otherwise ignore - datalayer.battery.status.cell_voltages_mV[voltage_index] = voltage; - } - voltage_index++; - } - } - - if (rx_frame.DLC = 64 && rx_frame.data.u8[0] == 0xF4 && rx_frame.data.u8[1] == 0x23){ //Individual Cell Voltages - 3rd Continue frame - int start_index = 2; //Data starts here - int voltage_index = 91; //Start cell ID - int num_voltages; - if (rx_frame.data.u8[12] == 0xFF && rx_frame.data.u8[13]== 0xFF){ //97th cell is blank - assume 96S Battery - num_voltages = 5; // number of voltage readings to get - 6 more to get on 96S - datalayer.battery.info.number_of_cells = 96; - } else { //We have data in 97th cell, assume 108S Battery - num_voltages = 17; // number of voltage readings to get - 17 more to get on 108S - datalayer.battery.info.number_of_cells = 108; - } - - for (int i = start_index; i < (start_index + num_voltages * 2); i += 2) { - uint16_t voltage = (rx_frame.data.u8[i] <<8) | rx_frame.data.u8[i + 1]; - datalayer.battery.status.cell_voltages_mV[voltage_index++] = voltage; - } - } - if (rx_frame.DLC = 7 && rx_frame.data.u8[4] == 0x4D) { //Main Battery Voltage (Pre Contactor) - battery_voltage = (rx_frame.data.u8[5] <<8 | rx_frame.data.u8[6])/10; - } - - if (rx_frame.DLC = 7 && rx_frame.data.u8[4] == 0x4A) { //Main Battery Voltage (After Contactor) - battery_voltage_after_contactor = (rx_frame.data.u8[5] <<8 | rx_frame.data.u8[6])/10; - } - - - if (rx_frame.DLC = 12 && rx_frame.data.u8[4] == 0xE5 && rx_frame.data.u8[5] == 0x61) { //Current amps 32bit signed MSB. dA . negative is discharge - battery_current = ((int32_t)((rx_frame.data.u8[6] << 24) | - (rx_frame.data.u8[7] << 16) | - (rx_frame.data.u8[8] << 8) | - rx_frame.data.u8[9]))*0.1; - } - - if (rx_frame.DLC = 64 && rx_frame.data.u8[4] == 0xE4 && rx_frame.data.u8[5] == 0xCA) { //Balancing Data - balancing_status = ( rx_frame.data.u8[6]); //4 = No symmetry mode active, invalid qualifier - } - if (rx_frame.DLC = 7 && rx_frame.data.u8[4] == 0xE5 && rx_frame.data.u8[5] == 0xCE) { //Min/Avg/Max SOC% - min_soc_state = (rx_frame.data.u8[8] <<8 | rx_frame.data.u8[9]); - avg_soc_state = (rx_frame.data.u8[6] <<8 | rx_frame.data.u8[7]); - max_soc_state = (rx_frame.data.u8[10] <<8 |rx_frame.data.u8[11]); - } - - if (rx_frame.DLC = 12 && rx_frame.data.u8[4] == 0xE5 && rx_frame.data.u8[5] == 0xC7) { //Current and max capacity kWh. Stored in kWh as 0.01 scale with -50 bias - remaining_capacity = ((rx_frame.data.u8[6] <<8 | rx_frame.data.u8[7]) *10) -50000; - max_capacity = ((rx_frame.data.u8[8] <<8 | rx_frame.data.u8[9]) *10) -50000; - } - - if (rx_frame.DLC = 20 && rx_frame.data.u8[4] == 0xE5 && rx_frame.data.u8[5] == 0x45) { //SOH Max Min Mean Request - min_soh_state = ((rx_frame.data.u8[8] <<8 | rx_frame.data.u8[9])); - avg_soh_state = ((rx_frame.data.u8[10] <<8 | rx_frame.data.u8[11])); - max_soh_state = ((rx_frame.data.u8[12] <<8 | rx_frame.data.u8[13])); - } - - if (rx_frame.DLC = 10 && rx_frame.data.u8[4] == 0xE5 && rx_frame.data.u8[5] == 0x62) { //Max allowed charge and discharge current - Signed 16bit - allowable_charge_amps = (int16_t)((rx_frame.data.u8[6] <<8 | rx_frame.data.u8[7]))/10; - allowable_discharge_amps = (int16_t)((rx_frame.data.u8[8] <<8 | rx_frame.data.u8[9]))/10; - } - - if (rx_frame.DLC = 9 && rx_frame.data.u8[4] == 0xE5 && rx_frame.data.u8[5] == 0x4B) { //Max allowed charge and discharge current - Signed 16bit - voltage_qualifier_status = ( rx_frame.data.u8[8]); // Request HV Voltage Qualifier - } - - if (rx_frame.DLC = 48 && rx_frame.data.u8[4] == 0xA8 && rx_frame.data.u8[5] == 0x60) { // Safety Isolation Measurements - iso_safety_positive = (rx_frame.data.u8[34] << 24) | (rx_frame.data.u8[35] << 16) | (rx_frame.data.u8[36] << 8) |rx_frame.data.u8[37]; //Assuming 32bit - iso_safety_negative = (rx_frame.data.u8[38] << 24) | (rx_frame.data.u8[39] << 16) | (rx_frame.data.u8[40] << 8) |rx_frame.data.u8[41]; //Assuming 32bit - iso_safety_parallel = (rx_frame.data.u8[42] << 24) | (rx_frame.data.u8[43] << 16) | (rx_frame.data.u8[44] << 8) |rx_frame.data.u8[45]; //Assuming 32bit - } - - - if (rx_frame.DLC = 48 && rx_frame.data.u8[4] == 0xE4 && rx_frame.data.u8[5] == 0xC0) { // Uptime and Vehicle Time Status - sme_uptime = (rx_frame.data.u8[10] << 24) | (rx_frame.data.u8[11] << 16) | (rx_frame.data.u8[12] << 8) |rx_frame.data.u8[13]; //Assuming 32bit - } - - if (rx_frame.DLC = 8 && rx_frame.data.u8[3] == 0xAC && rx_frame.data.u8[4] == 0x93) { // Pyro Status + if (rx_frame.DLC = 9 && rx_frame.data.u8[4] == 0xE5 && + rx_frame.data.u8[5] == 0x4B) { //Max allowed charge and discharge current - Signed 16bit + voltage_qualifier_status = (rx_frame.data.u8[8]); // Request HV Voltage Qualifier + } + + if (rx_frame.DLC = + 48 && rx_frame.data.u8[4] == 0xA8 && rx_frame.data.u8[5] == 0x60) { // Safety Isolation Measurements + iso_safety_positive = (rx_frame.data.u8[34] << 24) | (rx_frame.data.u8[35] << 16) | + (rx_frame.data.u8[36] << 8) | rx_frame.data.u8[37]; //Assuming 32bit + iso_safety_negative = (rx_frame.data.u8[38] << 24) | (rx_frame.data.u8[39] << 16) | + (rx_frame.data.u8[40] << 8) | rx_frame.data.u8[41]; //Assuming 32bit + iso_safety_parallel = (rx_frame.data.u8[42] << 24) | (rx_frame.data.u8[43] << 16) | + (rx_frame.data.u8[44] << 8) | rx_frame.data.u8[45]; //Assuming 32bit + } + + if (rx_frame.DLC = + 48 && rx_frame.data.u8[4] == 0xE4 && rx_frame.data.u8[5] == 0xC0) { // Uptime and Vehicle Time Status + sme_uptime = (rx_frame.data.u8[10] << 24) | (rx_frame.data.u8[11] << 16) | (rx_frame.data.u8[12] << 8) | + rx_frame.data.u8[13]; //Assuming 32bit + } + + if (rx_frame.DLC = 8 && rx_frame.data.u8[3] == 0xAC && rx_frame.data.u8[4] == 0x93) { // Pyro Status pyro_status_pss1 = (rx_frame.data.u8[5]); - pyro_status_pss4 = (rx_frame.data.u8[6]); - pyro_status_pss6 = (rx_frame.data.u8[7]); - } - - if (rx_frame.DLC = 12 && rx_frame.data.u8[4] == 0xE5 && rx_frame.data.u8[5] == 0x53) { //Min and max cell voltage 10V = Qualifier Invalid - - datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; //This is the most important safety values, if we receive this we reset CAN alive counter. - - if((rx_frame.data.u8[6] <<8 | rx_frame.data.u8[7]) == 10000 && (rx_frame.data.u8[8] <<8 | rx_frame.data.u8[9]) == 10000){ //Qualifier Invalid Mode - Request Reboot - #ifdef DEBUG_VIA_USB - Serial.println("Cell MinMax Qualifier Invalid - Requesting BMS Reset"); - #endif - set_event(EVENT_SOC_UNAVAILABLE, (millis())); - transmit_can(&BMWiX_6F4_REQUEST_HARD_RESET, can_config.battery); - } else{ //Only ingest values if they are not the 10V Error state - min_cell_voltage = (rx_frame.data.u8[6] <<8 | rx_frame.data.u8[7]); - max_cell_voltage = (rx_frame.data.u8[8] <<8 | rx_frame.data.u8[9]); + pyro_status_pss4 = (rx_frame.data.u8[6]); + pyro_status_pss6 = (rx_frame.data.u8[7]); + } + + if (rx_frame.DLC = 12 && rx_frame.data.u8[4] == 0xE5 && + rx_frame.data.u8[5] == 0x53) { //Min and max cell voltage 10V = Qualifier Invalid + + datalayer.battery.status.CAN_battery_still_alive = + CAN_STILL_ALIVE; //This is the most important safety values, if we receive this we reset CAN alive counter. + + if ((rx_frame.data.u8[6] << 8 | rx_frame.data.u8[7]) == 10000 && + (rx_frame.data.u8[8] << 8 | rx_frame.data.u8[9]) == 10000) { //Qualifier Invalid Mode - Request Reboot +#ifdef DEBUG_VIA_USB + Serial.println("Cell MinMax Qualifier Invalid - Requesting BMS Reset"); +#endif + set_event(EVENT_SOC_UNAVAILABLE, (millis())); + transmit_can(&BMWiX_6F4_REQUEST_HARD_RESET, can_config.battery); + } else { //Only ingest values if they are not the 10V Error state + min_cell_voltage = (rx_frame.data.u8[6] << 8 | rx_frame.data.u8[7]); + max_cell_voltage = (rx_frame.data.u8[8] << 8 | rx_frame.data.u8[9]); } - } - - if (rx_frame.DLC = 16 && rx_frame.data.u8[4] == 0xDD && rx_frame.data.u8[5] == 0xC0) { //Battery Temperature - min_battery_temperature = (rx_frame.data.u8[6] <<8 | rx_frame.data.u8[7])/10; - avg_battery_temperature = (rx_frame.data.u8[10] <<8 | rx_frame.data.u8[11])/10; - max_battery_temperature = (rx_frame.data.u8[8] <<8 | rx_frame.data.u8[9])/10; - } - if (rx_frame.DLC = 7 && rx_frame.data.u8[4] == 0xA3) { //Main Contactor Temperature CHECK FINGERPRINT 2 LEVEL - main_contactor_temperature = (rx_frame.data.u8[5] <<8 | rx_frame.data.u8[6]); - } - if (rx_frame.DLC = 7 && rx_frame.data.u8[4] == 0xA7) { //Terminal 30 Voltage (12V SME supply) - terminal30_12v_voltage = (rx_frame.data.u8[5] <<8 | rx_frame.data.u8[6]); - } - if (rx_frame.DLC = 6 && rx_frame.data.u8[0] == 0xF4 && rx_frame.data.u8[1] == 0x04 && rx_frame.data.u8[2] == 0x62 && rx_frame.data.u8[3] == 0xE5 && rx_frame.data.u8[4] == 0x69) { //HVIL Status - hvil_status = ( rx_frame.data.u8[5]); - } - - if (rx_frame.DLC = 12 && rx_frame.data.u8[2] == 0x07 && rx_frame.data.u8[3] == 0x62 && rx_frame.data.u8[4] == 0xE5 && rx_frame.data.u8[5] == 0x4C) { //Pack Voltage Limits - if ((rx_frame.data.u8[6] <<8 | rx_frame.data.u8[7]) < 4700 && (rx_frame.data.u8[8] <<8 | rx_frame.data.u8[9]) > 2600) { //Make sure values are plausible - battery_info_available = true; - max_design_voltage = (rx_frame.data.u8[6] <<8 | rx_frame.data.u8[7]); - min_design_voltage = (rx_frame.data.u8[8] <<8 | rx_frame.data.u8[9]); - } - } - - if (rx_frame.DLC = 16 && rx_frame.data.u8[3] == 0xF1 && rx_frame.data.u8[4] == 0x8C ) { //Battery Serial Number - //Convert hex bytes to ASCII characters and combine them into a string - char numberString[11]; // 10 characters + null terminator - for (int i = 0; i < 10; i++) { - numberString[i] = char(rx_frame.data.u8[i+6]); - } - numberString[10] = '\0'; // Null-terminate the string - // Step 3: Convert the string to an unsigned long integer - battery_serial_number = strtoul(numberString, NULL, 10); - } + } + + if (rx_frame.DLC = 16 && rx_frame.data.u8[4] == 0xDD && rx_frame.data.u8[5] == 0xC0) { //Battery Temperature + min_battery_temperature = (rx_frame.data.u8[6] << 8 | rx_frame.data.u8[7]) / 10; + avg_battery_temperature = (rx_frame.data.u8[10] << 8 | rx_frame.data.u8[11]) / 10; + max_battery_temperature = (rx_frame.data.u8[8] << 8 | rx_frame.data.u8[9]) / 10; + } + if (rx_frame.DLC = 7 && rx_frame.data.u8[4] == 0xA3) { //Main Contactor Temperature CHECK FINGERPRINT 2 LEVEL + main_contactor_temperature = (rx_frame.data.u8[5] << 8 | rx_frame.data.u8[6]); + } + if (rx_frame.DLC = 7 && rx_frame.data.u8[4] == 0xA7) { //Terminal 30 Voltage (12V SME supply) + terminal30_12v_voltage = (rx_frame.data.u8[5] << 8 | rx_frame.data.u8[6]); + } + if (rx_frame.DLC = 6 && rx_frame.data.u8[0] == 0xF4 && rx_frame.data.u8[1] == 0x04 && + rx_frame.data.u8[2] == 0x62 && rx_frame.data.u8[3] == 0xE5 && + rx_frame.data.u8[4] == 0x69) { //HVIL Status + hvil_status = (rx_frame.data.u8[5]); + } + + if (rx_frame.DLC = 12 && rx_frame.data.u8[2] == 0x07 && rx_frame.data.u8[3] == 0x62 && + rx_frame.data.u8[4] == 0xE5 && rx_frame.data.u8[5] == 0x4C) { //Pack Voltage Limits + if ((rx_frame.data.u8[6] << 8 | rx_frame.data.u8[7]) < 4700 && + (rx_frame.data.u8[8] << 8 | rx_frame.data.u8[9]) > 2600) { //Make sure values are plausible + battery_info_available = true; + max_design_voltage = (rx_frame.data.u8[6] << 8 | rx_frame.data.u8[7]); + min_design_voltage = (rx_frame.data.u8[8] << 8 | rx_frame.data.u8[9]); + } + } + + if (rx_frame.DLC = 16 && rx_frame.data.u8[3] == 0xF1 && rx_frame.data.u8[4] == 0x8C) { //Battery Serial Number + //Convert hex bytes to ASCII characters and combine them into a string + char numberString[11]; // 10 characters + null terminator + for (int i = 0; i < 10; i++) { + numberString[i] = char(rx_frame.data.u8[i + 6]); + } + numberString[10] = '\0'; // Null-terminate the string + // Step 3: Convert the string to an unsigned long integer + battery_serial_number = strtoul(numberString, NULL, 10); + } break; default: break; @@ -547,75 +732,72 @@ void send_can_battery() { unsigned long currentMillis = millis(); //if (battery_awake) { //We can always send CAN as the iX BMS will wake up on vehicle comms - //Send 20ms message - if (currentMillis - previousMillis20 >= INTERVAL_20_MS) { - // Check if sending of CAN messages has been delayed too much. - if ((currentMillis - previousMillis20 >= INTERVAL_20_MS_DELAYED) && (currentMillis > BOOTUP_TIME)) { - set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis20)); - } else { - clear_event(EVENT_CAN_OVERRUN); - } - previousMillis20 = currentMillis; + //Send 20ms message + if (currentMillis - previousMillis20 >= INTERVAL_20_MS) { + // Check if sending of CAN messages has been delayed too much. + if ((currentMillis - previousMillis20 >= INTERVAL_20_MS_DELAYED) && (currentMillis > BOOTUP_TIME)) { + set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis20)); + } else { + clear_event(EVENT_CAN_OVERRUN); } - // Send 100ms CAN Message - if (currentMillis - previousMillis100 >= INTERVAL_100_MS) { - previousMillis100 = currentMillis; - - //Loop through and send a different UDS request each cycle - uds_req_id_counter = increment_uds_req_id_counter(uds_req_id_counter); - transmit_can(UDS_REQUESTS100MS[uds_req_id_counter], can_config.battery); - + previousMillis20 = currentMillis; + } + // Send 100ms CAN Message + if (currentMillis - previousMillis100 >= INTERVAL_100_MS) { + previousMillis100 = currentMillis; - //Send SME Keep alive values 100ms - transmit_can(&BMWiX_510, can_config.battery); + //Loop through and send a different UDS request each cycle + uds_req_id_counter = increment_uds_req_id_counter(uds_req_id_counter); + transmit_can(UDS_REQUESTS100MS[uds_req_id_counter], can_config.battery); - } - // Send 200ms CAN Message - if (currentMillis - previousMillis200 >= INTERVAL_200_MS) { - previousMillis200 = currentMillis; - - //Send SME Keep alive values 200ms - BMWiX_0C0.data.u8[0] = increment_0C0_counter(BMWiX_0C0.data.u8[0]); //Keep Alive 1 - transmit_can(&BMWiX_0C0, can_config.battery); - } - // Send 500ms CAN Message - if (currentMillis - previousMillis500 >= INTERVAL_500_MS) { - previousMillis500 = currentMillis; - } - // Send 640ms CAN Message - if (currentMillis - previousMillis640 >= INTERVAL_640_MS) { - previousMillis640 = currentMillis; - } - // Send 1000ms CAN Message - if (currentMillis - previousMillis1000 >= INTERVAL_1_S) { - previousMillis1000 = currentMillis; + //Send SME Keep alive values 100ms + transmit_can(&BMWiX_510, can_config.battery); + } + // Send 200ms CAN Message + if (currentMillis - previousMillis200 >= INTERVAL_200_MS) { + previousMillis200 = currentMillis; - //Send SME Keep alive values 1000ms - //test disable transmit_can(&BMWiX_06D, can_config.battery); - //test disable transmit_can(&BMWiX_2F1, can_config.battery); - //test disable transmit_can(&BMWiX_439, can_config.battery); + //Send SME Keep alive values 200ms + BMWiX_0C0.data.u8[0] = increment_0C0_counter(BMWiX_0C0.data.u8[0]); //Keep Alive 1 + transmit_can(&BMWiX_0C0, can_config.battery); + } + // Send 500ms CAN Message + if (currentMillis - previousMillis500 >= INTERVAL_500_MS) { + previousMillis500 = currentMillis; + } + // Send 640ms CAN Message + if (currentMillis - previousMillis640 >= INTERVAL_640_MS) { + previousMillis640 = currentMillis; + } + // Send 1000ms CAN Message + if (currentMillis - previousMillis1000 >= INTERVAL_1_S) { + previousMillis1000 = currentMillis; - } - // Send 5000ms CAN Message - if (currentMillis - previousMillis5000 >= INTERVAL_5_S) { - previousMillis5000 = currentMillis; - } - // Send 10000ms CAN Message - if (currentMillis - previousMillis10000 >= INTERVAL_10_S) { - previousMillis10000 = currentMillis; - } + //Send SME Keep alive values 1000ms + //test disable transmit_can(&BMWiX_06D, can_config.battery); + //test disable transmit_can(&BMWiX_2F1, can_config.battery); + //test disable transmit_can(&BMWiX_439, can_config.battery); } - //We can always send CAN as the iX BMS will wake up on vehicle comms - // else { - // previousMillis20 = currentMillis; - // previousMillis100 = currentMillis; - // previousMillis200 = currentMillis; - // previousMillis500 = currentMillis; - // previousMillis640 = currentMillis; - // previousMillis1000 = currentMillis; - // previousMillis5000 = currentMillis; - // previousMillis10000 = currentMillis; - // } + // Send 5000ms CAN Message + if (currentMillis - previousMillis5000 >= INTERVAL_5_S) { + previousMillis5000 = currentMillis; + } + // Send 10000ms CAN Message + if (currentMillis - previousMillis10000 >= INTERVAL_10_S) { + previousMillis10000 = currentMillis; + } +} +//We can always send CAN as the iX BMS will wake up on vehicle comms +// else { +// previousMillis20 = currentMillis; +// previousMillis100 = currentMillis; +// previousMillis200 = currentMillis; +// previousMillis500 = currentMillis; +// previousMillis640 = currentMillis; +// previousMillis1000 = currentMillis; +// previousMillis5000 = currentMillis; +// previousMillis10000 = currentMillis; +// } //} //We can always send CAN as the iX BMS will wake up on vehicle comms void setup_battery(void) { // Performs one time setup at startup @@ -631,20 +813,19 @@ void setup_battery(void) { // Performs one time setup at startup datalayer.battery.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV; datalayer.system.status.battery_allows_contactor_closing = true; - //pinMode(WUP_PIN, OUTPUT); // Not needed - can hold WUP pin High with iX BMS - //digitalWrite(WUP_PIN, HIGH); // Wake up the battery // Not needed - can hold WUP pin High with iX BMS + //pinMode(WUP_PIN, OUTPUT); // Not needed - can hold WUP pin High with iX BMS + //digitalWrite(WUP_PIN, HIGH); // Wake up the battery // Not needed - can hold WUP pin High with iX BMS + //Send SME Keep alive values 100ms + transmit_can(&BMWiX_510, can_config.battery); + //Send SME Keep alive values 200ms + BMWiX_0C0.data.u8[0] = increment_0C0_counter(BMWiX_0C0.data.u8[0]); //Keep Alive 1 + transmit_can(&BMWiX_0C0, can_config.battery); - //Send SME Keep alive values 100ms - transmit_can(&BMWiX_510, can_config.battery); - //Send SME Keep alive values 200ms - BMWiX_0C0.data.u8[0] = increment_0C0_counter(BMWiX_0C0.data.u8[0]); //Keep Alive 1 - transmit_can(&BMWiX_0C0, can_config.battery); - - //Send SME Keep alive values 1000ms - //Not needed transmit_can(&BMWiX_06D, can_config.battery); - //Not needed transmit_can(&BMWiX_2F1, can_config.battery); - //Not needed transmit_can(&BMWiX_439, can_config.battery); + //Send SME Keep alive values 1000ms + //Not needed transmit_can(&BMWiX_06D, can_config.battery); + //Not needed transmit_can(&BMWiX_2F1, can_config.battery); + //Not needed transmit_can(&BMWiX_439, can_config.battery); } #endif diff --git a/Software/src/battery/BMW-IX-BATTERY.h b/Software/src/battery/BMW-IX-BATTERY.h index 657811c8..450ed80a 100644 --- a/Software/src/battery/BMW-IX-BATTERY.h +++ b/Software/src/battery/BMW-IX-BATTERY.h @@ -12,9 +12,10 @@ #define MAX_CELL_VOLTAGE_MV 4300 //Battery is put into emergency stop if one cell goes over this value #define MIN_CELL_VOLTAGE_MV 2800 //Battery is put into emergency stop if one cell goes below this value #define MAX_CHARGE_POWER_ALLOWED_W 5000 -#define MAX_CHARGE_POWER_WHEN_TOPBALANCING_W 500 +#define MAX_CHARGE_POWER_WHEN_TOPBALANCING_W 500 #define RAMPDOWN_SOC 9000 // (90.00) SOC% to start ramping down from max charge power towards 0 at 100.00% -#define STALE_PERIOD_CONFIG 180000; //Number of milliseconds before critical values are classed as stale/stuck 180000 = 180 seconds +#define STALE_PERIOD_CONFIG \ + 180000; //Number of milliseconds before critical values are classed as stale/stuck 180000 = 180 seconds void setup_battery(void); void transmit_can(CAN_frame* tx_frame, int interface); From bb77d91791882cd21111f1d1a595dabdbf242716 Mon Sep 17 00:00:00 2001 From: wjcloudy <56305354+wjcloudy@users.noreply.github.com> Date: Fri, 8 Nov 2024 12:14:35 +0000 Subject: [PATCH 129/210] Update formatting Part 2 --- .../webserver/advanced_battery_html.cpp | 36 +++++++++---------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/Software/src/devboard/webserver/advanced_battery_html.cpp b/Software/src/devboard/webserver/advanced_battery_html.cpp index ddad0304..5db01127 100644 --- a/Software/src/devboard/webserver/advanced_battery_html.cpp +++ b/Software/src/devboard/webserver/advanced_battery_html.cpp @@ -17,41 +17,39 @@ String advanced_battery_processor(const String& var) { content += "
"; #ifdef BMW_IX_BATTERY - content += "

Battery Voltage after Contactor: " + String(datalayer_extended.bmwix.battery_voltage_after_contactor) + " dV

"; + content += + "

Battery Voltage after Contactor: " + String(datalayer_extended.bmwix.battery_voltage_after_contactor) + + " dV

"; content += "

Max Design Voltage: " + String(datalayer.battery.info.max_design_voltage_dV) + " dV

"; content += "

Min Design Voltage: " + String(datalayer.battery.info.min_design_voltage_dV) + " dV

"; content += "

Max Cell Design Voltage: " + String(datalayer.battery.info.max_cell_voltage_mV) + " mV

"; content += "

Min Cell Design Voltage: " + String(datalayer.battery.info.min_cell_voltage_mV) + " mV

"; - content += "

Min Cell Voltage Data Age: " + String(datalayer_extended.bmwix.min_cell_voltage_data_age) + " ms

"; - content += "

Max Cell Voltage Data Age: " + String(datalayer_extended.bmwix.max_cell_voltage_data_age) + " ms

"; + content += + "

Min Cell Voltage Data Age: " + String(datalayer_extended.bmwix.min_cell_voltage_data_age) + " ms

"; + content += + "

Max Cell Voltage Data Age: " + String(datalayer_extended.bmwix.max_cell_voltage_data_age) + " ms

"; content += "

Allowed Discharge Power: " + String(datalayer.battery.status.max_discharge_power_W) + " W

"; content += "

Allowed Charge Power: " + String(datalayer.battery.status.max_charge_power_W) + " W

"; - content += "

T30 Terminal Voltage: " + String(datalayer_extended.bmwix.T30_Voltage) + " mV

"; + content += "

T30 Terminal Voltage: " + String(datalayer_extended.bmwix.T30_Voltage) + " mV

"; content += "

Detected Cell Count: " + String(datalayer.battery.info.number_of_cells) + "

"; - static const char* balanceText[5] = {"0 No balancing mode active", - "1 Voltage-Controlled Balancing Mode", - "2 Time-Controlled Balancing Mode with Demand Calculation at End of Charging" , - "3 Time-Controlled Balancing Mode with Demand Calculation at Resting Voltage" , - "4 No balancing mode active, qualifier invalid" - }; + static const char* balanceText[5] = {"0 No balancing mode active", "1 Voltage-Controlled Balancing Mode", + "2 Time-Controlled Balancing Mode with Demand Calculation at End of Charging", + "3 Time-Controlled Balancing Mode with Demand Calculation at Resting Voltage", + "4 No balancing mode active, qualifier invalid"}; content += "

Balancing: " + String((balanceText[datalayer_extended.bmwix.balancing_status])) + "

"; - static const char* hvilText[2] = {"Error (Loop Open)", - "OK (Loop Closed)"}; + static const char* hvilText[2] = {"Error (Loop Open)", "OK (Loop Closed)"}; content += "

HVIL Status: " + String(hvilText[datalayer_extended.bmwix.hvil_status]) + "

"; content += "

BMS Uptime: " + String(datalayer_extended.bmwix.bms_uptime) + " seconds

"; content += "

BMS Allowed Charge Amps: " + String(datalayer_extended.bmwix.allowable_charge_amps) + " A

"; - content += "

BMS Allowed Disharge Amps: " + String(datalayer_extended.bmwix.allowable_discharge_amps) + " A

"; + content += + "

BMS Allowed Disharge Amps: " + String(datalayer_extended.bmwix.allowable_discharge_amps) + " A

"; content += "
"; content += "

HV Isolation (2147483647kOhm = maximum/invalid)

"; content += "

Isolation Positive: " + String(datalayer_extended.bmwix.iso_safety_positive) + " kOhm

"; content += "

Isolation Negative: " + String(datalayer_extended.bmwix.iso_safety_negative) + " kOhm

"; content += "

Isolation Parallel: " + String(datalayer_extended.bmwix.iso_safety_parallel) + " kOhm

"; - static const char* pyroText[5] = {"0 Value Invalid", - "1 Successfully Blown", - "2 Disconnected" , - "3 Not Activated - Pyro Intact" , - "4 Unknown" - }; + static const char* pyroText[5] = {"0 Value Invalid", "1 Successfully Blown", "2 Disconnected", + "3 Not Activated - Pyro Intact", "4 Unknown"}; content += "

Pyro Status PSS1: " + String((pyroText[datalayer_extended.bmwix.pyro_status_pss1])) + "

"; content += "

Pyro Status PSS4: " + String((pyroText[datalayer_extended.bmwix.pyro_status_pss4])) + "

"; content += "

Pyro Status PSS6: " + String((pyroText[datalayer_extended.bmwix.pyro_status_pss6])) + "

"; From 8a4525c15c20344f3b00ef063fe39e7ed8ac04f0 Mon Sep 17 00:00:00 2001 From: wjcloudy <56305354+wjcloudy@users.noreply.github.com> Date: Fri, 8 Nov 2024 12:20:27 +0000 Subject: [PATCH 130/210] Update formatting part 3 --- Software/src/datalayer/datalayer_extended.h | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/Software/src/datalayer/datalayer_extended.h b/Software/src/datalayer/datalayer_extended.h index 3f434764..2009d60b 100644 --- a/Software/src/datalayer/datalayer_extended.h +++ b/Software/src/datalayer/datalayer_extended.h @@ -8,17 +8,17 @@ typedef struct { /** Terminal 30 - 12V SME Supply Voltage */ uint16_t T30_Voltage = 0; /** Status HVIL, 1 HVIL OK, 0 HVIL disconnected*/ - uint8_t hvil_status= 0; + uint8_t hvil_status = 0; /** Min/Max Cell SOH*/ uint16_t min_soh_state = 0; - uint16_t max_soh_state=0; - uint32_t bms_uptime=0; - uint8_t pyro_status_pss1=0; - uint8_t pyro_status_pss4=0; - uint8_t pyro_status_pss6=0; - int32_t iso_safety_positive=0; - int32_t iso_safety_negative=0; - int32_t iso_safety_parallel=0; + uint16_t max_soh_state = 0; + uint32_t bms_uptime = 0; + uint8_t pyro_status_pss1 = 0; + uint8_t pyro_status_pss4 = 0; + uint8_t pyro_status_pss6 = 0; + int32_t iso_safety_positive = 0; + int32_t iso_safety_negative = 0; + int32_t iso_safety_parallel = 0; int32_t allowable_charge_amps = 0; int32_t allowable_discharge_amps = 0; int16_t balancing_status = 0; @@ -28,7 +28,6 @@ typedef struct { } DATALAYER_INFO_BMWIX; - typedef struct { /** uint16_t */ /** SOC% raw battery value. Might not always reach 100% */ From b08d93b6a79b213ed5ca54f60ba482b98bdfbaa9 Mon Sep 17 00:00:00 2001 From: wjcloudy <56305354+wjcloudy@users.noreply.github.com> Date: Fri, 8 Nov 2024 13:44:12 +0000 Subject: [PATCH 131/210] Discharge rate defs and plausible check coverage --- Software/src/battery/BMW-IX-BATTERY.cpp | 7 +++++-- Software/src/battery/BMW-IX-BATTERY.h | 3 ++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Software/src/battery/BMW-IX-BATTERY.cpp b/Software/src/battery/BMW-IX-BATTERY.cpp index 37110fb7..3857f16e 100644 --- a/Software/src/battery/BMW-IX-BATTERY.cpp +++ b/Software/src/battery/BMW-IX-BATTERY.cpp @@ -437,7 +437,7 @@ void update_values_battery() { //This function maps all the values fetched via datalayer.battery.status.soh_pptt = min_soh_state; - datalayer.battery.status.max_discharge_power_W = 3200; //10000; //Aux HV Port has 100A Fuse + datalayer.battery.status.max_discharge_power_W = MAX_DISCHARGE_POWER_ALLOWED_W; //datalayer.battery.status.max_charge_power_W = 3200; //10000; //Aux HV Port has 100A Fuse Moved to Ramping @@ -593,7 +593,10 @@ void receive_can_battery(CAN_frame rx_frame) { for (int i = start_index; i < (start_index + num_voltages * 2); i += 2) { uint16_t voltage = (rx_frame.data.u8[i] << 8) | rx_frame.data.u8[i + 1]; - datalayer.battery.status.cell_voltages_mV[voltage_index++] = voltage; + if (voltage < 10000) { //Check reading is plausible - otherwise ignore + datalayer.battery.status.cell_voltages_mV[voltage_index] = voltage; + } + voltage_index++; } } if (rx_frame.DLC = 7 && rx_frame.data.u8[4] == 0x4D) { //Main Battery Voltage (Pre Contactor) diff --git a/Software/src/battery/BMW-IX-BATTERY.h b/Software/src/battery/BMW-IX-BATTERY.h index 450ed80a..72afe6d5 100644 --- a/Software/src/battery/BMW-IX-BATTERY.h +++ b/Software/src/battery/BMW-IX-BATTERY.h @@ -11,7 +11,8 @@ #define MAX_CELL_DEVIATION_MV 250 #define MAX_CELL_VOLTAGE_MV 4300 //Battery is put into emergency stop if one cell goes over this value #define MIN_CELL_VOLTAGE_MV 2800 //Battery is put into emergency stop if one cell goes below this value -#define MAX_CHARGE_POWER_ALLOWED_W 5000 +#define MAX_DISCHARGE_POWER_ALLOWED_W 10000 +#define MAX_CHARGE_POWER_ALLOWED_W 10000 #define MAX_CHARGE_POWER_WHEN_TOPBALANCING_W 500 #define RAMPDOWN_SOC 9000 // (90.00) SOC% to start ramping down from max charge power towards 0 at 100.00% #define STALE_PERIOD_CONFIG \ From 7efe4323fda6233aa6bd8b488568e4d7f5493a1f Mon Sep 17 00:00:00 2001 From: wjcloudy <56305354+wjcloudy@users.noreply.github.com> Date: Fri, 8 Nov 2024 14:14:20 +0000 Subject: [PATCH 132/210] Various Review cleanups -unchecked debug logging removed -datalayer updates grouped -cleaned up message loops -cleaned up setup function --- Software/src/battery/BMW-IX-BATTERY.cpp | 54 +++++-------------------- 1 file changed, 9 insertions(+), 45 deletions(-) diff --git a/Software/src/battery/BMW-IX-BATTERY.cpp b/Software/src/battery/BMW-IX-BATTERY.cpp index 3857f16e..33556d6c 100644 --- a/Software/src/battery/BMW-IX-BATTERY.cpp +++ b/Software/src/battery/BMW-IX-BATTERY.cpp @@ -461,34 +461,30 @@ void update_values_battery() { //This function maps all the values fetched via datalayer.battery.status.temperature_max_dC = max_battery_temperature; - if (isStale(min_cell_voltage, datalayer.battery.status.cell_min_voltage_mV, - min_cell_voltage_lastchanged)) { //TODO prevent flipflop after error - Serial.println("min_cell_voltage has gone stale."); + if (isStale(min_cell_voltage, datalayer.battery.status.cell_min_voltage_mV, min_cell_voltage_lastchanged)) { datalayer.battery.status.cell_min_voltage_mV = 9999; //Stale values force stop set_event(EVENT_CAN_RX_FAILURE, 0); } else { datalayer.battery.status.cell_min_voltage_mV = min_cell_voltage; //Value is alive } - if (isStale(max_cell_voltage, datalayer.battery.status.cell_max_voltage_mV, - max_cell_voltage_lastchanged)) { //TODO prevent flipflop after error - Serial.println("max_cell_voltage has gone stale."); + if (isStale(max_cell_voltage, datalayer.battery.status.cell_max_voltage_mV, max_cell_voltage_lastchanged)) { datalayer.battery.status.cell_max_voltage_mV = 9999; //Stale values force stop set_event(EVENT_CAN_RX_FAILURE, 0); } else { datalayer.battery.status.cell_max_voltage_mV = max_cell_voltage; //Value is alive } - datalayer_extended.bmwix.min_cell_voltage_data_age = (millis() - min_cell_voltage_lastchanged); - - datalayer_extended.bmwix.max_cell_voltage_data_age = (millis() - max_cell_voltage_lastchanged); - datalayer.battery.info.max_design_voltage_dV = max_design_voltage; datalayer.battery.info.min_design_voltage_dV = min_design_voltage; datalayer.battery.info.number_of_cells = 108; //init with 108S before autodetection + datalayer_extended.bmwix.min_cell_voltage_data_age = (millis() - min_cell_voltage_lastchanged); + + datalayer_extended.bmwix.max_cell_voltage_data_age = (millis() - max_cell_voltage_lastchanged); + datalayer_extended.bmwix.T30_Voltage = terminal30_12v_voltage; datalayer_extended.bmwix.hvil_status = hvil_status; @@ -735,16 +731,6 @@ void send_can_battery() { unsigned long currentMillis = millis(); //if (battery_awake) { //We can always send CAN as the iX BMS will wake up on vehicle comms - //Send 20ms message - if (currentMillis - previousMillis20 >= INTERVAL_20_MS) { - // Check if sending of CAN messages has been delayed too much. - if ((currentMillis - previousMillis20 >= INTERVAL_20_MS_DELAYED) && (currentMillis > BOOTUP_TIME)) { - set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis20)); - } else { - clear_event(EVENT_CAN_OVERRUN); - } - previousMillis20 = currentMillis; - } // Send 100ms CAN Message if (currentMillis - previousMillis100 >= INTERVAL_100_MS) { previousMillis100 = currentMillis; @@ -764,22 +750,14 @@ void send_can_battery() { BMWiX_0C0.data.u8[0] = increment_0C0_counter(BMWiX_0C0.data.u8[0]); //Keep Alive 1 transmit_can(&BMWiX_0C0, can_config.battery); } - // Send 500ms CAN Message - if (currentMillis - previousMillis500 >= INTERVAL_500_MS) { - previousMillis500 = currentMillis; - } - // Send 640ms CAN Message - if (currentMillis - previousMillis640 >= INTERVAL_640_MS) { - previousMillis640 = currentMillis; - } // Send 1000ms CAN Message if (currentMillis - previousMillis1000 >= INTERVAL_1_S) { previousMillis1000 = currentMillis; //Send SME Keep alive values 1000ms - //test disable transmit_can(&BMWiX_06D, can_config.battery); - //test disable transmit_can(&BMWiX_2F1, can_config.battery); - //test disable transmit_can(&BMWiX_439, can_config.battery); + //Don't believe this is needed: transmit_can(&BMWiX_06D, can_config.battery); + //Don't believe this is needed: transmit_can(&BMWiX_2F1, can_config.battery); + //Don't believe this is needed: transmit_can(&BMWiX_439, can_config.battery); } // Send 5000ms CAN Message if (currentMillis - previousMillis5000 >= INTERVAL_5_S) { @@ -815,20 +793,6 @@ void setup_battery(void) { // Performs one time setup at startup datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV; datalayer.battery.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV; datalayer.system.status.battery_allows_contactor_closing = true; - - //pinMode(WUP_PIN, OUTPUT); // Not needed - can hold WUP pin High with iX BMS - //digitalWrite(WUP_PIN, HIGH); // Wake up the battery // Not needed - can hold WUP pin High with iX BMS - - //Send SME Keep alive values 100ms - transmit_can(&BMWiX_510, can_config.battery); - //Send SME Keep alive values 200ms - BMWiX_0C0.data.u8[0] = increment_0C0_counter(BMWiX_0C0.data.u8[0]); //Keep Alive 1 - transmit_can(&BMWiX_0C0, can_config.battery); - - //Send SME Keep alive values 1000ms - //Not needed transmit_can(&BMWiX_06D, can_config.battery); - //Not needed transmit_can(&BMWiX_2F1, can_config.battery); - //Not needed transmit_can(&BMWiX_439, can_config.battery); } #endif From 8b14078b5222252a027ff7f8ef52356c05d0cd78 Mon Sep 17 00:00:00 2001 From: wjcloudy <56305354+wjcloudy@users.noreply.github.com> Date: Fri, 8 Nov 2024 15:25:49 +0000 Subject: [PATCH 133/210] Cleaner update of detected cell count --- Software/src/battery/BMW-IX-BATTERY.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Software/src/battery/BMW-IX-BATTERY.cpp b/Software/src/battery/BMW-IX-BATTERY.cpp index 33556d6c..a04cb093 100644 --- a/Software/src/battery/BMW-IX-BATTERY.cpp +++ b/Software/src/battery/BMW-IX-BATTERY.cpp @@ -373,6 +373,7 @@ static uint8_t pyro_status_pss1 = 0; //Using AC 93 static uint8_t pyro_status_pss4 = 0; //Using AC 93 static uint8_t pyro_status_pss6 = 0; //Using AC 93 static uint8_t uds_req_id_counter = 0; +static uint8_t detected_number_of_cells = 108; const unsigned long STALE_PERIOD = STALE_PERIOD_CONFIG; // Time in milliseconds to check for staleness (e.g., 5000 ms = 5 seconds) @@ -479,7 +480,7 @@ void update_values_battery() { //This function maps all the values fetched via datalayer.battery.info.min_design_voltage_dV = min_design_voltage; - datalayer.battery.info.number_of_cells = 108; //init with 108S before autodetection + datalayer.battery.info.number_of_cells = detected_number_of_cells; datalayer_extended.bmwix.min_cell_voltage_data_age = (millis() - min_cell_voltage_lastchanged); @@ -581,10 +582,10 @@ void receive_can_battery(CAN_frame rx_frame) { int num_voltages; if (rx_frame.data.u8[12] == 0xFF && rx_frame.data.u8[13] == 0xFF) { //97th cell is blank - assume 96S Battery num_voltages = 5; // number of voltage readings to get - 6 more to get on 96S - datalayer.battery.info.number_of_cells = 96; + detected_number_of_cells = 96; } else { //We have data in 97th cell, assume 108S Battery num_voltages = 17; // number of voltage readings to get - 17 more to get on 108S - datalayer.battery.info.number_of_cells = 108; + detected_number_of_cells = 108; } for (int i = start_index; i < (start_index + num_voltages * 2); i += 2) { From c2e92aee029d9081bb9935260b5fbcf1730a5aa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Fri, 8 Nov 2024 21:25:32 +0200 Subject: [PATCH 134/210] Clean up code --- Software/src/battery/NISSAN-LEAF-BATTERY.cpp | 94 +++----------------- 1 file changed, 13 insertions(+), 81 deletions(-) diff --git a/Software/src/battery/NISSAN-LEAF-BATTERY.cpp b/Software/src/battery/NISSAN-LEAF-BATTERY.cpp index 10b34363..d5e78809 100644 --- a/Software/src/battery/NISSAN-LEAF-BATTERY.cpp +++ b/Software/src/battery/NISSAN-LEAF-BATTERY.cpp @@ -1298,108 +1298,54 @@ void clearSOH(void) { stateMachineClearSOH = 1; break; case 1: // Set CAN_PROCESS_FLAG to 0xC0 - LEAF_CLEAR_SOH.data.u8[0] = 0x02; - LEAF_CLEAR_SOH.data.u8[1] = 0x10; - LEAF_CLEAR_SOH.data.u8[2] = 0xC0; - LEAF_CLEAR_SOH.data.u8[3] = 0x00; - LEAF_CLEAR_SOH.data.u8[4] = 0x00; - LEAF_CLEAR_SOH.data.u8[5] = 0x00; - LEAF_CLEAR_SOH.data.u8[6] = 0x00; - LEAF_CLEAR_SOH.data.u8[7] = 0x00; + LEAF_CLEAR_SOH.data = {0x02, 0x10, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00}; transmit_can(&LEAF_CLEAR_SOH, can_config.battery); // BMS should reply 02 50 C0 FF FF FF FF FF stateMachineClearSOH = 2; break; case 2: // Set something ? - LEAF_CLEAR_SOH.data.u8[0] = 0x02; - LEAF_CLEAR_SOH.data.u8[1] = 0x3E; - LEAF_CLEAR_SOH.data.u8[2] = 0x01; - LEAF_CLEAR_SOH.data.u8[3] = 0x00; - LEAF_CLEAR_SOH.data.u8[4] = 0x00; - LEAF_CLEAR_SOH.data.u8[5] = 0x00; - LEAF_CLEAR_SOH.data.u8[6] = 0x00; - LEAF_CLEAR_SOH.data.u8[7] = 0x00; + LEAF_CLEAR_SOH.data = {0x02, 0x3E, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00}; transmit_can(&LEAF_CLEAR_SOH, can_config.battery); // BMS should reply 7E FF FF FF FF FF FF stateMachineClearSOH = 3; break; case 3: // Request challenge to solve - LEAF_CLEAR_SOH.data.u8[0] = 0x02; - LEAF_CLEAR_SOH.data.u8[1] = 0x27; - LEAF_CLEAR_SOH.data.u8[2] = 0x65; - LEAF_CLEAR_SOH.data.u8[3] = 0x00; - LEAF_CLEAR_SOH.data.u8[4] = 0x00; - LEAF_CLEAR_SOH.data.u8[5] = 0x00; - LEAF_CLEAR_SOH.data.u8[6] = 0x00; - LEAF_CLEAR_SOH.data.u8[7] = 0x00; + LEAF_CLEAR_SOH.data = {0x02, 0x27, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00}; transmit_can(&LEAF_CLEAR_SOH, can_config.battery); // BMS should reply with (challenge) 06 67 65 (02 DD 86 43) FF stateMachineClearSOH = 4; break; case 4: // Send back decoded challenge data decodeChallengeData(incomingChallenge, solvedChallenge); - LEAF_CLEAR_SOH.data.u8[0] = 0x10; - LEAF_CLEAR_SOH.data.u8[1] = 0x0A; - LEAF_CLEAR_SOH.data.u8[2] = 0x27; - LEAF_CLEAR_SOH.data.u8[3] = 0x66; - LEAF_CLEAR_SOH.data.u8[4] = solvedChallenge[0]; - LEAF_CLEAR_SOH.data.u8[5] = solvedChallenge[1]; - LEAF_CLEAR_SOH.data.u8[6] = solvedChallenge[2]; - LEAF_CLEAR_SOH.data.u8[7] = solvedChallenge[3]; + LEAF_CLEAR_SOH.data = { + 0x10, 0x0A, 0x27, 0x66, solvedChallenge[0], solvedChallenge[1], solvedChallenge[2], solvedChallenge[3]}; transmit_can(&LEAF_CLEAR_SOH, can_config.battery); // BMS should reply 7BB 8 30 01 00 FF FF FF FF FF // Proceed with more data (PID ACK) stateMachineClearSOH = 5; break; case 5: // Reply with even more decoded challenge data - LEAF_CLEAR_SOH.data.u8[0] = 0x21; - LEAF_CLEAR_SOH.data.u8[1] = solvedChallenge[4]; - LEAF_CLEAR_SOH.data.u8[2] = solvedChallenge[5]; - LEAF_CLEAR_SOH.data.u8[3] = solvedChallenge[6]; - LEAF_CLEAR_SOH.data.u8[4] = solvedChallenge[7]; - LEAF_CLEAR_SOH.data.u8[5] = 0x00; - LEAF_CLEAR_SOH.data.u8[6] = 0x00; - LEAF_CLEAR_SOH.data.u8[7] = 0x00; + LEAF_CLEAR_SOH.data = { + 0x21, solvedChallenge[4], solvedChallenge[5], solvedChallenge[6], solvedChallenge[7], 0x00, 0x00, 0x00}; transmit_can(&LEAF_CLEAR_SOH, can_config.battery); // BMS should reply 02 67 66 FF FF FF FF FF // Thank you for the data stateMachineClearSOH = 6; break; case 6: // Check if solved data was OK - LEAF_CLEAR_SOH.data.u8[0] = 0x03; - LEAF_CLEAR_SOH.data.u8[1] = 0x31; - LEAF_CLEAR_SOH.data.u8[2] = 0x03; - LEAF_CLEAR_SOH.data.u8[3] = 0x00; - LEAF_CLEAR_SOH.data.u8[4] = 0x00; - LEAF_CLEAR_SOH.data.u8[5] = 0x00; - LEAF_CLEAR_SOH.data.u8[6] = 0x00; - LEAF_CLEAR_SOH.data.u8[7] = 0x00; + LEAF_CLEAR_SOH.data = {0x03, 0x31, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00}; transmit_can(&LEAF_CLEAR_SOH, can_config.battery); //7BB 8 03 71 03 01 FF FF FF FF // If all is well, BMS replies with 03 71 03 01. //Incase you sent wrong challenge, you get 03 7f 31 12 stateMachineClearSOH = 7; break; case 7: // Reset SOH% request - LEAF_CLEAR_SOH.data.u8[0] = 0x03; - LEAF_CLEAR_SOH.data.u8[1] = 0x31; - LEAF_CLEAR_SOH.data.u8[2] = 0x03; - LEAF_CLEAR_SOH.data.u8[3] = 0x01; - LEAF_CLEAR_SOH.data.u8[4] = 0x00; - LEAF_CLEAR_SOH.data.u8[5] = 0x00; - LEAF_CLEAR_SOH.data.u8[6] = 0x00; - LEAF_CLEAR_SOH.data.u8[7] = 0x00; + LEAF_CLEAR_SOH.data = {0x03, 0x31, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00}; transmit_can(&LEAF_CLEAR_SOH, can_config.battery); //7BB 8 03 71 03 02 FF FF FF FF // 03 71 03 02 means that BMS accepted command. //7BB 03 7f 31 12 means your challenge was wrong, so command ignored stateMachineClearSOH = 8; break; case 8: // Please proceed with resetting SOH - LEAF_CLEAR_SOH.data.u8[0] = 0x02; - LEAF_CLEAR_SOH.data.u8[1] = 0x10; - LEAF_CLEAR_SOH.data.u8[2] = 0x81; - LEAF_CLEAR_SOH.data.u8[3] = 0x00; - LEAF_CLEAR_SOH.data.u8[4] = 0x00; - LEAF_CLEAR_SOH.data.u8[5] = 0x00; - LEAF_CLEAR_SOH.data.u8[6] = 0x00; - LEAF_CLEAR_SOH.data.u8[7] = 0x00; + LEAF_CLEAR_SOH.data = {0x02, 0x10, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00}; transmit_can(&LEAF_CLEAR_SOH, can_config.battery); // 7BB 8 02 50 81 FF FF FF FF FF // SOH reset OK stateMachineClearSOH = 255; @@ -1423,17 +1369,7 @@ void clearSOH(void) { uint32_t CyclicXorHash16Bit(uint32_t param_1, uint32_t param_2) { bool bVar1; - uint32_t uVar2; - uint32_t uVar3; - uint32_t uVar4; - uint32_t uVar5; - uint32_t uVar6; - uint32_t uVar7; - uint32_t uVar8; - uint32_t uVar9; - uint32_t uVar10; - uint32_t uVar11; - uint32_t iVar12; + uint32_t uVar2, uVar3, uVar4, uVar5, uVar6, uVar7, uVar8, uVar9, uVar10, uVar11, iVar12; param_1 = param_1 & 0xffff; param_2 = param_2 & 0xffff; @@ -1518,10 +1454,7 @@ uint32_t MaskedBitwiseRotateMultiply(uint32_t param_1, uint32_t param_2) { } uint32_t CryptAlgo(uint32_t param_1, uint32_t param_2, uint32_t param_3) { - uint32_t uVar1; - uint32_t uVar2; - uint32_t iVar3; - uint32_t iVar4; + uint32_t uVar1, uVar2, iVar3, iVar4; uVar1 = MaskedBitwiseRotateMultiply(param_2, param_3); uVar2 = ShortMaskedSumAndProduct(param_2, param_3); @@ -1533,8 +1466,7 @@ uint32_t CryptAlgo(uint32_t param_1, uint32_t param_2, uint32_t param_3) { } void decodeChallengeData(uint32_t incomingChallenge, unsigned char* solvedChallenge) { - uint32_t uVar1; - uint32_t uVar2; + uint32_t uVar1, uVar2; uVar1 = CryptAlgo(0x609, 0xDD2, incomingChallenge >> 0x10); uVar2 = CryptAlgo(incomingChallenge & 0xffff, incomingChallenge >> 0x10, 0x609); From 710a7339d881c28498fe0adc8ad09336c78ea6cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Fri, 8 Nov 2024 22:56:53 +0200 Subject: [PATCH 135/210] Add charge/discharge current to datalayer --- Software/Software.ino | 22 +++++++- Software/src/datalayer/datalayer.h | 8 ++- Software/src/devboard/safety/safety.cpp | 8 +++ Software/src/devboard/webserver/webserver.cpp | 8 +++ Software/src/inverter/AFORE-CAN.cpp | 33 ++--------- Software/src/inverter/BYD-CAN.cpp | 36 ++---------- Software/src/inverter/BYD-SMA.cpp | 35 ++---------- Software/src/inverter/FOXESS-CAN.cpp | 56 ++----------------- Software/src/inverter/PYLON-CAN.cpp | 54 ++++++------------ Software/src/inverter/PYLON-LV-CAN.cpp | 28 ++++------ Software/src/inverter/SMA-CAN.cpp | 33 ++--------- Software/src/inverter/SMA-TRIPOWER-CAN.cpp | 33 ++--------- Software/src/inverter/SOFAR-CAN.cpp | 8 +-- Software/src/inverter/SOLAX-CAN.cpp | 54 ++---------------- 14 files changed, 104 insertions(+), 312 deletions(-) diff --git a/Software/Software.ino b/Software/Software.ino index 588f28cf..adc771e1 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -293,7 +293,7 @@ void core_loop(void* task_time_us) { #ifdef DOUBLE_BATTERY update_values_battery2(); #endif - update_scaled_values(); // Check if real or calculated SOC% value should be sent + update_calculated_values(); #ifndef SERIAL_LINK_RECEIVER update_machineryprotection(); // Check safeties (Not on serial link reciever board) #endif @@ -837,7 +837,23 @@ void handle_contactors() { #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.info.max_charge_amp_dA) { + datalayer.battery.status.max_charge_current_dA = datalayer.battery.info.max_charge_amp_dA; + } + if (datalayer.battery.status.max_discharge_current_dA > datalayer.battery.info.max_discharge_amp_dA) { + datalayer.battery.status.max_discharge_current_dA = datalayer.battery.info.max_discharge_amp_dA; + } + if (datalayer.battery.settings.soc_scaling_active) { /** SOC Scaling * @@ -896,7 +912,7 @@ void update_scaled_values() { } #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_remaining_capacity_Wh = datalayer.battery.status.remaining_capacity_Wh; #ifdef DOUBLE_BATTERY diff --git a/Software/src/datalayer/datalayer.h b/Software/src/datalayer/datalayer.h index d0b6e840..b4967f4b 100644 --- a/Software/src/datalayer/datalayer.h +++ b/Software/src/datalayer/datalayer.h @@ -49,10 +49,14 @@ typedef struct { */ 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; - /** Maximum allowed battery charge power in Watts */ + /** Maximum allowed battery charge power in Watts. Set by battery */ 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 */ /** Maximum temperature currently measured in the pack, in d°C. 150 = 15.0 °C */ diff --git a/Software/src/devboard/safety/safety.cpp b/Software/src/devboard/safety/safety.cpp index f2023f41..ee72af88 100644 --- a/Software/src/devboard/safety/safety.cpp +++ b/Software/src/devboard/safety/safety.cpp @@ -229,6 +229,14 @@ void update_machineryprotection() { } #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 diff --git a/Software/src/devboard/webserver/webserver.cpp b/Software/src/devboard/webserver/webserver.cpp index d42bac4b..4a1554f3 100644 --- a/Software/src/devboard/webserver/webserver.cpp +++ b/Software/src/devboard/webserver/webserver.cpp @@ -652,6 +652,10 @@ String processor(const String& var) { float powerFloat = static_cast(datalayer.battery.status.active_power_W); // Convert to float float tempMaxFloat = static_cast(datalayer.battery.status.temperature_max_dC) / 10.0; // Convert to float float tempMinFloat = static_cast(datalayer.battery.status.temperature_min_dC) / 10.0; // Convert to float + float maxCurrentChargeFloat = + static_cast(datalayer.battery.status.max_charge_current_dA) / 10.0; // Convert to float + float maxCurrentDischargeFloat = + static_cast(datalayer.battery.status.max_discharge_current_dA) / 10.0; // Convert to float uint16_t cell_delta_mv = datalayer.battery.status.cell_max_voltage_mV - datalayer.battery.status.cell_min_voltage_mV; @@ -669,9 +673,13 @@ String processor(const String& var) { if (emulator_pause_status == NORMAL) { 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 += "

Max discharge current: " + String(maxCurrentDischargeFloat, 1) + " A

"; + content += "

Max charge current: " + String(maxCurrentChargeFloat, 1) + " A

"; } else { 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 += "

Max discharge current: " + String(maxCurrentDischargeFloat, 1) + " A

"; + content += "

Max charge current: " + String(maxCurrentChargeFloat, 1) + " A

"; } content += "

Cell max: " + String(datalayer.battery.status.cell_max_voltage_mV) + " mV

"; diff --git a/Software/src/inverter/AFORE-CAN.cpp b/Software/src/inverter/AFORE-CAN.cpp index 6ac88873..74435116 100644 --- a/Software/src/inverter/AFORE-CAN.cpp +++ b/Software/src/inverter/AFORE-CAN.cpp @@ -72,31 +72,10 @@ CAN_frame AFORE_35A = {.FD = false, .DLC = 8, .ID = 0x35A, .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 //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*/ AFORE_350.data.u8[0] = (datalayer.battery.status.voltage_dV & 0x00FF); 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[3] = SOCMIN; 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)) { 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)) { 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); /*0x352 - Protection parameters*/ - AFORE_352.data.u8[0] = (max_charge_current_dA & 0x00FF); - AFORE_352.data.u8[1] = (max_charge_current_dA >> 8); - AFORE_352.data.u8[2] = (max_discharge_current_dA & 0x00FF); - AFORE_352.data.u8[3] = (max_discharge_current_dA >> 8); + AFORE_352.data.u8[0] = (datalayer.battery.status.max_charge_current_dA & 0x00FF); + AFORE_352.data.u8[1] = (datalayer.battery.status.max_charge_current_dA >> 8); + AFORE_352.data.u8[2] = (datalayer.battery.status.max_discharge_current_dA & 0x00FF); + 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[5] = (datalayer.battery.info.max_design_voltage_dV >> 8); AFORE_352.data.u8[6] = (datalayer.battery.info.min_design_voltage_dV & 0x00FF); diff --git a/Software/src/inverter/BYD-CAN.cpp b/Software/src/inverter/BYD-CAN.cpp index 82527469..757547ec 100644 --- a/Software/src/inverter/BYD-CAN.cpp +++ b/Software/src/inverter/BYD-CAN.cpp @@ -79,8 +79,6 @@ CAN_frame BYD_210 = {.FD = false, .ID = 0x210, .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 uint16_t inverter_voltage = 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 - /* 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 */ temperature_average = ((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[3] = (datalayer.battery.info.min_design_voltage_dV & 0x00FF); //Maximum discharge power allowed (Unit: A+1) - BYD_110.data.u8[4] = (discharge_current >> 8); - BYD_110.data.u8[5] = (discharge_current & 0x00FF); + BYD_110.data.u8[4] = (datalayer.battery.status.max_discharge_current_dA >> 8); + BYD_110.data.u8[5] = (datalayer.battery.status.max_discharge_current_dA & 0x00FF); //Maximum charge power allowed (Unit: A+1) - BYD_110.data.u8[6] = (charge_current >> 8); - BYD_110.data.u8[7] = (charge_current & 0x00FF); + BYD_110.data.u8[6] = (datalayer.battery.status.max_charge_current_dA >> 8); + BYD_110.data.u8[7] = (datalayer.battery.status.max_charge_current_dA & 0x00FF); //SOC (100.00%) BYD_150.data.u8[0] = (datalayer.battery.status.reported_soc >> 8); diff --git a/Software/src/inverter/BYD-SMA.cpp b/Software/src/inverter/BYD-SMA.cpp index 162cfb3a..230b61e7 100644 --- a/Software/src/inverter/BYD-SMA.cpp +++ b/Software/src/inverter/BYD-SMA.cpp @@ -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 //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 = ((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[1] = (datalayer.battery.info.max_design_voltage_dV & 0x00FF); //Minvoltage (eg 300.0V = 3000 , 16bits long) - SMA_358.data.u8[2] = (datalayer.battery.info.min_design_voltage_dV >> - 8); //Minvoltage behaves strange on SMA, cuts out at 56% of the set value? + SMA_358.data.u8[2] = (datalayer.battery.info.min_design_voltage_dV >> 8); SMA_358.data.u8[3] = (datalayer.battery.info.min_design_voltage_dV & 0x00FF); //Discharge limited current, 500 = 50A, (0.1, A) - SMA_358.data.u8[4] = (discharge_current >> 8); - SMA_358.data.u8[5] = (discharge_current & 0x00FF); + SMA_358.data.u8[4] = (datalayer.battery.status.max_discharge_current_dA >> 8); + SMA_358.data.u8[5] = (datalayer.battery.status.max_discharge_current_dA & 0x00FF); //Charge limited current, 125 =12.5A (0.1, A) - SMA_358.data.u8[6] = (charge_current >> 8); - SMA_358.data.u8[7] = (charge_current & 0x00FF); + SMA_358.data.u8[6] = (datalayer.battery.status.max_charge_current_dA >> 8); + SMA_358.data.u8[7] = (datalayer.battery.status.max_charge_current_dA & 0x00FF); //SOC (100.00%) SMA_3D8.data.u8[0] = (datalayer.battery.status.reported_soc >> 8); diff --git a/Software/src/inverter/FOXESS-CAN.cpp b/Software/src/inverter/FOXESS-CAN.cpp index 8b567946..3909bbbe 100644 --- a/Software/src/inverter/FOXESS-CAN.cpp +++ b/Software/src/inverter/FOXESS-CAN.cpp @@ -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 /* 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 uint16_t voltage_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 = ((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) { 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[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[4] = (uint8_t)(max_charge_rate_amp * 10); - FOXESS_1872.data.u8[5] = ((max_charge_rate_amp * 10) >> 8); - FOXESS_1872.data.u8[6] = (uint8_t)(max_discharge_rate_amp * 10); - FOXESS_1872.data.u8[7] = ((max_discharge_rate_amp * 10) >> 8); + FOXESS_1872.data.u8[4] = (uint8_t)datalayer.battery.status.max_charge_current_dA; + FOXESS_1872.data.u8[5] = (datalayer.battery.status.max_charge_current_dA >> 8); + FOXESS_1872.data.u8[6] = (uint8_t)datalayer.battery.status.max_discharge_current_dA; + FOXESS_1872.data.u8[7] = (datalayer.battery.status.max_discharge_current_dA >> 8); //BMS_PackData 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 - // 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 - 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)) { FOXESS_1876.data.u8[0] = 0x01; } else { //continue using battery diff --git a/Software/src/inverter/PYLON-CAN.cpp b/Software/src/inverter/PYLON-CAN.cpp index 9706f759..2b8e036c 100644 --- a/Software/src/inverter/PYLON-CAN.cpp +++ b/Software/src/inverter/PYLON-CAN.cpp @@ -134,32 +134,10 @@ CAN_frame PYLON_4291 = {.FD = false, .ID = 0x4291, .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 //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 = (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 PYLON_4280.data.u8[0] = 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 //Max ChargeCurrent - PYLON_4220.data.u8[4] = ((max_charge_current + 30000) & 0x00FF); - PYLON_4220.data.u8[5] = ((max_charge_current + 30000) >> 8); - PYLON_4221.data.u8[4] = ((max_charge_current + 30000) & 0x00FF); - PYLON_4221.data.u8[5] = ((max_charge_current + 30000) >> 8); + PYLON_4220.data.u8[4] = ((datalayer.battery.status.max_charge_current_dA + 30000) & 0x00FF); + PYLON_4220.data.u8[5] = ((datalayer.battery.status.max_charge_current_dA + 30000) >> 8); + PYLON_4221.data.u8[4] = ((datalayer.battery.status.max_charge_current_dA + 30000) & 0x00FF); + PYLON_4221.data.u8[5] = ((datalayer.battery.status.max_charge_current_dA + 30000) >> 8); //Max DischargeCurrent - PYLON_4220.data.u8[6] = ((30000 - max_discharge_current) & 0x00FF); - PYLON_4220.data.u8[7] = ((30000 - max_discharge_current) >> 8); - PYLON_4221.data.u8[6] = ((30000 - max_discharge_current) & 0x00FF); - PYLON_4221.data.u8[7] = ((30000 - max_discharge_current) >> 8); + PYLON_4220.data.u8[6] = ((30000 - datalayer.battery.status.max_discharge_current_dA) & 0x00FF); + PYLON_4220.data.u8[7] = ((30000 - datalayer.battery.status.max_discharge_current_dA) >> 8); + PYLON_4221.data.u8[6] = ((30000 - datalayer.battery.status.max_discharge_current_dA) & 0x00FF); + PYLON_4221.data.u8[7] = ((30000 - datalayer.battery.status.max_discharge_current_dA) >> 8); #else //Max ChargeCurrent - PYLON_4220.data.u8[4] = (max_charge_current & 0x00FF); - PYLON_4220.data.u8[5] = (max_charge_current >> 8); - PYLON_4221.data.u8[4] = (max_charge_current & 0x00FF); - PYLON_4221.data.u8[5] = (max_charge_current >> 8); + PYLON_4220.data.u8[4] = (datalayer.battery.status.max_charge_current_dA & 0x00FF); + PYLON_4220.data.u8[5] = (datalayer.battery.status.max_charge_current_dA >> 8); + PYLON_4221.data.u8[4] = (datalayer.battery.status.max_charge_current_dA & 0x00FF); + PYLON_4221.data.u8[5] = (datalayer.battery.status.max_charge_current_dA >> 8); //Max DishargeCurrent - PYLON_4220.data.u8[6] = (max_discharge_current & 0x00FF); - PYLON_4220.data.u8[7] = (max_discharge_current >> 8); - PYLON_4221.data.u8[6] = (max_discharge_current & 0x00FF); - PYLON_4221.data.u8[7] = (max_discharge_current >> 8); + PYLON_4220.data.u8[6] = (datalayer.battery.status.max_discharge_current_dA & 0x00FF); + PYLON_4220.data.u8[7] = (datalayer.battery.status.max_discharge_current_dA >> 8); + PYLON_4221.data.u8[6] = (datalayer.battery.status.max_discharge_current_dA & 0x00FF); + PYLON_4221.data.u8[7] = (datalayer.battery.status.max_discharge_current_dA >> 8); #endif //Max cell voltage diff --git a/Software/src/inverter/PYLON-LV-CAN.cpp b/Software/src/inverter/PYLON-LV-CAN.cpp index d169146e..458ac7a0 100644 --- a/Software/src/inverter/PYLON-LV-CAN.cpp +++ b/Software/src/inverter/PYLON-LV-CAN.cpp @@ -42,20 +42,13 @@ CAN_frame PYLON_35E = {.FD = false, void update_values_can_inverter() { // 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? PYLON_351.data.u8[0] = datalayer.battery.status.voltage_dV & 0xff; 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] = maxChargeCurrent & 0xff; - PYLON_351.data.u8[3] = maxChargeCurrent >> 8; - int16_t maxDischargeCurrent = - 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_351.data.u8[2] = datalayer.battery.status.max_charge_current_dA & 0xff; + PYLON_351.data.u8[3] = datalayer.battery.status.max_charge_current_dA >> 8; + PYLON_351.data.u8[4] = datalayer.battery.status.max_discharge_current_dA & 0xff; + PYLON_351.data.u8[5] = datalayer.battery.status.max_discharge_current_dA >> 8; PYLON_355.data.u8[0] = (datalayer.battery.status.reported_soc / 10) & 0xff; 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[3] = 0x00; PYLON_359.data.u8[4] = PACK_NUMBER; - PYLON_359.data.u8[5] = 'P'; - PYLON_359.data.u8[6] = 'N'; + PYLON_359.data.u8[5] = 0x50; //P + PYLON_359.data.u8[6] = 0x4E; //N // 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; if (datalayer.battery.status.temperature_min_dC <= BATTERY_MINTEMPERATURE) 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) PYLON_359.data.u8[0] |= 0x04; // 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; // 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; if (datalayer.battery.status.temperature_min_dC <= BATTERY_MINTEMPERATURE * WARNINGS_PERCENT / 100) 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) PYLON_359.data.u8[2] |= 0x04; // 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_35C.data.u8[0] = 0xC0; // enable charging and discharging diff --git a/Software/src/inverter/SMA-CAN.cpp b/Software/src/inverter/SMA-CAN.cpp index 4fabbd16..cecae60c 100644 --- a/Software/src/inverter/SMA-CAN.cpp +++ b/Software/src/inverter/SMA-CAN.cpp @@ -70,37 +70,12 @@ CAN_frame SMA_158 = {.FD = false, .ID = 0x158, .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 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 //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 = ((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? SMA_358.data.u8[3] = (datalayer.battery.info.min_design_voltage_dV & 0x00FF); //Discharge limited current, 500 = 50A, (0.1, A) - SMA_358.data.u8[4] = (discharge_current >> 8); - SMA_358.data.u8[5] = (discharge_current & 0x00FF); + SMA_358.data.u8[4] = (datalayer.battery.status.max_discharge_current_dA >> 8); + SMA_358.data.u8[5] = (datalayer.battery.status.max_discharge_current_dA & 0x00FF); //Charge limited current, 125 =12.5A (0.1, A) - SMA_358.data.u8[6] = (charge_current >> 8); - SMA_358.data.u8[7] = (charge_current & 0x00FF); + SMA_358.data.u8[6] = (datalayer.battery.status.max_charge_current_dA >> 8); + SMA_358.data.u8[7] = (datalayer.battery.status.max_charge_current_dA & 0x00FF); //SOC (100.00%) SMA_3D8.data.u8[0] = (datalayer.battery.status.reported_soc >> 8); diff --git a/Software/src/inverter/SMA-TRIPOWER-CAN.cpp b/Software/src/inverter/SMA-TRIPOWER-CAN.cpp index 0cf0e0b7..9c0cc279 100644 --- a/Software/src/inverter/SMA-TRIPOWER-CAN.cpp +++ b/Software/src/inverter/SMA-TRIPOWER-CAN.cpp @@ -81,8 +81,6 @@ CAN_frame SMA_018 = {.FD = false, .ID = 0x018, .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 uint16_t ampere_hours_remaining = 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 //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 = ((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[3] = (datalayer.battery.info.min_design_voltage_dV & 0x00FF); //Discharge limited current, 500 = 50A, (0.1, A) - SMA_00D.data.u8[4] = (discharge_current >> 8); - SMA_00D.data.u8[5] = (discharge_current & 0x00FF); + SMA_00D.data.u8[4] = (datalayer.battery.status.max_discharge_current_dA >> 8); + SMA_00D.data.u8[5] = (datalayer.battery.status.max_discharge_current_dA & 0x00FF); //Charge limited current, 125 =12.5A (0.1, A) - SMA_00D.data.u8[6] = (charge_current >> 8); - SMA_00D.data.u8[7] = (charge_current & 0x00FF); + SMA_00D.data.u8[6] = (datalayer.battery.status.max_charge_current_dA >> 8); + SMA_00D.data.u8[7] = (datalayer.battery.status.max_charge_current_dA & 0x00FF); // Battery State //SOC (100.00%) diff --git a/Software/src/inverter/SOFAR-CAN.cpp b/Software/src/inverter/SOFAR-CAN.cpp index 628db1a3..eb13b7c5 100644 --- a/Software/src/inverter/SOFAR-CAN.cpp +++ b/Software/src/inverter/SOFAR-CAN.cpp @@ -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 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[2] = DC charge current limitation (Default 25.0A) - //SOFAR_351.data.u8[3] = DC charge current limitation - //SOFAR_351.data.u8[4] = DC discharge current limitation (Default 25.0A) - //SOFAR_351.data.u8[5] = DC discharge current limitation + SOFAR_351.data.u8[2] = (datalayer.battery.status.max_charge_current_dA >> 8); + SOFAR_351.data.u8[3] = (datalayer.battery.status.max_charge_current_dA & 0x00FF); + SOFAR_351.data.u8[4] = (datalayer.battery.status.max_discharge_current_dA >> 8); + SOFAR_351.data.u8[5] = (datalayer.battery.status.max_discharge_current_dA & 0x00FF); //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[7] = (datalayer.battery.info.min_design_voltage_dV & 0x00FF); diff --git a/Software/src/inverter/SOLAX-CAN.cpp b/Software/src/inverter/SOLAX-CAN.cpp index 32c6207b..790c8524 100644 --- a/Software/src/inverter/SOLAX-CAN.cpp +++ b/Software/src/inverter/SOLAX-CAN.cpp @@ -10,8 +10,6 @@ // https://github.com/dalathegreat/Battery-Emulator/wiki/Solax-inverters /* 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 uint8_t STATE = BATTERY_ANNOUNCE; static unsigned long LastFrameTime = 0; @@ -93,50 +91,6 @@ void update_values_can_inverter() { //This function maps all the values fetched temperature_average = ((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 if (datalayer.battery.info.total_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[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[4] = (uint8_t)(max_charge_rate_amp * 10); - SOLAX_1872.data.u8[5] = ((max_charge_rate_amp * 10) >> 8); - SOLAX_1872.data.u8[6] = (uint8_t)(max_discharge_rate_amp * 10); - SOLAX_1872.data.u8[7] = ((max_discharge_rate_amp * 10) >> 8); + SOLAX_1872.data.u8[4] = (uint8_t)datalayer.battery.status.max_charge_current_dA; + SOLAX_1872.data.u8[5] = (datalayer.battery.status.max_charge_current_dA >> 8); + SOLAX_1872.data.u8[6] = (uint8_t)datalayer.battery.status.max_discharge_current_dA; + SOLAX_1872.data.u8[7] = (datalayer.battery.status.max_discharge_current_dA >> 8); //BMS_PackData SOLAX_1873.data.u8[0] = (uint8_t)datalayer.battery.status.voltage_dV; // OK From 91f3c8caf92e2bdbf9fe43d633f3b3d148e14987 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Fri, 8 Nov 2024 23:37:16 +0200 Subject: [PATCH 136/210] Rename user specified limit in datalayer --- Software/src/datalayer/datalayer.h | 8 ++++---- Software/src/devboard/webserver/settings_html.cpp | 8 ++++---- Software/src/devboard/webserver/webserver.cpp | 6 +++--- Software/src/inverter/BYD-MODBUS.cpp | 4 ++-- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Software/src/datalayer/datalayer.h b/Software/src/datalayer/datalayer.h index b4967f4b..a26097af 100644 --- a/Software/src/datalayer/datalayer.h +++ b/Software/src/datalayer/datalayer.h @@ -19,10 +19,10 @@ typedef struct { uint16_t min_cell_voltage_mV = 2700; /** The maxumum allowed deviation between cells, in milliVolt. 500 = 0.500 V */ 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; + /** 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; /** uint8_t */ /** Total number of cells in the pack */ diff --git a/Software/src/devboard/webserver/settings_html.cpp b/Software/src/devboard/webserver/settings_html.cpp index 8d2d5ec7..5efe2493 100644 --- a/Software/src/devboard/webserver/settings_html.cpp +++ b/Software/src/devboard/webserver/settings_html.cpp @@ -57,11 +57,11 @@ String settings_processor(const String& var) { content += "

SOC min percentage: " + String(datalayer.battery.settings.min_percentage / 100.0, 1) + "

"; - content += - "

Max charge speed: " + String(datalayer.battery.info.max_charge_amp_dA / 10.0, 1) + - " A

"; + content += "

Max charge speed: " + + String(datalayer.battery.info.max_user_set_charge_dA / 10.0, 1) + + " A

"; content += "

Max discharge speed: " + - String(datalayer.battery.info.max_discharge_amp_dA / 10.0, 1) + + String(datalayer.battery.info.max_user_set_discharge_dA / 10.0, 1) + " A

"; // Close the block content += "
"; diff --git a/Software/src/devboard/webserver/webserver.cpp b/Software/src/devboard/webserver/webserver.cpp index 4a1554f3..244d8846 100644 --- a/Software/src/devboard/webserver/webserver.cpp +++ b/Software/src/devboard/webserver/webserver.cpp @@ -209,7 +209,7 @@ void init_webserver() { return request->requestAuthentication(); if (request->hasParam("value")) { String value = request->getParam("value")->value(); - datalayer.battery.info.max_charge_amp_dA = static_cast(value.toFloat() * 10); + datalayer.battery.info.max_user_set_charge_dA = static_cast(value.toFloat() * 10); storeSettings(); request->send(200, "text/plain", "Updated successfully"); } else { @@ -223,7 +223,7 @@ void init_webserver() { return request->requestAuthentication(); if (request->hasParam("value")) { String value = request->getParam("value")->value(); - datalayer.battery.info.max_discharge_amp_dA = static_cast(value.toFloat() * 10); + datalayer.battery.info.max_user_set_discharge_dA = static_cast(value.toFloat() * 10); storeSettings(); request->send(200, "text/plain", "Updated successfully"); } else { @@ -285,7 +285,7 @@ void init_webserver() { String value = request->getParam("value")->value(); float val = value.toFloat(); - if (!(val <= datalayer.battery.info.max_charge_amp_dA && val <= CHARGER_MAX_A)) { + if (!(val <= datalayer.battery.info.max_user_set_charge_dA && val <= CHARGER_MAX_A)) { request->send(400, "text/plain", "Bad Request"); } diff --git a/Software/src/inverter/BYD-MODBUS.cpp b/Software/src/inverter/BYD-MODBUS.cpp index c37a5139..3522e18b 100644 --- a/Software/src/inverter/BYD-MODBUS.cpp +++ b/Software/src/inverter/BYD-MODBUS.cpp @@ -65,13 +65,13 @@ void handle_update_data_modbusp301_byd() { } // Convert max discharge Amp value to max Watt user_configured_max_discharge_W = - ((datalayer.battery.info.max_discharge_amp_dA * datalayer.battery.info.max_design_voltage_dV) / 100); + ((datalayer.battery.info.max_user_set_discharge_dA * datalayer.battery.info.max_design_voltage_dV) / 100); // 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); // Convert max charge Amp value to max Watt user_configured_max_charge_W = - ((datalayer.battery.info.max_charge_amp_dA * datalayer.battery.info.max_design_voltage_dV) / 100); + ((datalayer.battery.info.max_user_set_charge_dA * datalayer.battery.info.max_design_voltage_dV) / 100); // 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); From 221278355ecad0dfa71c7b517a18c5649a39b0a0 Mon Sep 17 00:00:00 2001 From: rha Date: Sat, 9 Nov 2024 22:40:41 +0200 Subject: [PATCH 137/210] added tmpframe for battery_info --- Software/src/inverter/KOSTAL-RS485.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Software/src/inverter/KOSTAL-RS485.cpp b/Software/src/inverter/KOSTAL-RS485.cpp index 83e08ff3..bd5cb8ef 100644 --- a/Software/src/inverter/KOSTAL-RS485.cpp +++ b/Software/src/inverter/KOSTAL-RS485.cpp @@ -332,9 +332,11 @@ void receive_RS485() // Runs as fast as possible to handle the serial stream } if (code == 0x84a) { //Send battery info - BATTERY_INFO[38] = calculate_kostal_crc(BATTERY_INFO, 38); - scramble_null_bytes(BATTERY_INFO,40); - send_kostal(BATTERY_INFO, 40); + byte tmpframe[40]; //copy values to prevent data manipulation during rewrite/crc calculation + memcpy(tmpframe, BATTERY_INFO, 40); + tmpframe[38] = calculate_kostal_crc(tmpframe, 38); + scramble_null_bytes(tmpframe, 40); + send_kostal(tmpframe, 40); if (!startupMillis) { startupMillis = currentMillis; } From ae34a9985cc50b0628b2445d7ae9e3ce834d03b4 Mon Sep 17 00:00:00 2001 From: wjcloudy <56305354+wjcloudy@users.noreply.github.com> Date: Sat, 9 Nov 2024 21:03:37 +0000 Subject: [PATCH 138/210] Update BMW-IX-BATTERY.h --- Software/src/battery/BMW-IX-BATTERY.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Software/src/battery/BMW-IX-BATTERY.h b/Software/src/battery/BMW-IX-BATTERY.h index 72afe6d5..602411af 100644 --- a/Software/src/battery/BMW-IX-BATTERY.h +++ b/Software/src/battery/BMW-IX-BATTERY.h @@ -16,7 +16,7 @@ #define MAX_CHARGE_POWER_WHEN_TOPBALANCING_W 500 #define RAMPDOWN_SOC 9000 // (90.00) SOC% to start ramping down from max charge power towards 0 at 100.00% #define STALE_PERIOD_CONFIG \ - 180000; //Number of milliseconds before critical values are classed as stale/stuck 180000 = 180 seconds + 210000; //Number of milliseconds before critical values are classed as stale/stuck 210000 = 210 seconds void setup_battery(void); void transmit_can(CAN_frame* tx_frame, int interface); From c3e67d2f62b14ca112e30e5dabb1f99ff77a39e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Sun, 10 Nov 2024 11:49:31 +0200 Subject: [PATCH 139/210] Make discharge limits under settings datalayer --- Software/Software.ino | 16 ++++++++-------- Software/src/datalayer/datalayer.h | 8 ++++---- .../src/devboard/webserver/settings_html.cpp | 4 ++-- Software/src/devboard/webserver/webserver.cpp | 6 +++--- Software/src/inverter/BYD-MODBUS.cpp | 4 ++-- 5 files changed, 19 insertions(+), 19 deletions(-) diff --git a/Software/Software.ino b/Software/Software.ino index adc771e1..2b45dc5a 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -401,11 +401,11 @@ void init_stored_settings() { } temp = settings.getUInt("MAXCHARGEAMP", false); 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); 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); 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 @@ -847,11 +847,11 @@ void update_calculated_values() { ((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.info.max_charge_amp_dA) { - datalayer.battery.status.max_charge_current_dA = datalayer.battery.info.max_charge_amp_dA; + 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.info.max_discharge_amp_dA) { - datalayer.battery.status.max_discharge_current_dA = datalayer.battery.info.max_discharge_amp_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) { @@ -991,8 +991,8 @@ void storeSettings() { datalayer.battery.settings.max_percentage / 10); // Divide by 10 for backwards compatibility settings.putUInt("MINPERCENTAGE", datalayer.battery.settings.min_percentage / 10); // Divide by 10 for backwards compatibility - settings.putUInt("MAXCHARGEAMP", datalayer.battery.info.max_charge_amp_dA); - settings.putUInt("MAXDISCHARGEAMP", datalayer.battery.info.max_discharge_amp_dA); + settings.putUInt("MAXCHARGEAMP", datalayer.battery.settings.max_user_set_charge_dA); + settings.putUInt("MAXDISCHARGEAMP", datalayer.battery.settings.max_user_set_discharge_dA); settings.putBool("USE_SCALED_SOC", datalayer.battery.settings.soc_scaling_active); settings.end(); } diff --git a/Software/src/datalayer/datalayer.h b/Software/src/datalayer/datalayer.h index a26097af..7e1880af 100644 --- a/Software/src/datalayer/datalayer.h +++ b/Software/src/datalayer/datalayer.h @@ -19,10 +19,6 @@ typedef struct { uint16_t min_cell_voltage_mV = 2700; /** The maxumum allowed deviation between cells, in milliVolt. 500 = 0.500 V */ uint16_t max_cell_voltage_deviation_mV = 500; - /** 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; /** uint8_t */ /** Total number of cells in the pack */ @@ -111,6 +107,10 @@ typedef struct { * you want the inverter to be able to use. At this real SOC, the inverter * will "see" 100% */ 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; typedef struct { diff --git a/Software/src/devboard/webserver/settings_html.cpp b/Software/src/devboard/webserver/settings_html.cpp index 5efe2493..caac64d4 100644 --- a/Software/src/devboard/webserver/settings_html.cpp +++ b/Software/src/devboard/webserver/settings_html.cpp @@ -58,10 +58,10 @@ String settings_processor(const String& var) { ";'>SOC min percentage: " + String(datalayer.battery.settings.min_percentage / 100.0, 1) + " "; content += "

Max charge speed: " + - String(datalayer.battery.info.max_user_set_charge_dA / 10.0, 1) + + String(datalayer.battery.settings.max_user_set_charge_dA / 10.0, 1) + " A

"; content += "

Max discharge speed: " + - String(datalayer.battery.info.max_user_set_discharge_dA / 10.0, 1) + + String(datalayer.battery.settings.max_user_set_discharge_dA / 10.0, 1) + " A

"; // Close the block content += "
"; diff --git a/Software/src/devboard/webserver/webserver.cpp b/Software/src/devboard/webserver/webserver.cpp index 244d8846..81d77567 100644 --- a/Software/src/devboard/webserver/webserver.cpp +++ b/Software/src/devboard/webserver/webserver.cpp @@ -209,7 +209,7 @@ void init_webserver() { return request->requestAuthentication(); if (request->hasParam("value")) { String value = request->getParam("value")->value(); - datalayer.battery.info.max_user_set_charge_dA = static_cast(value.toFloat() * 10); + datalayer.battery.settings.max_user_set_charge_dA = static_cast(value.toFloat() * 10); storeSettings(); request->send(200, "text/plain", "Updated successfully"); } else { @@ -223,7 +223,7 @@ void init_webserver() { return request->requestAuthentication(); if (request->hasParam("value")) { String value = request->getParam("value")->value(); - datalayer.battery.info.max_user_set_discharge_dA = static_cast(value.toFloat() * 10); + datalayer.battery.settings.max_user_set_discharge_dA = static_cast(value.toFloat() * 10); storeSettings(); request->send(200, "text/plain", "Updated successfully"); } else { @@ -285,7 +285,7 @@ void init_webserver() { String value = request->getParam("value")->value(); float val = value.toFloat(); - if (!(val <= datalayer.battery.info.max_user_set_charge_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"); } diff --git a/Software/src/inverter/BYD-MODBUS.cpp b/Software/src/inverter/BYD-MODBUS.cpp index 3522e18b..7cee6b8d 100644 --- a/Software/src/inverter/BYD-MODBUS.cpp +++ b/Software/src/inverter/BYD-MODBUS.cpp @@ -65,13 +65,13 @@ void handle_update_data_modbusp301_byd() { } // Convert max discharge Amp value to max Watt user_configured_max_discharge_W = - ((datalayer.battery.info.max_user_set_discharge_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 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 user_configured_max_charge_W = - ((datalayer.battery.info.max_user_set_charge_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 max_charge_W = std::min(datalayer.battery.status.max_charge_power_W, user_configured_max_charge_W); From 5650c991abd592b265805ab1907e2ece2ded09f2 Mon Sep 17 00:00:00 2001 From: wjcloudy <56305354+wjcloudy@users.noreply.github.com> Date: Sun, 10 Nov 2024 14:24:12 +0000 Subject: [PATCH 140/210] Reduce iX stale value sensitivity --- Software/src/battery/BMW-IX-BATTERY.cpp | 2 +- Software/src/battery/BMW-IX-BATTERY.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Software/src/battery/BMW-IX-BATTERY.cpp b/Software/src/battery/BMW-IX-BATTERY.cpp index a04cb093..2f316097 100644 --- a/Software/src/battery/BMW-IX-BATTERY.cpp +++ b/Software/src/battery/BMW-IX-BATTERY.cpp @@ -672,7 +672,7 @@ void receive_can_battery(CAN_frame rx_frame) { datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; //This is the most important safety values, if we receive this we reset CAN alive counter. - if ((rx_frame.data.u8[6] << 8 | rx_frame.data.u8[7]) == 10000 && + if ((rx_frame.data.u8[6] << 8 | rx_frame.data.u8[7]) == 10000 || (rx_frame.data.u8[8] << 8 | rx_frame.data.u8[9]) == 10000) { //Qualifier Invalid Mode - Request Reboot #ifdef DEBUG_VIA_USB Serial.println("Cell MinMax Qualifier Invalid - Requesting BMS Reset"); diff --git a/Software/src/battery/BMW-IX-BATTERY.h b/Software/src/battery/BMW-IX-BATTERY.h index 602411af..a5336778 100644 --- a/Software/src/battery/BMW-IX-BATTERY.h +++ b/Software/src/battery/BMW-IX-BATTERY.h @@ -16,7 +16,7 @@ #define MAX_CHARGE_POWER_WHEN_TOPBALANCING_W 500 #define RAMPDOWN_SOC 9000 // (90.00) SOC% to start ramping down from max charge power towards 0 at 100.00% #define STALE_PERIOD_CONFIG \ - 210000; //Number of milliseconds before critical values are classed as stale/stuck 210000 = 210 seconds + 300000; //Number of milliseconds before critical values are classed as stale/stuck 300000 = 300 seconds void setup_battery(void); void transmit_can(CAN_frame* tx_frame, int interface); From ec00b9be8f0b09cbf60f004dd345e35b0d36541f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Mon, 11 Nov 2024 20:26:35 +0200 Subject: [PATCH 141/210] Refactor CAN logging --- Software/Software.ino | 42 ++++++++++++---------- Software/USER_SETTINGS.h | 4 +-- Software/src/battery/TEST-FAKE-BATTERY.cpp | 24 ------------- 3 files changed, 25 insertions(+), 45 deletions(-) diff --git a/Software/Software.ino b/Software/Software.ino index 2b45dc5a..c85c376d 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -614,31 +614,32 @@ void init_equipment_stop_button() { #endif -#ifdef CAN_FD -// Functions -#ifdef DEBUG_CANFD_DATA -enum frameDirection { MSG_RX, MSG_TX }; -void print_canfd_frame(CANFDMessage rx_frame, frameDirection msgDir); // Needs to be declared before it is defined -void print_canfd_frame(CANFDMessage rx_frame, frameDirection msgDir) { - int i = 0; +enum frameDirection { MSG_RX, MSG_TX }; //RX = 0, TX = 1 +void print_can_frame(CAN_frame frame, frameDirection msgDir); +void print_can_frame(CAN_frame frame, frameDirection msgDir) { + uint8_t i = 0; + Serial.print(millis()); + Serial.print(" "); (msgDir == 0) ? Serial.print("RX ") : Serial.print("TX "); - Serial.print(rx_frame.id, HEX); + Serial.print(frame.ID, HEX); + Serial.print(" "); + Serial.print(frame.DLC); Serial.print(" "); - for (i = 0; i < rx_frame.len; i++) { - Serial.print(rx_frame.data[i] < 16 ? "0" : ""); - Serial.print(rx_frame.data[i], HEX); + for (i = 0; i < frame.DLC; i++) { + Serial.print(frame.data.u8[i] < 16 ? "0" : ""); + Serial.print(frame.data.u8[i], HEX); Serial.print(" "); } Serial.println(" "); } -#endif + +#ifdef CAN_FD +// Functions void receive_canfd() { // This section checks if we have a complete CAN-FD message incoming CANFDMessage frame; if (canfd.available()) { canfd.receive(frame); -#ifdef DEBUG_CANFD_DATA - print_canfd_frame(frame, frameDirection(MSG_RX)); -#endif + CAN_frame rx_frame; rx_frame.ID = frame.id; rx_frame.ext_ID = frame.ext; @@ -1078,6 +1079,9 @@ void transmit_can(CAN_frame* tx_frame, int interface) { if (!allowed_to_send_CAN) { return; } +#ifdef DEBUG_CAN_DATA + print_can_frame(*tx_frame, frameDirection(MSG_TX)); +#endif //DEBUG_CAN_DATA switch (interface) { case CAN_NATIVE: @@ -1120,10 +1124,6 @@ void transmit_can(CAN_frame* tx_frame, int interface) { send_ok = canfd.tryToSend(MCP2518Frame); if (!send_ok) { set_event(EVENT_CANFD_BUFFER_FULL, interface); - } else { -#ifdef DEBUG_CANFD_DATA - print_canfd_frame(MCP2518Frame, frameDirection(MSG_TX)); -#endif } #else // Interface not compiled, and settings try to use it set_event(EVENT_INTERFACE_MISSING, interface); @@ -1136,6 +1136,10 @@ void transmit_can(CAN_frame* tx_frame, int interface) { } void receive_can(CAN_frame* rx_frame, int interface) { +#ifdef DEBUG_CAN_DATA + print_can_frame(*rx_frame, frameDirection(MSG_RX)); +#endif //DEBUG_CAN_DATA + if (interface == can_config.battery) { receive_can_battery(*rx_frame); } diff --git a/Software/USER_SETTINGS.h b/Software/USER_SETTINGS.h index 26272ca7..2f30bf26 100644 --- a/Software/USER_SETTINGS.h +++ b/Software/USER_SETTINGS.h @@ -20,7 +20,7 @@ //#define KIA_E_GMP_BATTERY //#define KIA_HYUNDAI_HYBRID_BATTERY //#define MG_5_BATTERY -//#define NISSAN_LEAF_BATTERY +#define NISSAN_LEAF_BATTERY //#define PYLON_BATTERY //#define RJXZS_BMS //#define RENAULT_KANGOO_BATTERY @@ -54,7 +54,7 @@ /* Other options */ //#define DEBUG_VIA_USB //Enable this line to have the USB port output serial diagnostic data while program runs (WARNING, raises CPU load, do not use for production) -//#define DEBUG_CANFD_DATA //Enable this line to have the USB port output CAN-FD data while program runs (WARNING, raises CPU load, do not use for production) +//#define DEBUG_CAN_DATA //Enable this line to print incoming/outgoing CAN & CAN-FD messages to USB serial (WARNING, raises CPU load, do not use for production) //#define INTERLOCK_REQUIRED //Nissan LEAF specific setting, if enabled requires both high voltage conenctors to be seated before starting //#define CONTACTOR_CONTROL //Enable this line to have pins 25,32,33 handle automatic precharge/contactor+/contactor- closing sequence //#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. diff --git a/Software/src/battery/TEST-FAKE-BATTERY.cpp b/Software/src/battery/TEST-FAKE-BATTERY.cpp index 630fa536..423654aa 100644 --- a/Software/src/battery/TEST-FAKE-BATTERY.cpp +++ b/Software/src/battery/TEST-FAKE-BATTERY.cpp @@ -130,35 +130,11 @@ void update_values_battery2() { // Handle the values coming in from battery #2 void receive_can_battery2(CAN_frame rx_frame) { datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE; - // All CAN messages recieved will be logged via serial - Serial.print(millis()); // Example printout, time, ID, length, data: 7553 1DB 8 FF C0 B9 EA 0 0 2 5D - Serial.print(" "); - Serial.print(rx_frame.ID, HEX); - Serial.print(" "); - Serial.print(rx_frame.DLC); - Serial.print(" "); - for (int i = 0; i < rx_frame.DLC; ++i) { - Serial.print(rx_frame.data.u8[i], HEX); - Serial.print(" "); - } - Serial.println(""); } #endif // DOUBLE_BATTERY void receive_can_battery(CAN_frame rx_frame) { datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; - // All CAN messages recieved will be logged via serial - Serial.print(millis()); // Example printout, time, ID, length, data: 7553 1DB 8 FF C0 B9 EA 0 0 2 5D - Serial.print(" "); - Serial.print(rx_frame.ID, HEX); - Serial.print(" "); - Serial.print(rx_frame.DLC); - Serial.print(" "); - for (int i = 0; i < rx_frame.DLC; ++i) { - Serial.print(rx_frame.data.u8[i], HEX); - Serial.print(" "); - } - Serial.println(""); } void send_can_battery() { unsigned long currentMillis = millis(); From 538d7b6ac0013d8220ca543b7fea99a3f324881a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Mon, 11 Nov 2024 20:56:40 +0200 Subject: [PATCH 142/210] Remove LEAF --- Software/USER_SETTINGS.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Software/USER_SETTINGS.h b/Software/USER_SETTINGS.h index 2f30bf26..2f5428c9 100644 --- a/Software/USER_SETTINGS.h +++ b/Software/USER_SETTINGS.h @@ -20,7 +20,7 @@ //#define KIA_E_GMP_BATTERY //#define KIA_HYUNDAI_HYBRID_BATTERY //#define MG_5_BATTERY -#define NISSAN_LEAF_BATTERY +//#define NISSAN_LEAF_BATTERY //#define PYLON_BATTERY //#define RJXZS_BMS //#define RENAULT_KANGOO_BATTERY From 72c2373cf4680913330ea3dd251618b0927b5cd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Tue, 12 Nov 2024 00:02:14 +0200 Subject: [PATCH 143/210] Fix inverter missing event for Foxess --- Software/src/inverter/FOXESS-CAN.cpp | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/Software/src/inverter/FOXESS-CAN.cpp b/Software/src/inverter/FOXESS-CAN.cpp index 3909bbbe..e4b3b34c 100644 --- a/Software/src/inverter/FOXESS-CAN.cpp +++ b/Software/src/inverter/FOXESS-CAN.cpp @@ -28,7 +28,6 @@ static int16_t current_per_pack = 0; static uint8_t temperature_max_per_pack = 0; static uint8_t temperature_min_per_pack = 0; static uint8_t current_pack_info = 0; -static uint8_t inverterStillAlive = 60; // Inverter can be missing for 1minute on startup static bool send_cellvoltages = false; static unsigned long previousMillisCellvoltage = 0; // Store the last time a cellvoltage CAN messages were sent @@ -362,16 +361,6 @@ void update_values_can_inverter() { //This function maps all the CAN values fet temperature_average = ((datalayer.battery.status.temperature_max_dC + datalayer.battery.status.temperature_min_dC) / 2); - if (inverterStillAlive > 0) { - inverterStillAlive--; - } - - if (!inverterStillAlive) { - set_event(EVENT_CAN_INVERTER_MISSING, 0); - } else { - clear_event(EVENT_CAN_INVERTER_MISSING); - } - //Put the values into the CAN messages //BMS_Limits FOXESS_1872.data.u8[0] = (uint8_t)datalayer.battery.info.max_design_voltage_dV; @@ -686,7 +675,7 @@ void send_can_inverter() { // This function loops as fast as possible void receive_can_inverter(CAN_frame rx_frame) { if (rx_frame.ID == 0x1871) { - inverterStillAlive = CAN_STILL_ALIVE; + datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE; if (rx_frame.data.u8[0] == 0x03) { //0x1871 [0x03, 0x06, 0x17, 0x05, 0x09, 0x09, 0x28, 0x22] //This message is sent by the inverter every '6' seconds (0.5s after the pack serial numbers) //and contains a timestamp in bytes 2-7 i.e. ,,
,,, From ff496f3d4e5729fbc7f5c6ca16b49c8b2f350a11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Wed, 13 Nov 2024 12:11:07 +0200 Subject: [PATCH 144/210] Add Range Rover PHEV support --- Software/USER_SETTINGS.h | 1 + Software/src/battery/BATTERIES.h | 4 + .../src/battery/RANGE-ROVER-PHEV-BATTERY.cpp | 329 ++++++++++++++++++ .../src/battery/RANGE-ROVER-PHEV-BATTERY.h | 18 + Software/src/devboard/webserver/webserver.cpp | 3 + 5 files changed, 355 insertions(+) create mode 100644 Software/src/battery/RANGE-ROVER-PHEV-BATTERY.cpp create mode 100644 Software/src/battery/RANGE-ROVER-PHEV-BATTERY.h diff --git a/Software/USER_SETTINGS.h b/Software/USER_SETTINGS.h index 2f5428c9..c6351a12 100644 --- a/Software/USER_SETTINGS.h +++ b/Software/USER_SETTINGS.h @@ -23,6 +23,7 @@ //#define NISSAN_LEAF_BATTERY //#define PYLON_BATTERY //#define RJXZS_BMS +//#define RANGE_ROVER_PHEV_BATTERY //#define RENAULT_KANGOO_BATTERY //#define RENAULT_TWIZY_BATTERY //#define RENAULT_ZOE_GEN1_BATTERY diff --git a/Software/src/battery/BATTERIES.h b/Software/src/battery/BATTERIES.h index ecc21ad1..3bc65189 100644 --- a/Software/src/battery/BATTERIES.h +++ b/Software/src/battery/BATTERIES.h @@ -58,6 +58,10 @@ #include "RJXZS-BMS.h" #endif +#ifdef RANGE_ROVER_PHEV_BATTERY +#include "RANGE-ROVER-PHEV-BATTERY.h" +#endif + #ifdef RENAULT_KANGOO_BATTERY #include "RENAULT-KANGOO-BATTERY.h" #endif diff --git a/Software/src/battery/RANGE-ROVER-PHEV-BATTERY.cpp b/Software/src/battery/RANGE-ROVER-PHEV-BATTERY.cpp new file mode 100644 index 00000000..d530c2d5 --- /dev/null +++ b/Software/src/battery/RANGE-ROVER-PHEV-BATTERY.cpp @@ -0,0 +1,329 @@ +#include "../include.h" +#ifdef RANGE_ROVER_PHEV_BATTERY +#include "../datalayer/datalayer.h" +#include "../devboard/utils/events.h" +#include "RANGE-ROVER-PHEV-BATTERY.h" + +/* TODO +- LOG files from vehicle needed to determine CAN content needed to send towards battery! + - BCCM_PMZ_A (0x18B 50ms) + - BCCMB_PMZ_A (0x224 90ms) + - BCM_CCP_RX_PMZCAN (0x601 non cyclic) + - EPIC_PMZ_B (0x009 non cyclic) + - GWM_FuelPumpEnableDataControl_PMZ (0x1F8 non cyclic) + - GWM_IgnitionAuthDataTarget_PMZ (0x004 non cyclic) + - GWM_PMZ_A (0x008 10ms cyclic) + - GWM_PMZ_B -F, G-I, Immo, K-P + - 0x010 10ms + - 0x090 10ms + - 0x108 20ms + - 0x110 20ms + - 0x1d0 80ms + - 0x490 900ms + - 0x1B0 80ms + - 0x460 720ms + - 0x006 non cyclic immo + - 0x450 600ms + - 0x2b8 180ms + - 0x388 200ms + - 0x2b0 180ms + - 0x380 80ms + - GWM_PMZ_V_HYBRID (0x18d 60ms) + - HVAC_PMZ_A-E + - 0x1a8 70ms + - 0x210 100ms + - 0x300 200ms + - 0x440 180ms + - 0x0c0 10ms + - PCM_PMZ_C_Hybrid C, D, H, M + - 0x030 15ms + - 0x304 180ms + - 0x1C0 80ms + - 0x434 350ms + - TCU_PMZ_A + - 0x014 non cyclic, command from TCU, most likely not needed +- Determine CRC calculation +- Figure out contactor closing requirements +*/ + +/* Do not change code below unless you are sure what you are doing */ +static unsigned long previousMillis50ms = 0; // will store last time a 50ms CAN Message was sent + +//CAN content from battery +static bool StatusCAT5BPOChg = false; +static bool StatusCAT4Derate = false; +static uint8_t OCMonitorStatus = 0; +static bool StatusCAT3 = false; +static bool IsolationStatus = false; +static bool HVILStatus = false; +static bool ContactorStatus = false; +static uint8_t StatusGpCounter = 0; +static bool WeldCheckStatus = false; +static bool StatusCAT7NowBPO = false; +static bool StatusCAT6DlyBPO = false; +static uint8_t StatusGpCS = 0; +static uint8_t CAT6Count = 0; +static bool EndOfCharge = false; +static bool DerateWarning = false; +static bool PrechargeAllowed = false; +static uint8_t DischargeExtGpCounter = 0; // Counter 0-15 +static uint8_t DischargeExtGpCS = 0; // CRC +static uint16_t DischargeVoltageLimit = 0; //Min voltage battery allows discharging to +static uint16_t DischargePowerLimitExt = 0; //Momentary Discharge power limit kW*0.01 (0-655) +static uint16_t DischargeContPwrLmt = 0; //Longterm Discharge power limit kW*0.01 (0-655) +static uint8_t PwrGpCS = 0; // CRC +static uint8_t PwrGpCounter = 0; // Counter 0-15 +static uint16_t VoltageExt = 370; // Voltage of the HV Battery +static uint16_t VoltageBus = 0; // Voltage on the high-voltage DC bus +static int32_t CurrentExt = + 209715; //Positive - discharge, Negative Charge (0 - 16777215) Scaling: 0.025 Offset: -209715.175 Units: Amps +static bool HVIsolationTestRunning = false; +static uint16_t VoltageOC = + 0; //The instantaneous equivalent open-circuit voltage of the high voltage battery. This is used by the high-voltage inverter in power prediction and derating calculations. +static uint16_t DchCurrentLimit = + 0; // A, 'Maximum current that can be delivered by the HV Battery during motoring mode i.e during discharging. +static uint16_t ChgCurrentLimit = + 0; // - 1023 A, Maximum current that can be transferred into the HV Battery during generating mode i.e during charging. Charging is neagtive and discharging is positive. +static uint16_t ChargeContPwrLmt = 0; //Longterm charge power limit kW*0.01 (0-655) +static uint16_t ChargePowerLimitExt = 0; //Momentary Charge power limit kW*0.01 (0-655) +static uint8_t ChgExtGpCS = 0; // CRC +static uint8_t ChgExtGpCounter = 0; //counter 0-15 +static uint16_t ChargeVoltageLimit = 500; //Max voltage limit during charging of the HV Battery. +static uint8_t CurrentWarning = 0; // 0 normal, 1 cell overcurrent, 2 cell undercurrent +static uint8_t TempWarning = 0; // 0 normal, 1 cell overtemp, 2 cell undertemp +static int8_t TempUpLimit = 0; //Upper temperature limit. +static uint8_t CellVoltWarning = 0; // 0 normal, 1 cell overvoltage, 2 cell undervoltage +static bool CCCVChargeMode = false; //0 CC, 1 = CV +static uint16_t CellVoltUpLimit = 0; //mV, Upper cell voltage limit +static uint16_t SOCHighestCell = 0; //0.01, % +static uint16_t SOCLowestCell = 0; //0.01, % +static uint16_t SOCAverage = 0; //0.01, % +static bool WakeUpTopUpReq = + false; //The HV Battery can trigger a vehicle wake-up to request its State of Charge to be increased. +static bool WakeUpThermalReq = + false; //The HV Battery can trigger a vehicle wake-up in order to be thermally managed (ie. cooled down OR warmed up). +static bool WakeUpDchReq = + false; //The HV Battery can trigger a vehicle wake-up to request its State of Charge to be reduced. +static uint16_t StateofHealth = 0; +static uint16_t EstimatedLossChg = + 0; //fact0.001, kWh Expected energy which will be lost during charging (at the rate given by VSCEstChargePower) due to resistance within the HV Battery. +static bool CoolingRequest = + false; //HV Battery cooling request to be cooled by the eAC/chiller as its cooling needs exceed the LTR cooling loop capability. +static uint16_t EstimatedLossDch = + 0; //fact0.001, kWh Expected energy which will be lost during discharging (at the rate given by VSCEstDischargePower) due to resistance within the HV Battery. +static uint8_t FanDutyRequest = + 0; //Request from the HV Battery cooling system to demand a change of duty for the electrical engine cooling fan speed (whilst using its LTR cooling loop). +static bool ValveCtrlStat = false; //0 Chiller/Heater cooling loop requested , 1 LTR cooling loop requested +static uint16_t EstLossDchTgtSoC = + 0; //fact0.001, kWh Expected energy which will be lost during discharging (at the rate given by VSCEstimatedDchPower) from the target charging SoC (PHEV: HVBattEnergyUsableMax, BEV: HVBattEnergyUsableBulk) down to HVBattEnergyUsableMin, due to resistance within the Traction Battery. +static uint8_t HeatPowerGenChg = + 0; //fact0.1, kW, Estimated average heat generated by battery if charged at the rate given by VSCEstimatedChgPower. +static uint8_t HeatPowerGenDch = + 0; //fact0.1, kW, Estimated average heat generated by battery if discharged at the rate given by VSCEstimatedDchPower. +static uint8_t WarmupRateChg = + 0; //fact0.1, C/min , Expected average rate at which the battery will self-heat if charged at the rate given by VSCEstimatedChgPower. +static uint8_t WarmupRateDch = + 0; //fact0.1, C/min , Expected average rate at which the battery will self-heat if discharged at the rate given by VSCEstimatedDchPower. +static uint16_t CellVoltageMax = 3700; +static uint16_t CellVoltageMin = 3700; +static int8_t CellTempAverage = 0; //factor0.5, -40 offset +static int8_t CellTempColdest = 0; //factor0.5, -40 offset +static int8_t CellTempHottest = 0; //factor0.5, -40 offset +static uint8_t HeaterCtrlStat = 0; //factor1, 0 offset +static bool ThermalOvercheck = false; // 0 OK, 1 NOT OK +static int8_t InletCoolantTemp = 0; //factor0.5, -40 offset +static bool ClntPumpDiagStat_UB = false; +static bool InletCoolantTemp_UB = false; +static bool CoolantLevel = false; // Coolant level OK , 1 NOT OK +static bool ClntPumpDiagStat = false; // 0 Pump OK, 1 NOT OK +static uint8_t MILRequest = 0; //No req, 1 ON, 2 FLASHING, 3 unused +static uint16_t EnergyAvailable = 0; //fac0.05 , The total energy available from the HV Battery +static uint16_t EnergyUsableMax = 0; //fac0.05 , The total energy available from the HV Battery at its maximum SOC +static uint16_t EnergyUsableMin = 0; //fac0.05 , The total energy available from the HV Battery at its minimum SOC +static uint16_t TotalCapacity = + 0; //fac0.1 , Total Battery capacity in Kwh. This will reduce over the lifetime of the HV Battery. + +//CAN messages needed by battery (LOG needed!) +CAN_frame RANGE_ROVER_18B = {.FD = false, + .ext_ID = false, + .DLC = 8, + .ID = 0x18B, //CONTENT??? TODO + .data = {0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; + +void update_values_battery() { + + datalayer.battery.status.real_soc = SOCAverage; + + datalayer.battery.status.soh_pptt = StateofHealth * 10; + + datalayer.battery.status.voltage_dV = VoltageExt * 10; + + datalayer.battery.status.current_dA = (CurrentExt * 0.025) - 209715; + + datalayer.battery.status.active_power_W = //Power in watts, Negative = charging batt + ((datalayer.battery.status.voltage_dV * datalayer.battery.status.current_dA) / 100); + + datalayer.battery.status.max_charge_power_W = (ChargeContPwrLmt * 10) - 6550; + + datalayer.battery.status.max_discharge_power_W = (DischargeContPwrLmt * 10) - 6550; + + datalayer.battery.status.remaining_capacity_Wh = static_cast( + (static_cast(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh); + + datalayer.battery.status.cell_max_voltage_mV = CellVoltageMax; + + datalayer.battery.status.cell_min_voltage_mV = CellVoltageMin; + + datalayer.battery.status.temperature_min_dC = CellTempColdest * 10; + + datalayer.battery.status.temperature_max_dC = CellTempHottest * 10; + + datalayer.battery.info.max_design_voltage_dV = ChargeVoltageLimit * 10; + + datalayer.battery.info.min_design_voltage_dV = DischargeVoltageLimit * 10; +} + +void receive_can_battery(CAN_frame rx_frame) { + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + switch (rx_frame.ID) { + case 0x080: // 15ms + StatusCAT5BPOChg = (rx_frame.data.u8[0] & 0x01); + StatusCAT4Derate = (rx_frame.data.u8[0] & 0x02) >> 1; + OCMonitorStatus = (rx_frame.data.u8[0] & 0x0C) >> 2; + StatusCAT3 = (rx_frame.data.u8[0] & 0x10) >> 4; + IsolationStatus = (rx_frame.data.u8[0] & 0x20) >> 5; + HVILStatus = (rx_frame.data.u8[0] & 0x40) >> 6; + ContactorStatus = (rx_frame.data.u8[0] & 0x80) >> 7; + StatusGpCounter = (rx_frame.data.u8[1] & 0x0F); + WeldCheckStatus = (rx_frame.data.u8[1] & 0x20) >> 5; + StatusCAT7NowBPO = (rx_frame.data.u8[1] & 0x40) >> 6; + StatusCAT6DlyBPO = (rx_frame.data.u8[1] & 0x80) >> 7; + StatusGpCS = rx_frame.data.u8[2]; + CAT6Count = rx_frame.data.u8[3] & 0x7F; + EndOfCharge = (rx_frame.data.u8[6] & 0x04) >> 2; + DerateWarning = (rx_frame.data.u8[6] & 0x08) >> 3; + PrechargeAllowed = (rx_frame.data.u8[6] & 0x10) >> 4; + break; + case 0x100: // 20ms + DischargeExtGpCounter = (rx_frame.data.u8[0] & 0x0F); + DischargeExtGpCS = rx_frame.data.u8[1]; + DischargeVoltageLimit = (((rx_frame.data.u8[2] & 0x03) << 8) | rx_frame.data.u8[3]); + DischargePowerLimitExt = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + DischargeContPwrLmt = ((rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]); + break; + case 0x102: // 20ms + PwrGpCS = rx_frame.data.u8[0]; + PwrGpCounter = (rx_frame.data.u8[1] & 0x3C) >> 2; + VoltageExt = (((rx_frame.data.u8[1] & 0x03) << 8) | rx_frame.data.u8[2]); + VoltageBus = (((rx_frame.data.u8[3] & 0x03) << 8) | rx_frame.data.u8[4]); + CurrentExt = ((rx_frame.data.u8[5] << 8) | (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]); + break; + case 0x104: // 20ms + HVIsolationTestRunning = (rx_frame.data.u8[2] & 0x10) >> 4; + VoltageOC = (((rx_frame.data.u8[2] & 0x03) << 8) | rx_frame.data.u8[3]); + DchCurrentLimit = (((rx_frame.data.u8[4] & 0x03) << 8) | rx_frame.data.u8[5]); + ChgCurrentLimit = (((rx_frame.data.u8[6] & 0x03) << 8) | rx_frame.data.u8[7]); + break; + case 0x10A: // 20ms + ChargeContPwrLmt = ((rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]); + ChargePowerLimitExt = ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]); + ChgExtGpCS = rx_frame.data.u8[4]; + ChgExtGpCounter = (rx_frame.data.u8[5] >> 4); + ChargeVoltageLimit = (((rx_frame.data.u8[6] & 0x03) << 8) | rx_frame.data.u8[7]); + break; + case 0x198: // 60ms + CurrentWarning = (rx_frame.data.u8[4] & 0x03); + TempWarning = ((rx_frame.data.u8[4] & 0x0C) >> 2); + TempUpLimit = (rx_frame.data.u8[5] / 2) - 40; + CellVoltWarning = ((rx_frame.data.u8[6] & 0x60) >> 5); + CCCVChargeMode = ((rx_frame.data.u8[6] & 0x80) >> 7); + CellVoltUpLimit = (((rx_frame.data.u8[6] & 0x1F) << 8) | rx_frame.data.u8[7]); + break; + case 0x220: // 100ms + SOCHighestCell = (((rx_frame.data.u8[0] & 0x3F) << 8) | rx_frame.data.u8[1]); + SOCLowestCell = (((rx_frame.data.u8[2] & 0x3F) << 8) | rx_frame.data.u8[3]); + SOCAverage = (((rx_frame.data.u8[4] & 0x3F) << 8) | rx_frame.data.u8[5]); + WakeUpTopUpReq = ((rx_frame.data.u8[6] & 0x04) >> 2); + WakeUpThermalReq = ((rx_frame.data.u8[6] & 0x08) >> 3); + WakeUpDchReq = ((rx_frame.data.u8[6] & 0x10) >> 4); + StateofHealth = (((rx_frame.data.u8[6] & 0x03) << 8) | rx_frame.data.u8[7]); + break; + case 0x308: // 190ms + EstimatedLossChg = (((rx_frame.data.u8[0] & 0x03) << 8) | rx_frame.data.u8[1]); + CoolingRequest = ((rx_frame.data.u8[6] & 0x04) >> 2); + EstimatedLossDch = (((rx_frame.data.u8[2] & 0x03) << 8) | rx_frame.data.u8[3]); + FanDutyRequest = (rx_frame.data.u8[4] & 0x7F); + ValveCtrlStat = ((rx_frame.data.u8[4] & 0x80) >> 7); + EstLossDchTgtSoC = (((rx_frame.data.u8[5] & 0x03) << 8) | rx_frame.data.u8[6]); + break; + case 0x424: // 280ms + HeatPowerGenChg = (rx_frame.data.u8[0] & 0x7F); + HeatPowerGenDch = (rx_frame.data.u8[1] & 0x7F); + WarmupRateChg = (rx_frame.data.u8[2] & 0x3F); + WarmupRateDch = (rx_frame.data.u8[3] & 0x3F); + CellVoltageMax = (((rx_frame.data.u8[4] & 0x1F) << 8) | rx_frame.data.u8[5]); + CellVoltageMin = (((rx_frame.data.u8[6] & 0x1F) << 8) | rx_frame.data.u8[7]); + break; + case 0x448: // 600ms + CellTempAverage = (rx_frame.data.u8[0] / 2) - 40; + CellTempColdest = (rx_frame.data.u8[1] / 2) - 40; + CellTempHottest = (rx_frame.data.u8[2] / 2) - 40; + HeaterCtrlStat = (rx_frame.data.u8[3] & 0x7F); + ThermalOvercheck = ((rx_frame.data.u8[3] & 0x80) >> 7); + InletCoolantTemp = rx_frame.data.u8[5]; + ClntPumpDiagStat_UB = ((rx_frame.data.u8[6] & 0x04) >> 2); + InletCoolantTemp_UB = ((rx_frame.data.u8[6] & 0x08) >> 3); + CoolantLevel = ((rx_frame.data.u8[6] & 0x10) >> 4); + ClntPumpDiagStat = ((rx_frame.data.u8[6] & 0x20) >> 5); + MILRequest = ((rx_frame.data.u8[6] & 0xC0) >> 6); + break; + case 0x464: // 800ms + EnergyAvailable = (((rx_frame.data.u8[0] & 0x07) << 8) | rx_frame.data.u8[1]); + EnergyUsableMax = (((rx_frame.data.u8[2] & 0x07) << 8) | rx_frame.data.u8[3]); + EnergyUsableMin = (((rx_frame.data.u8[4] & 0x07) << 8) | rx_frame.data.u8[5]); + TotalCapacity = (((rx_frame.data.u8[6] & 0x0F) << 8) | rx_frame.data.u8[7]); + break; + case 0x5A2: //Not periodically transferred + break; + case 0x656: //Not periodically transferred + break; + case 0x657: //Not periodically transferred + break; + case 0x6C8: //Not periodically transferred + break; + case 0x6C9: //Not periodically transferred + break; + case 0x6CA: //Not periodically transferred + break; + case 0x6CB: //Not periodically transferred + break; + case 0x7EC: //Not periodically transferred + break; + default: + break; + } +} + +void send_can_battery() { + unsigned long currentMillis = millis(); + // Send 50ms CAN Message + if (currentMillis - previousMillis50ms >= INTERVAL_50_MS) { + + previousMillis50ms = currentMillis; + + transmit_can(&RANGE_ROVER_18B, can_config.battery); + } +} + +void setup_battery(void) { // Performs one time setup at startup +#ifdef DEBUG_VIA_USB + Serial.println("Range Rover PHEV battery (L494 / L405) selected"); +#endif //DEBUG_VIA_USB + + datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; + datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV; + datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV; + datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV; +} + +#endif //RANGE_ROVER_PHEV_BATTERY diff --git a/Software/src/battery/RANGE-ROVER-PHEV-BATTERY.h b/Software/src/battery/RANGE-ROVER-PHEV-BATTERY.h new file mode 100644 index 00000000..c4cdcf07 --- /dev/null +++ b/Software/src/battery/RANGE-ROVER-PHEV-BATTERY.h @@ -0,0 +1,18 @@ +#ifndef RANGE_ROVER_PHEV_BATTERY_H +#define RANGE_ROVER_PHEV_BATTERY_H +#include +#include "../include.h" + +#define BATTERY_SELECTED + +/* Change the following to suit your battery */ +#define MAX_PACK_VOLTAGE_DV 5000 //TODO: Configure +#define MIN_PACK_VOLTAGE_DV 0 //TODO: Configure +#define MAX_CELL_VOLTAGE_MV 4250 //Battery is put into emergency stop if one cell goes over this value +#define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value +#define MAX_CELL_DEVIATION_MV 500 //TODO: Configure + +void setup_battery(void); +void transmit_can(CAN_frame* tx_frame, int interface); + +#endif diff --git a/Software/src/devboard/webserver/webserver.cpp b/Software/src/devboard/webserver/webserver.cpp index 09ebe9d4..8eab7631 100644 --- a/Software/src/devboard/webserver/webserver.cpp +++ b/Software/src/devboard/webserver/webserver.cpp @@ -558,6 +558,9 @@ String processor(const String& var) { #ifdef RJXZS_BMS content += "RJXZS BMS, DIY battery"; #endif // RJXZS_BMS +#ifdef RANGE_ROVER_PHEV_BATTERY + content += "Range Rover 13kWh PHEV battery (L494/L405)"; +#endif //RANGE_ROVER_PHEV_BATTERY #ifdef RENAULT_KANGOO_BATTERY content += "Renault Kangoo"; #endif // RENAULT_KANGOO_BATTERY From 3a9aefd58c2668e9165231fb46f156ab488ff0e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Wed, 13 Nov 2024 21:28:46 +0200 Subject: [PATCH 145/210] Fix endianess, simplify inverter brand read --- Software/src/inverter/BYD-CAN.cpp | 39 +++++++++++-------------------- 1 file changed, 13 insertions(+), 26 deletions(-) diff --git a/Software/src/inverter/BYD-CAN.cpp b/Software/src/inverter/BYD-CAN.cpp index 5cf999a3..ae0bb560 100644 --- a/Software/src/inverter/BYD-CAN.cpp +++ b/Software/src/inverter/BYD-CAN.cpp @@ -7,13 +7,6 @@ static unsigned long previousMillis2s = 0; // will store last time a 2s CAN Message was send static unsigned long previousMillis10s = 0; // will store last time a 10s CAN Message was send static unsigned long previousMillis60s = 0; // will store last time a 60s CAN Message was send -static uint8_t char1_151 = 0; -static uint8_t char2_151 = 0; -static uint8_t char3_151 = 0; -static uint8_t char4_151 = 0; -static uint8_t char5_151 = 0; -static uint8_t char6_151 = 0; -static uint8_t char7_151 = 0; CAN_frame BYD_250 = {.FD = false, .ext_ID = false, @@ -79,6 +72,7 @@ CAN_frame BYD_210 = {.FD = false, .ID = 0x210, .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; +static uint8_t inverter_name[7] = {0}; static int16_t temperature_average = 0; static uint16_t inverter_voltage = 0; static uint16_t inverter_SOC = 0; @@ -146,15 +140,12 @@ void update_values_can_inverter() { //This function maps all the values fetched BYD_210.data.u8[3] = (datalayer.battery.status.temperature_min_dC & 0x00FF); #ifdef DEBUG_VIA_USB - if (char1_151 != 0) { + if (inverter_name[0] != 0) { Serial.print("Detected inverter: "); - Serial.print((char)char1_151); - Serial.print((char)char2_151); - Serial.print((char)char3_151); - Serial.print((char)char4_151); - Serial.print((char)char5_151); - Serial.print((char)char6_151); - Serial.println((char)char7_151); + for (uint8_t i = 0; i < 7; i++) { + Serial.print((char)inverter_name[i]); + } + Serial.println(); } #endif } @@ -166,27 +157,23 @@ void receive_can_inverter(CAN_frame rx_frame) { if (rx_frame.data.u8[0] & 0x01) { //Battery requests identification send_intial_data(); } else { // We can identify what inverter type we are connected to - char1_151 = rx_frame.data.u8[1]; - char2_151 = rx_frame.data.u8[2]; - char3_151 = rx_frame.data.u8[3]; - char4_151 = rx_frame.data.u8[4]; - char5_151 = rx_frame.data.u8[5]; - char6_151 = rx_frame.data.u8[6]; - char7_151 = rx_frame.data.u8[7]; + for (uint8_t i = 0; i < 7; i++) { + inverter_name[i] = rx_frame.data.u8[i + 1]; + } } break; case 0x091: datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE; - inverter_voltage = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]) * 0.1; + inverter_voltage = ((rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]) * 0.1; break; case 0x0D1: datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE; - inverter_SOC = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]) * 0.1; + inverter_SOC = ((rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]) * 0.1; break; case 0x111: datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE; - inverter_timestamp = ((rx_frame.data.u8[3] << 24) | (rx_frame.data.u8[2] << 16) | (rx_frame.data.u8[1] << 8) | - rx_frame.data.u8[0]); + inverter_timestamp = ((rx_frame.data.u8[0] << 24) | (rx_frame.data.u8[1] << 16) | (rx_frame.data.u8[2] << 8) | + rx_frame.data.u8[3]); break; default: break; From 1e50394b0f9643f870653461ffaae8ec99f55d06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Wed, 13 Nov 2024 22:41:51 +0200 Subject: [PATCH 146/210] Centralize active_power_w writing --- Software/Software.ino | 6 ++++++ Software/src/battery/BMW-I3-BATTERY.cpp | 10 ---------- Software/src/battery/BMW-IX-BATTERY.cpp | 5 ----- Software/src/battery/BYD-ATTO-3-BATTERY.cpp | 6 ------ Software/src/battery/CELLPOWER-BMS.cpp | 3 --- Software/src/battery/IMIEV-CZERO-ION-BATTERY.cpp | 2 -- Software/src/battery/JAGUAR-IPACE-BATTERY.cpp | 4 ---- Software/src/battery/KIA-E-GMP-BATTERY.cpp | 5 ----- Software/src/battery/KIA-HYUNDAI-64-BATTERY.cpp | 4 ---- Software/src/battery/KIA-HYUNDAI-HYBRID-BATTERY.cpp | 4 ---- Software/src/battery/MG-5-BATTERY.cpp | 2 -- Software/src/battery/NISSAN-LEAF-BATTERY.cpp | 7 ------- Software/src/battery/PYLON-BATTERY.cpp | 3 --- Software/src/battery/RANGE-ROVER-PHEV-BATTERY.cpp | 3 --- Software/src/battery/RENAULT-KANGOO-BATTERY.cpp | 3 --- Software/src/battery/RENAULT-TWIZY.cpp | 3 --- Software/src/battery/RENAULT-ZOE-GEN1-BATTERY.cpp | 4 ---- Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.cpp | 3 --- Software/src/battery/RJXZS-BMS.cpp | 3 --- Software/src/battery/SANTA-FE-PHEV-BATTERY.cpp | 4 ---- Software/src/battery/TESLA-BATTERY.cpp | 4 ---- Software/src/battery/TEST-FAKE-BATTERY.cpp | 4 ---- Software/src/battery/VOLVO-SPA-BATTERY.cpp | 1 - 23 files changed, 6 insertions(+), 87 deletions(-) diff --git a/Software/Software.ino b/Software/Software.ino index c85c376d..babef4dd 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -854,6 +854,9 @@ void update_calculated_values() { 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; } + /* Calculate active power based on voltage and current*/ + datalayer.battery.status.active_power_W = + (datalayer.battery.status.current_dA * (datalayer.battery.status.voltage_dV / 100)); if (datalayer.battery.settings.soc_scaling_active) { /** SOC Scaling @@ -899,6 +902,9 @@ void update_calculated_values() { } #ifdef DOUBLE_BATTERY + /* Calculate active power based on voltage and current*/ + datalayer.battery2.status.active_power_W = + (datalayer.battery2.status.current_dA * (datalayer.battery2.status.voltage_dV / 100)); // Calculate the scaled remaining capacity in Wh if (datalayer.battery2.info.total_capacity_Wh > 0 && datalayer.battery2.status.real_soc > 0) { diff --git a/Software/src/battery/BMW-I3-BATTERY.cpp b/Software/src/battery/BMW-I3-BATTERY.cpp index c02a112e..a1b576d4 100644 --- a/Software/src/battery/BMW-I3-BATTERY.cpp +++ b/Software/src/battery/BMW-I3-BATTERY.cpp @@ -239,7 +239,6 @@ static int16_t battery_temperature_max = 0; static int16_t battery_temperature_min = 0; static int16_t battery_max_charge_amperage = 0; static int16_t battery_max_discharge_amperage = 0; -static int16_t battery_power = 0; static int16_t battery_current = 0; static uint8_t battery_status_error_isolation_external_Bordnetz = 0; static uint8_t battery_status_error_isolation_internal_Bordnetz = 0; @@ -308,7 +307,6 @@ static int16_t battery2_temperature_max = 0; static int16_t battery2_temperature_min = 0; static int16_t battery2_max_charge_amperage = 0; static int16_t battery2_max_discharge_amperage = 0; -static int16_t battery2_power = 0; static int16_t battery2_current = 0; static uint8_t battery2_status_error_isolation_external_Bordnetz = 0; static uint8_t battery2_status_error_isolation_internal_Bordnetz = 0; @@ -388,10 +386,6 @@ void update_values_battery2() { //This function maps all the values fetched via datalayer.battery2.status.max_charge_power_W = battery2_BEV_available_power_longterm_charge; } - battery2_power = (datalayer.battery2.status.current_dA * (datalayer.battery2.status.voltage_dV / 100)); - - datalayer.battery2.status.active_power_W = battery2_power; - datalayer.battery2.status.temperature_min_dC = battery2_temperature_min * 10; // Add a decimal datalayer.battery2.status.temperature_max_dC = battery2_temperature_max * 10; // Add a decimal @@ -456,10 +450,6 @@ void update_values_battery() { //This function maps all the values fetched via datalayer.battery.status.max_charge_power_W = battery_BEV_available_power_longterm_charge; - battery_power = (datalayer.battery.status.current_dA * (datalayer.battery.status.voltage_dV / 100)); - - datalayer.battery.status.active_power_W = battery_power; - datalayer.battery.status.temperature_min_dC = battery_temperature_min * 10; // Add a decimal datalayer.battery.status.temperature_max_dC = battery_temperature_max * 10; // Add a decimal diff --git a/Software/src/battery/BMW-IX-BATTERY.cpp b/Software/src/battery/BMW-IX-BATTERY.cpp index 2f316097..a994e27d 100644 --- a/Software/src/battery/BMW-IX-BATTERY.cpp +++ b/Software/src/battery/BMW-IX-BATTERY.cpp @@ -353,7 +353,6 @@ static unsigned long min_cell_voltage_lastchanged = 0; static unsigned long max_cell_voltage_lastchanged = 0; static unsigned min_cell_voltage_lastreceived = 0; static unsigned max_cell_voltage_lastreceived = 0; -static int16_t battery_power = 0; static uint32_t sme_uptime = 0; //Uses E4 C0 static int16_t allowable_charge_amps = 0; //E5 62 static int16_t allowable_discharge_amps = 0; //E5 62 @@ -454,10 +453,6 @@ void update_values_battery() { //This function maps all the values fetched via datalayer.battery.status.max_charge_power_W = MAX_CHARGE_POWER_ALLOWED_W; } - battery_power = (datalayer.battery.status.current_dA * (datalayer.battery.status.voltage_dV / 100)); - - datalayer.battery.status.active_power_W = battery_power; - datalayer.battery.status.temperature_min_dC = min_battery_temperature; datalayer.battery.status.temperature_max_dC = max_battery_temperature; diff --git a/Software/src/battery/BYD-ATTO-3-BATTERY.cpp b/Software/src/battery/BYD-ATTO-3-BATTERY.cpp index f20f0c1f..9a1e1f9b 100644 --- a/Software/src/battery/BYD-ATTO-3-BATTERY.cpp +++ b/Software/src/battery/BYD-ATTO-3-BATTERY.cpp @@ -122,9 +122,6 @@ void update_values_battery() { //This function maps all the values fetched via datalayer.battery.status.max_charge_power_W = 10000; //TODO: Map from CAN later on - datalayer.battery.status.active_power_W = - (datalayer.battery.status.current_dA * (datalayer.battery.status.voltage_dV / 100)); - datalayer.battery.status.cell_max_voltage_mV = BMS_highest_cell_voltage_mV; datalayer.battery.status.cell_min_voltage_mV = BMS_lowest_cell_voltage_mV; @@ -445,9 +442,6 @@ void update_values_battery2() { //This function maps all the values fetched via datalayer.battery2.status.max_charge_power_W = 10000; //TODO: Map from CAN later on - datalayer.battery2.status.active_power_W = - (datalayer.battery2.status.current_dA * (datalayer.battery2.status.voltage_dV / 100)); - datalayer.battery2.status.cell_max_voltage_mV = BMS2_highest_cell_voltage_mV; datalayer.battery2.status.cell_min_voltage_mV = BMS2_lowest_cell_voltage_mV; diff --git a/Software/src/battery/CELLPOWER-BMS.cpp b/Software/src/battery/CELLPOWER-BMS.cpp index 8bd5a1b5..ec448312 100644 --- a/Software/src/battery/CELLPOWER-BMS.cpp +++ b/Software/src/battery/CELLPOWER-BMS.cpp @@ -124,9 +124,6 @@ void update_values_battery() { datalayer.battery.status.current_dA = battery_pack_current_dA; - datalayer.battery.status.active_power_W = //Power in watts, Negative = charging batt - ((datalayer.battery.status.voltage_dV * datalayer.battery.status.current_dA) / 100); - datalayer.battery.status.max_charge_power_W = 5000; //TODO, is this available via CAN? datalayer.battery.status.max_discharge_power_W = 5000; //TODO, is this available via CAN? diff --git a/Software/src/battery/IMIEV-CZERO-ION-BATTERY.cpp b/Software/src/battery/IMIEV-CZERO-ION-BATTERY.cpp index c4094e3f..43ef0b07 100644 --- a/Software/src/battery/IMIEV-CZERO-ION-BATTERY.cpp +++ b/Software/src/battery/IMIEV-CZERO-ION-BATTERY.cpp @@ -51,8 +51,6 @@ void update_values_battery() { //This function maps all the values fetched via datalayer.battery.status.max_discharge_power_W = 10000; // 10kW //TODO: Fix when CAN is decoded - datalayer.battery.status.active_power_W = BMU_Power; //TODO: Scaling? - static int n = sizeof(cell_voltages) / sizeof(cell_voltages[0]); max_volt_cel = cell_voltages[0]; // Initialize max with the first element of the array for (int i = 1; i < n; i++) { diff --git a/Software/src/battery/JAGUAR-IPACE-BATTERY.cpp b/Software/src/battery/JAGUAR-IPACE-BATTERY.cpp index e5f3fe9b..a083a8ce 100644 --- a/Software/src/battery/JAGUAR-IPACE-BATTERY.cpp +++ b/Software/src/battery/JAGUAR-IPACE-BATTERY.cpp @@ -81,10 +81,6 @@ void update_values_battery() { datalayer.battery.status.cell_min_voltage_mV = HVBattCellVoltageMinMv; - //Power in watts, Negative = charging batt - datalayer.battery.status.active_power_W = - ((datalayer.battery.status.voltage_dV * datalayer.battery.status.current_dA) / 100); - datalayer.battery.status.temperature_min_dC = HVBattCellTempColdest * 10; // C to dC datalayer.battery.status.temperature_max_dC = HVBattCellTempHottest * 10; // C to dC diff --git a/Software/src/battery/KIA-E-GMP-BATTERY.cpp b/Software/src/battery/KIA-E-GMP-BATTERY.cpp index bb0c174d..19db7992 100644 --- a/Software/src/battery/KIA-E-GMP-BATTERY.cpp +++ b/Software/src/battery/KIA-E-GMP-BATTERY.cpp @@ -38,7 +38,6 @@ static uint16_t CellVoltMin_mV = 3700; static uint16_t batteryVoltage = 6700; static int16_t leadAcidBatteryVoltage = 120; static int16_t batteryAmps = 0; -static int16_t powerWatt = 0; static int16_t temperatureMax = 0; static int16_t temperatureMin = 0; static int16_t allowedDischargePower = 0; @@ -660,10 +659,6 @@ void update_values_battery() { //This function maps all the values fetched via //The allowed discharge power is not available. We hardcode this value for now datalayer.battery.status.max_discharge_power_W = MAXDISCHARGEPOWERALLOWED; - powerWatt = ((batteryVoltage * batteryAmps) / 100); - - datalayer.battery.status.active_power_W = powerWatt; //Power in watts, Negative = charging batt - datalayer.battery.status.temperature_min_dC = (int8_t)temperatureMin * 10; //Increase decimals, 17C -> 17.0C datalayer.battery.status.temperature_max_dC = (int8_t)temperatureMax * 10; //Increase decimals, 18C -> 18.0C diff --git a/Software/src/battery/KIA-HYUNDAI-64-BATTERY.cpp b/Software/src/battery/KIA-HYUNDAI-64-BATTERY.cpp index a99be631..b3f935a6 100644 --- a/Software/src/battery/KIA-HYUNDAI-64-BATTERY.cpp +++ b/Software/src/battery/KIA-HYUNDAI-64-BATTERY.cpp @@ -124,10 +124,6 @@ void update_values_battery() { //This function maps all the values fetched via datalayer.battery.status.max_discharge_power_W = allowedDischargePower * 10; - //Power in watts, Negative = charging batt - datalayer.battery.status.active_power_W = - ((datalayer.battery.status.voltage_dV * datalayer.battery.status.current_dA) / 100); - datalayer.battery.status.temperature_min_dC = (int8_t)temperatureMin * 10; //Increase decimals, 17C -> 17.0C datalayer.battery.status.temperature_max_dC = (int8_t)temperatureMax * 10; //Increase decimals, 18C -> 18.0C diff --git a/Software/src/battery/KIA-HYUNDAI-HYBRID-BATTERY.cpp b/Software/src/battery/KIA-HYUNDAI-HYBRID-BATTERY.cpp index f2597d3d..49a8dae6 100644 --- a/Software/src/battery/KIA-HYUNDAI-HYBRID-BATTERY.cpp +++ b/Software/src/battery/KIA-HYUNDAI-HYBRID-BATTERY.cpp @@ -68,10 +68,6 @@ void update_values_battery() { //This function maps all the values fetched via datalayer.battery.status.max_charge_power_W = available_charge_power * 10; - //Power in watts, Negative = charging batt - datalayer.battery.status.active_power_W = - ((datalayer.battery.status.voltage_dV * datalayer.battery.status.current_dA) / 100); - datalayer.battery.status.temperature_min_dC = (int16_t)(battery_module_min_temperature * 10); datalayer.battery.status.temperature_max_dC = (int16_t)(battery_module_max_temperature * 10); diff --git a/Software/src/battery/MG-5-BATTERY.cpp b/Software/src/battery/MG-5-BATTERY.cpp index b181edaf..8da1b9a2 100644 --- a/Software/src/battery/MG-5-BATTERY.cpp +++ b/Software/src/battery/MG-5-BATTERY.cpp @@ -39,8 +39,6 @@ void update_values_battery() { //This function maps all the values fetched via datalayer.battery.status.max_charge_power_W; - datalayer.battery.status.active_power_W; - datalayer.battery.status.temperature_min_dC; datalayer.battery.status.temperature_max_dC; diff --git a/Software/src/battery/NISSAN-LEAF-BATTERY.cpp b/Software/src/battery/NISSAN-LEAF-BATTERY.cpp index 67bb21f9..f32a9ba6 100644 --- a/Software/src/battery/NISSAN-LEAF-BATTERY.cpp +++ b/Software/src/battery/NISSAN-LEAF-BATTERY.cpp @@ -197,9 +197,6 @@ void update_values_battery() { /* This function maps all the values fetched via datalayer.battery.status.remaining_capacity_Wh = battery_Wh_Remaining; - datalayer.battery.status.active_power_W = ((battery_Total_Voltage2 * battery_Current2) / - 4); //P = U * I (Both values are 0.5 per bit so the math is non-intuitive) - //Update temperature readings. Method depends on which generation LEAF battery is used if (LEAF_battery_Type == ZE0_BATTERY) { //Since we only have average value, send the minimum as -1.0 degrees below average @@ -369,10 +366,6 @@ void update_values_battery2() { // Handle the values coming in from battery #2 datalayer.battery2.status.remaining_capacity_Wh = battery2_Wh_Remaining; - datalayer.battery2.status.active_power_W = - ((battery2_Total_Voltage2 * battery2_Current2) / - 4); //P = U * I (Both values are 0.5 per bit so the math is non-intuitive) - //Update temperature readings. Method depends on which generation LEAF battery is used if (LEAF_battery2_Type == ZE0_BATTERY) { //Since we only have average value, send the minimum as -1.0 degrees below average diff --git a/Software/src/battery/PYLON-BATTERY.cpp b/Software/src/battery/PYLON-BATTERY.cpp index 068a12b7..16906722 100644 --- a/Software/src/battery/PYLON-BATTERY.cpp +++ b/Software/src/battery/PYLON-BATTERY.cpp @@ -60,9 +60,6 @@ void update_values_battery() { datalayer.battery.status.current_dA = current_dA; //value is *10 (150 = 15.0) , invert the sign - datalayer.battery.status.active_power_W = //Power in watts, Negative = charging batt - ((datalayer.battery.status.voltage_dV * datalayer.battery.status.current_dA) / 100); - datalayer.battery.status.max_charge_power_W = (max_charge_current * (voltage_dV / 10)); datalayer.battery.status.max_discharge_power_W = (-max_discharge_current * (voltage_dV / 10)); diff --git a/Software/src/battery/RANGE-ROVER-PHEV-BATTERY.cpp b/Software/src/battery/RANGE-ROVER-PHEV-BATTERY.cpp index d530c2d5..4f6eb16c 100644 --- a/Software/src/battery/RANGE-ROVER-PHEV-BATTERY.cpp +++ b/Software/src/battery/RANGE-ROVER-PHEV-BATTERY.cpp @@ -160,9 +160,6 @@ void update_values_battery() { datalayer.battery.status.current_dA = (CurrentExt * 0.025) - 209715; - datalayer.battery.status.active_power_W = //Power in watts, Negative = charging batt - ((datalayer.battery.status.voltage_dV * datalayer.battery.status.current_dA) / 100); - datalayer.battery.status.max_charge_power_W = (ChargeContPwrLmt * 10) - 6550; datalayer.battery.status.max_discharge_power_W = (DischargeContPwrLmt * 10) - 6550; diff --git a/Software/src/battery/RENAULT-KANGOO-BATTERY.cpp b/Software/src/battery/RENAULT-KANGOO-BATTERY.cpp index bd8b1665..00b4e12b 100644 --- a/Software/src/battery/RENAULT-KANGOO-BATTERY.cpp +++ b/Software/src/battery/RENAULT-KANGOO-BATTERY.cpp @@ -95,9 +95,6 @@ void update_values_battery() { //This function maps all the values fetched via //The above value is 0 on some packs. We instead hardcode this now. datalayer.battery.status.max_charge_power_W = MAX_CHARGE_POWER_W; - datalayer.battery.status.active_power_W = - ((datalayer.battery.status.voltage_dV * datalayer.battery.status.current_dA) / 100); - datalayer.battery.status.temperature_min_dC = (LB_MIN_TEMPERATURE * 10); datalayer.battery.status.temperature_max_dC = (LB_MAX_TEMPERATURE * 10); diff --git a/Software/src/battery/RENAULT-TWIZY.cpp b/Software/src/battery/RENAULT-TWIZY.cpp index 98f27a83..69a83316 100644 --- a/Software/src/battery/RENAULT-TWIZY.cpp +++ b/Software/src/battery/RENAULT-TWIZY.cpp @@ -46,9 +46,6 @@ void update_values_battery() { datalayer.battery.status.current_dA = current_dA; //value is *10 (150 = 15.0) datalayer.battery.status.remaining_capacity_Wh = remaining_capacity_Wh; - datalayer.battery.status.active_power_W = //Power in watts, Negative = charging batt - ((datalayer.battery.status.voltage_dV * datalayer.battery.status.current_dA) / 100); - // The twizy provides two values: one for the maximum charge provided by the on-board charger // and one for the maximum charge during recuperation. // For now we use the lower of the two (usually the charger one) diff --git a/Software/src/battery/RENAULT-ZOE-GEN1-BATTERY.cpp b/Software/src/battery/RENAULT-ZOE-GEN1-BATTERY.cpp index 917e7892..1044729f 100644 --- a/Software/src/battery/RENAULT-ZOE-GEN1-BATTERY.cpp +++ b/Software/src/battery/RENAULT-ZOE-GEN1-BATTERY.cpp @@ -104,10 +104,6 @@ void update_values_battery() { //This function maps all the values fetched via datalayer.battery.status.max_charge_power_W = 50; } - //Power in watts, Negative = charging batt - datalayer.battery.status.active_power_W = - ((datalayer.battery.status.voltage_dV * datalayer.battery.status.current_dA) / 100); - int16_t temperatures[] = {cell_1_temperature_polled, cell_2_temperature_polled, cell_3_temperature_polled, cell_4_temperature_polled, cell_5_temperature_polled, cell_6_temperature_polled, cell_7_temperature_polled, cell_8_temperature_polled, cell_9_temperature_polled, diff --git a/Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.cpp b/Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.cpp index 38c875b1..50b09d2e 100644 --- a/Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.cpp +++ b/Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.cpp @@ -166,9 +166,6 @@ void update_values_battery() { //This function maps all the values fetched via datalayer.battery.status.max_charge_power_W = battery_max_generated * 10; - datalayer.battery.status.active_power_W = - (datalayer.battery.status.current_dA * (datalayer.battery.status.voltage_dV / 100)); - datalayer.battery.status.temperature_min_dC = ((battery_min_temp - 640) * 0.625); datalayer.battery.status.temperature_max_dC = ((battery_max_temp - 640) * 0.625); diff --git a/Software/src/battery/RJXZS-BMS.cpp b/Software/src/battery/RJXZS-BMS.cpp index ed23fb40..b75934c8 100644 --- a/Software/src/battery/RJXZS-BMS.cpp +++ b/Software/src/battery/RJXZS-BMS.cpp @@ -98,9 +98,6 @@ void update_values_battery() { datalayer.battery.status.current_dA = total_current; - datalayer.battery.status.active_power_W = //Power in watts, Negative = charging batt - ((datalayer.battery.status.voltage_dV * datalayer.battery.status.current_dA) / 100); - // Charge power is set in .h file if (datalayer.battery.status.real_soc > 9900) { datalayer.battery.status.max_charge_power_W = MAX_CHARGE_POWER_WHEN_TOPBALANCING_W; diff --git a/Software/src/battery/SANTA-FE-PHEV-BATTERY.cpp b/Software/src/battery/SANTA-FE-PHEV-BATTERY.cpp index dbe69b55..36b1a649 100644 --- a/Software/src/battery/SANTA-FE-PHEV-BATTERY.cpp +++ b/Software/src/battery/SANTA-FE-PHEV-BATTERY.cpp @@ -85,10 +85,6 @@ void update_values_battery() { //This function maps all the values fetched via datalayer.battery.status.max_charge_power_W = allowedChargePower * 10; - //Power in watts, Negative = charging batt - datalayer.battery.status.active_power_W = - ((datalayer.battery.status.voltage_dV * datalayer.battery.status.current_dA) / 100); - datalayer.battery.status.cell_max_voltage_mV = CellVoltMax_mV; datalayer.battery.status.cell_min_voltage_mV = CellVoltMin_mV; diff --git a/Software/src/battery/TESLA-BATTERY.cpp b/Software/src/battery/TESLA-BATTERY.cpp index 3eac62cc..eaa01e1b 100644 --- a/Software/src/battery/TESLA-BATTERY.cpp +++ b/Software/src/battery/TESLA-BATTERY.cpp @@ -297,8 +297,6 @@ void update_values_battery() { //This function maps all the values fetched via datalayer.battery.status.max_charge_power_W = MAXCHARGEPOWERALLOWED; } - datalayer.battery.status.active_power_W = ((battery_volts / 10) * battery_amps); - datalayer.battery.status.temperature_min_dC = battery_min_temp; datalayer.battery.status.temperature_max_dC = battery_max_temp; @@ -857,8 +855,6 @@ void update_values_battery2() { //This function maps all the values fetched via datalayer.battery2.status.max_charge_power_W = MAXCHARGEPOWERALLOWED; } - datalayer.battery2.status.active_power_W = ((battery2_volts / 10) * battery2_amps); - datalayer.battery2.status.temperature_min_dC = battery2_min_temp; datalayer.battery2.status.temperature_max_dC = battery2_max_temp; diff --git a/Software/src/battery/TEST-FAKE-BATTERY.cpp b/Software/src/battery/TEST-FAKE-BATTERY.cpp index 423654aa..fdc35294 100644 --- a/Software/src/battery/TEST-FAKE-BATTERY.cpp +++ b/Software/src/battery/TEST-FAKE-BATTERY.cpp @@ -40,8 +40,6 @@ void update_values_battery() { /* This function puts fake values onto the parame datalayer.battery.status.cell_min_voltage_mV = 3500; - datalayer.battery.status.active_power_W = 0; // 0W - datalayer.battery.status.temperature_min_dC = 50; // 5.0*C datalayer.battery.status.temperature_max_dC = 60; // 6.0*C @@ -95,8 +93,6 @@ void update_values_battery2() { // Handle the values coming in from battery #2 datalayer.battery2.status.cell_min_voltage_mV = 3500; - datalayer.battery2.status.active_power_W = 0; // 0W - datalayer.battery2.status.temperature_min_dC = 50; // 5.0*C datalayer.battery2.status.temperature_max_dC = 60; // 6.0*C diff --git a/Software/src/battery/VOLVO-SPA-BATTERY.cpp b/Software/src/battery/VOLVO-SPA-BATTERY.cpp index d321ad38..efdbc0a0 100644 --- a/Software/src/battery/VOLVO-SPA-BATTERY.cpp +++ b/Software/src/battery/VOLVO-SPA-BATTERY.cpp @@ -83,7 +83,6 @@ void update_values_battery() { //This function maps all the values fetched via //datalayer.battery.status.max_discharge_power_W = HvBattPwrLimDchaSoft * 1000; // Use power limit reported from BMS, not trusted ATM datalayer.battery.status.max_discharge_power_W = 30000; datalayer.battery.status.max_charge_power_W = 30000; - datalayer.battery.status.active_power_W = (BATT_U)*BATT_I; datalayer.battery.status.temperature_min_dC = BATT_T_MIN; datalayer.battery.status.temperature_max_dC = BATT_T_MAX; From 1398c6213b36cdfc7f5441e94fa157979d2a3cd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Wed, 13 Nov 2024 23:07:05 +0200 Subject: [PATCH 147/210] Update datalayer comment --- Software/src/datalayer/datalayer.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Software/src/datalayer/datalayer.h b/Software/src/datalayer/datalayer.h index 7e1880af..5b198158 100644 --- a/Software/src/datalayer/datalayer.h +++ b/Software/src/datalayer/datalayer.h @@ -31,7 +31,7 @@ typedef struct { typedef struct { /** int32_t */ - /** Instantaneous battery power in Watts */ + /** Instantaneous battery power in Watts. Calculated based on voltage_dV and current_dA */ /* Positive value = Battery Charging */ /* Negative value = Battery Discharging */ int32_t active_power_W; From ca83af81dc943a58c8d84955582fd56371d0028d Mon Sep 17 00:00:00 2001 From: wjcloudy <56305354+wjcloudy@users.noreply.github.com> Date: Thu, 14 Nov 2024 09:09:26 +0000 Subject: [PATCH 148/210] Stale value improvement Stale values still overly sensitive in idle conditions. Now requires both min/max to go stale (ie: the whole frame) --- Software/src/battery/BMW-IX-BATTERY.cpp | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/Software/src/battery/BMW-IX-BATTERY.cpp b/Software/src/battery/BMW-IX-BATTERY.cpp index 2f316097..245fd66c 100644 --- a/Software/src/battery/BMW-IX-BATTERY.cpp +++ b/Software/src/battery/BMW-IX-BATTERY.cpp @@ -462,17 +462,13 @@ void update_values_battery() { //This function maps all the values fetched via datalayer.battery.status.temperature_max_dC = max_battery_temperature; - if (isStale(min_cell_voltage, datalayer.battery.status.cell_min_voltage_mV, min_cell_voltage_lastchanged)) { + //Check stale values. As values dont change much during idle only consider stale if both parts of this message freeze. + if (isStale(min_cell_voltage, datalayer.battery.status.cell_min_voltage_mV, min_cell_voltage_lastchanged) && isStale(max_cell_voltage, datalayer.battery.status.cell_max_voltage_mV, max_cell_voltage_lastchanged)) { datalayer.battery.status.cell_min_voltage_mV = 9999; //Stale values force stop - set_event(EVENT_CAN_RX_FAILURE, 0); - } else { - datalayer.battery.status.cell_min_voltage_mV = min_cell_voltage; //Value is alive - } - - if (isStale(max_cell_voltage, datalayer.battery.status.cell_max_voltage_mV, max_cell_voltage_lastchanged)) { datalayer.battery.status.cell_max_voltage_mV = 9999; //Stale values force stop set_event(EVENT_CAN_RX_FAILURE, 0); } else { + datalayer.battery.status.cell_min_voltage_mV = min_cell_voltage; //Value is alive datalayer.battery.status.cell_max_voltage_mV = max_cell_voltage; //Value is alive } @@ -677,7 +673,7 @@ void receive_can_battery(CAN_frame rx_frame) { #ifdef DEBUG_VIA_USB Serial.println("Cell MinMax Qualifier Invalid - Requesting BMS Reset"); #endif - set_event(EVENT_SOC_UNAVAILABLE, (millis())); + //set_event(EVENT_BATTERY_VALUE_UNAVAILABLE, (millis())); //Eventually need new Info level event type transmit_can(&BMWiX_6F4_REQUEST_HARD_RESET, can_config.battery); } else { //Only ingest values if they are not the 10V Error state min_cell_voltage = (rx_frame.data.u8[6] << 8 | rx_frame.data.u8[7]); From 578cb07263488828fe582388c95373ee59476f08 Mon Sep 17 00:00:00 2001 From: wjcloudy <56305354+wjcloudy@users.noreply.github.com> Date: Thu, 14 Nov 2024 09:51:24 +0000 Subject: [PATCH 149/210] Update BMW-IX-BATTERY.cpp Code formatting --- Software/src/battery/BMW-IX-BATTERY.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Software/src/battery/BMW-IX-BATTERY.cpp b/Software/src/battery/BMW-IX-BATTERY.cpp index 245fd66c..ebc7e2ab 100644 --- a/Software/src/battery/BMW-IX-BATTERY.cpp +++ b/Software/src/battery/BMW-IX-BATTERY.cpp @@ -463,7 +463,8 @@ void update_values_battery() { //This function maps all the values fetched via datalayer.battery.status.temperature_max_dC = max_battery_temperature; //Check stale values. As values dont change much during idle only consider stale if both parts of this message freeze. - if (isStale(min_cell_voltage, datalayer.battery.status.cell_min_voltage_mV, min_cell_voltage_lastchanged) && isStale(max_cell_voltage, datalayer.battery.status.cell_max_voltage_mV, max_cell_voltage_lastchanged)) { + if (isStale(min_cell_voltage, datalayer.battery.status.cell_min_voltage_mV, min_cell_voltage_lastchanged) && + isStale(max_cell_voltage, datalayer.battery.status.cell_max_voltage_mV, max_cell_voltage_lastchanged)) { datalayer.battery.status.cell_min_voltage_mV = 9999; //Stale values force stop datalayer.battery.status.cell_max_voltage_mV = 9999; //Stale values force stop set_event(EVENT_CAN_RX_FAILURE, 0); From f83b714824ef555d96278d5b6f6cf6239912c823 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Thu, 14 Nov 2024 13:44:22 +0200 Subject: [PATCH 150/210] Add Schneider CAN inverter protocol --- .github/workflows/compile-all-inverters.yml | 1 + Software/USER_SETTINGS.h | 1 + Software/src/devboard/webserver/webserver.cpp | 3 + Software/src/inverter/INVERTERS.h | 4 + Software/src/inverter/SCHNEIDER-CAN.cpp | 300 ++++++++++++++++++ Software/src/inverter/SCHNEIDER-CAN.h | 32 ++ 6 files changed, 341 insertions(+) create mode 100644 Software/src/inverter/SCHNEIDER-CAN.cpp create mode 100644 Software/src/inverter/SCHNEIDER-CAN.h diff --git a/.github/workflows/compile-all-inverters.yml b/.github/workflows/compile-all-inverters.yml index 85beb384..a0bed871 100644 --- a/.github/workflows/compile-all-inverters.yml +++ b/.github/workflows/compile-all-inverters.yml @@ -47,6 +47,7 @@ jobs: - BYD_MODBUS - FOXESS_CAN - PYLON_CAN + - SCHNEIDER_CAN - SMA_CAN - SMA_TRIPOWER_CAN - SOFAR_CAN diff --git a/Software/USER_SETTINGS.h b/Software/USER_SETTINGS.h index c6351a12..cbd51f4c 100644 --- a/Software/USER_SETTINGS.h +++ b/Software/USER_SETTINGS.h @@ -43,6 +43,7 @@ //#define FOXESS_CAN //Enable this line to emulate a "HV2600/ECS4100 battery" over CAN bus //#define PYLON_LV_CAN //Enable this line to emulate a "48V Pylontech battery" over CAN bus //#define PYLON_CAN //Enable this line to emulate a "High Voltage Pylontech battery" over CAN bus +//#define SCHNEIDER_CAN //Enable this line to emulate a "Schneider Version 2: SE BMS" over CAN bus //#define SMA_CAN //Enable this line to emulate a "BYD Battery-Box H 8.9kWh, 7 mod" over CAN bus //#define SMA_TRIPOWER_CAN //Enable this line to emulate a "SMA Home Storage battery" over CAN bus //#define SOFAR_CAN //Enable this line to emulate a "Sofar Energy Storage Inverter High Voltage BMS General Protocol (Extended Frame)" over CAN bus diff --git a/Software/src/devboard/webserver/webserver.cpp b/Software/src/devboard/webserver/webserver.cpp index 8eab7631..39051ed6 100644 --- a/Software/src/devboard/webserver/webserver.cpp +++ b/Software/src/devboard/webserver/webserver.cpp @@ -501,6 +501,9 @@ String processor(const String& var) { #ifdef PYLON_LV_CAN content += "Pylontech LV battery over CAN bus"; #endif // PYLON_LV_CAN +#ifdef SCHNEIDER_CAN + content += "Schneider V2 BMS protocol over CAN bus"; +#endif // SCHNEIDER_CAN #ifdef SERIAL_LINK_TRANSMITTER content += "Serial link to another LilyGo board"; #endif // SERIAL_LINK_TRANSMITTER diff --git a/Software/src/inverter/INVERTERS.h b/Software/src/inverter/INVERTERS.h index d1922b82..18089fe8 100644 --- a/Software/src/inverter/INVERTERS.h +++ b/Software/src/inverter/INVERTERS.h @@ -31,6 +31,10 @@ #include "PYLON-LV-CAN.h" #endif +#ifdef SCHNEIDER_CAN +#include "SCHNEIDER-CAN.h" +#endif + #ifdef SMA_CAN #include "SMA-CAN.h" #endif diff --git a/Software/src/inverter/SCHNEIDER-CAN.cpp b/Software/src/inverter/SCHNEIDER-CAN.cpp new file mode 100644 index 00000000..c13d3e87 --- /dev/null +++ b/Software/src/inverter/SCHNEIDER-CAN.cpp @@ -0,0 +1,300 @@ +#include "../include.h" +#ifdef SCHNEIDER_CAN +#include "../datalayer/datalayer.h" +#include "SCHNEIDER-CAN.h" + +/* Version 2: SE BMS Communication Protocol +Protocol: CAN 2.0 Specification +Frame: Extended CAN Bus Frame (29 bit identifier) +Bitrate: 500 kbps +Endian: Big Endian (MSB, most significant byte of a value received first)*/ + +/* TODOs +- Figure out how to reply with protocol version in 0x320 +- Figure out what to set Battery Manufacturer ID to in 0x330 +- Figure out what to set Battery Model ID in 0x330 + - We will need CAN logs from existing battery OR contact Schneider for one free number +*/ + +/* Do not change code below unless you are sure what you are doing */ +static unsigned long previousMillis10s = 0; // will store last time a 10s CAN Message was send +static unsigned long previousMillis2s = 0; // will store last time a 2s CAN Message was send +static unsigned long previousMillis500ms = 0; // will store last time a 500ms CAN Message was send + +CAN_frame SE_320 = {.FD = false, //SE BMS Protocol Version + .ext_ID = true, + .DLC = 2, + .ID = 0x320, + .data = {0x00, 0x02}}; //TODO: How do we reply with Protocol Version: 0x0002 ? +CAN_frame SE_321 = {.FD = false, + .ext_ID = true, + .DLC = 8, + .ID = 0x321, + .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame SE_322 = {.FD = false, + .ext_ID = true, + .DLC = 8, + .ID = 0x322, + .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame SE_323 = {.FD = false, + .ext_ID = true, + .DLC = 8, + .ID = 0x323, + .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame SE_324 = {.FD = false, .ext_ID = true, .DLC = 4, .ID = 0x324, .data = {0x00, 0x00, 0x00, 0x00}}; +CAN_frame SE_325 = {.FD = false, .ext_ID = true, .DLC = 6, .ID = 0x325, .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame SE_326 = {.FD = false, + .ext_ID = true, + .DLC = 8, + .ID = 0x326, + .data = {0x00, STATE_STARTING, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame SE_327 = {.FD = false, + .ext_ID = true, + .DLC = 8, + .ID = 0x327, + .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame SE_328 = {.FD = false, + .ext_ID = true, + .DLC = 8, + .ID = 0x328, + .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame SE_330 = {.FD = false, + .ext_ID = true, + .DLC = 8, + .ID = 0x330, + .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame SE_331 = {.FD = false, + .ext_ID = true, + .DLC = 8, + .ID = 0x331, + .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame SE_332 = {.FD = false, + .ext_ID = true, + .DLC = 8, + .ID = 0x332, + .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame SE_333 = {.FD = false, + .ext_ID = true, + .DLC = 8, + .ID = 0x333, + .data = {0x53, 0x45, 0x42, 0x4D, 0x53, 0x00, 0x00, 0x00}}; //SEBMS + +static int16_t temperature_average = 0; +static uint16_t remaining_capacity_ah = 0; +static uint16_t fully_charged_capacity_ah = 0; +static uint16_t commands = 0; +static uint16_t warnings = 0; +static uint16_t faults = 0; +static uint16_t state = 0; + +void update_values_can_inverter() { //This function maps all the values fetched from battery CAN to the correct CAN messages + + /* Calculate temperature */ + temperature_average = + ((datalayer.battery.status.temperature_max_dC + datalayer.battery.status.temperature_min_dC) / 2); + + /* Calculate capacity, Amp hours(Ah) = Watt hours (Wh) / Voltage (V)*/ + if (datalayer.battery.status.voltage_dV > 10) { // Only update value when we have voltage available to avoid div0 + remaining_capacity_ah = + ((datalayer.battery.status.reported_remaining_capacity_Wh / datalayer.battery.status.voltage_dV) * 100); + fully_charged_capacity_ah = + ((datalayer.battery.info.total_capacity_Wh / datalayer.battery.status.voltage_dV) * 100); + } + /* Set active commands/warnings/faults/state*/ + if (datalayer.battery.status.bms_status == FAULT) { + state = STATE_FAULTED; + //TODO: Map warnings and faults incase an event is set. Low prio, but nice to have + commands = COMMAND_STOP; + } else { //Battery-Emulator running + state = STATE_ONLINE; + warnings = 0; + faults = 0; + if (datalayer.battery.status.reported_soc == 10000) { + //Battery full. Only allow discharge + commands = COMMAND_ONLY_DISCHARGE_ALLOWED; + } else if (datalayer.battery.status.reported_soc == 0) { + //Battery empty. Only allow charge + commands = COMMAND_ONLY_CHARGE_ALLOWED; + } else { //SOC is somewhere between 0.1% and 99.9%. Allow both charge and discharge + commands = COMMAND_CHARGE_AND_DISCHARGE_ALLOWED; + } + } + + //Map values to CAN messages + //Max charge voltage+2 (eg 10000.00V = 1000000 , 32bits long) + SE_321.data.u8[0] = ((datalayer.battery.info.max_design_voltage_dV * 10) >> 24); + SE_321.data.u8[1] = (((datalayer.battery.info.max_design_voltage_dV * 10) & 0x00FF0000) >> 16); + SE_321.data.u8[2] = (((datalayer.battery.info.max_design_voltage_dV * 10) & 0x0000FF00) >> 8); + SE_321.data.u8[3] = ((datalayer.battery.info.max_design_voltage_dV * 10) & 0x000000FF); + //Minimum discharge voltage+2 (eg 10000.00V = 1000000 , 32bits long) + SE_321.data.u8[4] = ((datalayer.battery.info.min_design_voltage_dV * 10) >> 24); + SE_321.data.u8[5] = (((datalayer.battery.info.min_design_voltage_dV * 10) & 0x00FF0000) >> 16); + SE_321.data.u8[6] = (((datalayer.battery.info.min_design_voltage_dV * 10) & 0x0000FF00) >> 8); + SE_321.data.u8[7] = ((datalayer.battery.info.min_design_voltage_dV * 10) & 0x000000FF); + + //Maximum charge current+2 (eg 10000.00A = 1000000) TODO: Note s32 bit, which direction? + SE_322.data.u8[0] = ((datalayer.battery.status.max_charge_current_dA * 10) >> 24); + SE_322.data.u8[1] = (((datalayer.battery.status.max_charge_current_dA * 10) & 0x00FF0000) >> 16); + SE_322.data.u8[2] = (((datalayer.battery.status.max_charge_current_dA * 10) & 0x0000FF00) >> 8); + SE_322.data.u8[3] = ((datalayer.battery.status.max_charge_current_dA * 10) & 0x000000FF); + //Maximum discharge current+2 (eg 10000.00A = 1000000) TODO: Note s32 bit, which direction? + SE_322.data.u8[4] = ((datalayer.battery.status.max_discharge_current_dA * 10) >> 24); + SE_322.data.u8[5] = (((datalayer.battery.status.max_discharge_current_dA * 10) & 0x00FF0000) >> 16); + SE_322.data.u8[6] = (((datalayer.battery.status.max_discharge_current_dA * 10) & 0x0000FF00) >> 8); + SE_322.data.u8[7] = ((datalayer.battery.status.max_discharge_current_dA * 10) & 0x000000FF); + + //Voltage (ex 370.00 = 37000, 32bits long) + SE_323.data.u8[0] = ((datalayer.battery.status.voltage_dV * 10) >> 24); + SE_323.data.u8[1] = (((datalayer.battery.status.voltage_dV * 10) & 0x00FF0000) >> 16); + SE_323.data.u8[2] = (((datalayer.battery.status.voltage_dV * 10) & 0x0000FF00) >> 8); + SE_323.data.u8[3] = ((datalayer.battery.status.voltage_dV * 10) & 0x000000FF); + //Current (ex 81.00A = 8100) TODO: Note s32 bit, which direction? + SE_323.data.u8[4] = ((datalayer.battery.status.current_dA * 10) >> 24); + SE_323.data.u8[5] = (((datalayer.battery.status.current_dA * 10) & 0x00FF0000) >> 16); + SE_323.data.u8[6] = (((datalayer.battery.status.current_dA * 10) & 0x0000FF00) >> 8); + SE_323.data.u8[7] = ((datalayer.battery.status.current_dA * 10) & 0x000000FF); + + //Temperature average + SE_324.data.u8[0] = (temperature_average >> 8); + SE_324.data.u8[1] = (temperature_average & 0x00FF); + //SOC (100.0%) + SE_324.data.u8[2] = ((datalayer.battery.status.reported_soc / 10) >> 8); + SE_324.data.u8[3] = ((datalayer.battery.status.reported_soc / 10) & 0x00FF); + //Commands (enum) + SE_325.data.u8[0] = (commands >> 8); + SE_325.data.u8[1] = (commands & 0x00FF); + //Warnings (enum) + SE_325.data.u8[2] = (warnings >> 8); + SE_325.data.u8[3] = (warnings & 0x00FF); + //Faults (enum) + SE_325.data.u8[4] = (faults >> 8); + SE_325.data.u8[5] = (faults & 0x00FF); + + //State (enum) + SE_326.data.u8[0] = (state >> 8); + SE_326.data.u8[1] = (state & 0x00FF); + //Cycle count (OPTIONAL UINT16) + //SE_326.data.u8[2] = Cycle count not tracked by emulator + //SE_326.data.u8[3] = Cycle count not tracked by emulator + //StateOfHealth (OPTIONAL 0-100%) + SE_326.data.u8[4] = (datalayer.battery.status.soh_pptt / 100 >> 8); + SE_326.data.u8[5] = (datalayer.battery.status.soh_pptt / 100 & 0x00FF); + //Capacity (OPTIONAL, full charge) AH+1 + SE_326.data.u8[6] = (fully_charged_capacity_ah >> 8); + SE_326.data.u8[7] = (fully_charged_capacity_ah & 0x00FF); + + //Cell temp max (OPTIONAL dC) + SE_327.data.u8[0] = (datalayer.battery.status.temperature_max_dC >> 8); + SE_327.data.u8[1] = (datalayer.battery.status.temperature_max_dC & 0x00FF); + //Cell temp min (OPTIONAL dC) + SE_327.data.u8[2] = (datalayer.battery.status.temperature_min_dC >> 8); + SE_327.data.u8[3] = (datalayer.battery.status.temperature_min_dC & 0x00FF); + //Cell max volt (OPTIONAL 4.000V) + SE_327.data.u8[4] = (datalayer.battery.status.cell_max_voltage_mV >> 8); + SE_327.data.u8[5] = (datalayer.battery.status.cell_max_voltage_mV & 0x00FF); + //Cell min volt (OPTIONAL 4.000V) + SE_327.data.u8[6] = (datalayer.battery.status.cell_min_voltage_mV >> 8); + SE_327.data.u8[7] = (datalayer.battery.status.cell_min_voltage_mV & 0x00FF); + + //Lifetime Charge Energy (OPTIONAL, WH, UINT32) + //SE_328.data.u8[0] = Lifetime energy not tracked by emulator + //SE_328.data.u8[1] = Lifetime energy not tracked by emulator + //SE_328.data.u8[2] = Lifetime energy not tracked by emulator + //SE_328.data.u8[3] = Lifetime energy not tracked by emulator + //Lifetime Discharge Energy (OPTIONAL, WH, UINT32) + //SE_328.data.u8[4] = Lifetime energy not tracked by emulator + //SE_328.data.u8[5] = Lifetime energy not tracked by emulator + //SE_328.data.u8[6] = Lifetime energy not tracked by emulator + //SE_328.data.u8[7] = Lifetime energy not tracked by emulator + + //Battery Manufacturer ID (UINT16) + //Unique identifier for each battery manufacturer implementing this protocol. IDs must be requested through Schneider Electric Solar. + SE_330.data.u8[0] = 0; //TODO, set Battery Manufacturer ID + SE_330.data.u8[1] = 0; //TODO, set Battery Manufacturer ID + //Battery Model ID (UINT16) + //Unique identifier for each battery model that a manufacturer has implemented this protocol on. IDs must be requested through Schneider Electric Solar. + SE_330.data.u8[2] = 0; //TODO, set Battery Model ID + SE_330.data.u8[3] = 0; //TODO, set Battery Model ID + //Serial numbers + //(For instance ABC123 would be represented as: + //0x41[char5], 0x42[char4], 0x43[char3], 0x31[char2], 0x32 [char1], 0x33 [char0]) + SE_330.data.u8[4] = 0x42; //Char 19 - B + SE_330.data.u8[5] = 0x41; //Char 18 - A + SE_330.data.u8[6] = 0x54; //Char 17 - T + SE_330.data.u8[7] = 0x54; //Char 16 - T + + SE_331.data.u8[0] = 0x45; //Char 15 - E + SE_331.data.u8[1] = 0x52; //Char 14 - R + SE_331.data.u8[2] = 0x59; //Char 13 - Y + SE_331.data.u8[3] = 0x45; //Char 12 - E + SE_331.data.u8[4] = 0x4D; //Char 11 - M + SE_331.data.u8[5] = 0x55; //Char 10 - U + SE_331.data.u8[6] = 0x4C; //Char 9 - L + SE_331.data.u8[7] = 0x41; //Char 8 - A + + SE_332.data.u8[0] = 0x54; //Char 7 - T + SE_332.data.u8[1] = 0x4F; //Char 6 - O + SE_332.data.u8[2] = 0x52; //Char 5 - R + SE_332.data.u8[3] = 0x30; //Char 4 - 0 + SE_332.data.u8[4] = 0x31; //Char 3 - 1 + SE_332.data.u8[5] = 0x32; //Char 2 - 2 + SE_332.data.u8[6] = 0x33; //Char 1 - 3 + SE_332.data.u8[7] = 0x34; //Char 0 - 4 + + //UNIQUE ID + //Schneider Electric Unique string identifier. The value should be an unique string "SEBMS" + SE_333.data.u8[0] = 0x53; //Char 5 - S + SE_333.data.u8[1] = 0x45; //Char 4 - E + SE_333.data.u8[2] = 0x42; //Char 3 - B + SE_333.data.u8[3] = 0x4D; //Char 2 - M + SE_333.data.u8[4] = 0x53; //Char 1 - S + SE_333.data.u8[5] = 0x00; //Char 0 - NULL + + //Protocol version, TODO: How do we reply with protocol version 0x0002 ? + SE_320.data.u8[0] = 0x00; + SE_320.data.u8[1] = 0x02; +} + +void receive_can_inverter(CAN_frame rx_frame) { + switch (rx_frame.ID) { + case 0x310: // Still alive message from inverter, every 1s + datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE; + break; + default: + break; + } +} + +void send_can_inverter() { + unsigned long currentMillis = millis(); + + // Send 500ms CAN Message + if (currentMillis - previousMillis500ms >= INTERVAL_500_MS) { + previousMillis500ms = currentMillis; + + transmit_can(&SE_321, can_config.inverter); + transmit_can(&SE_322, can_config.inverter); + transmit_can(&SE_323, can_config.inverter); + transmit_can(&SE_324, can_config.inverter); + transmit_can(&SE_325, can_config.inverter); + } + // Send 2s CAN Message + if (currentMillis - previousMillis2s >= INTERVAL_2_S) { + previousMillis2s = currentMillis; + + transmit_can(&SE_320, can_config.inverter); + transmit_can(&SE_326, can_config.inverter); + transmit_can(&SE_327, can_config.inverter); + } + // Send 10s CAN Message + if (currentMillis - previousMillis10s >= INTERVAL_10_S) { + previousMillis10s = currentMillis; + transmit_can(&SE_328, can_config.inverter); + transmit_can(&SE_330, can_config.inverter); + transmit_can(&SE_331, can_config.inverter); + transmit_can(&SE_332, can_config.inverter); + transmit_can(&SE_333, can_config.inverter); + } +} + +#endif diff --git a/Software/src/inverter/SCHNEIDER-CAN.h b/Software/src/inverter/SCHNEIDER-CAN.h new file mode 100644 index 00000000..7178a533 --- /dev/null +++ b/Software/src/inverter/SCHNEIDER-CAN.h @@ -0,0 +1,32 @@ +#ifndef SCHNEIDER_CAN_H +#define SCHNEIDER_CAN_H +#include "../include.h" + +#define CAN_INVERTER_SELECTED + +#define STATE_OFFLINE 0 +#define STATE_STANDBY 1 +#define STATE_STARTING 2 +#define STATE_ONLINE 3 +#define STATE_FAULTED 4 + +// Same enumerations used for Fault and Warning +#define FAULTS_CHARGE_OVERCURRENT 0 +#define FAULTS_DISCHARGE_OVERCURRENT 1 +#define FAULTS_OVER_TEMPERATURE 2 +#define FAULTS_UNDER_TEMPERATURE 3 +#define FAULTS_OVER_VOLTAGE 4 +#define FAULTS_UNDER_VOLTAGE 5 +#define FAULTS_CELL_IMBALANCE 6 +#define FAULTS_INTERNAL_COM_ERROR 7 +#define FAULTS_SYSTEM_ERROR 8 + +// Commands. Bit0 forced charge request. Bit1 charge permitted. Bit2 discharge permitted. Bit3 Stop +#define COMMAND_ONLY_CHARGE_ALLOWED 0x02 +#define COMMAND_ONLY_DISCHARGE_ALLOWED 0x04 +#define COMMAND_CHARGE_AND_DISCHARGE_ALLOWED 0x06 +#define COMMAND_STOP 0x08 + +void transmit_can(CAN_frame* tx_frame, int interface); + +#endif From d5cd25bdf3c19b07b1b83d8262ca0a91fae3ac75 Mon Sep 17 00:00:00 2001 From: wjcloudy <56305354+wjcloudy@users.noreply.github.com> Date: Thu, 14 Nov 2024 16:35:42 +0000 Subject: [PATCH 151/210] Ensure both stale checks run --- Software/src/battery/BMW-IX-BATTERY.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Software/src/battery/BMW-IX-BATTERY.cpp b/Software/src/battery/BMW-IX-BATTERY.cpp index ebc7e2ab..dba9ddca 100644 --- a/Software/src/battery/BMW-IX-BATTERY.cpp +++ b/Software/src/battery/BMW-IX-BATTERY.cpp @@ -463,8 +463,12 @@ void update_values_battery() { //This function maps all the values fetched via datalayer.battery.status.temperature_max_dC = max_battery_temperature; //Check stale values. As values dont change much during idle only consider stale if both parts of this message freeze. - if (isStale(min_cell_voltage, datalayer.battery.status.cell_min_voltage_mV, min_cell_voltage_lastchanged) && - isStale(max_cell_voltage, datalayer.battery.status.cell_max_voltage_mV, max_cell_voltage_lastchanged)) { + bool isMinCellVoltageStale = + isStale(min_cell_voltage, datalayer.battery.status.cell_min_voltage_mV, min_cell_voltage_lastchanged); + bool isMaxCellVoltageStale = + isStale(max_cell_voltage, datalayer.battery.status.cell_max_voltage_mV, max_cell_voltage_lastchanged); + + if (isMinCellVoltageStale && isMaxCellVoltageStale) { datalayer.battery.status.cell_min_voltage_mV = 9999; //Stale values force stop datalayer.battery.status.cell_max_voltage_mV = 9999; //Stale values force stop set_event(EVENT_CAN_RX_FAILURE, 0); From edb6dcd39b66e0930df57717148815169c3897f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Thu, 14 Nov 2024 18:38:23 +0200 Subject: [PATCH 152/210] Add battery text to datalayer --- Software/src/battery/BMW-I3-BATTERY.cpp | 9 +-- Software/src/battery/BMW-IX-BATTERY.cpp | 7 +- Software/src/battery/BYD-ATTO-3-BATTERY.cpp | 6 +- Software/src/battery/CELLPOWER-BMS.cpp | 6 +- Software/src/battery/CHADEMO-BATTERY.cpp | 8 +- .../src/battery/IMIEV-CZERO-ION-BATTERY.cpp | 7 +- Software/src/battery/JAGUAR-IPACE-BATTERY.cpp | 6 +- Software/src/battery/KIA-E-GMP-BATTERY.cpp | 8 +- .../src/battery/KIA-HYUNDAI-64-BATTERY.cpp | 7 +- .../battery/KIA-HYUNDAI-HYBRID-BATTERY.cpp | 8 +- Software/src/battery/MG-5-BATTERY.cpp | 6 +- Software/src/battery/NISSAN-LEAF-BATTERY.cpp | 7 +- Software/src/battery/PYLON-BATTERY.cpp | 7 +- .../src/battery/RANGE-ROVER-PHEV-BATTERY.cpp | 7 +- .../src/battery/RENAULT-KANGOO-BATTERY.cpp | 7 +- Software/src/battery/RENAULT-TWIZY.cpp | 7 +- .../src/battery/RENAULT-ZOE-GEN1-BATTERY.cpp | 7 +- .../src/battery/RENAULT-ZOE-GEN2-BATTERY.cpp | 7 +- Software/src/battery/RJXZS-BMS.cpp | 8 +- .../src/battery/SANTA-FE-PHEV-BATTERY.cpp | 6 +- .../SERIAL-LINK-RECEIVER-FROM-BATTERY.cpp | 5 +- Software/src/battery/TESLA-BATTERY.cpp | 12 ++- Software/src/battery/TEST-FAKE-BATTERY.cpp | 7 +- Software/src/battery/VOLVO-SPA-BATTERY.cpp | 8 +- Software/src/datalayer/datalayer.h | 5 +- Software/src/devboard/webserver/webserver.cpp | 78 +------------------ 26 files changed, 101 insertions(+), 155 deletions(-) diff --git a/Software/src/battery/BMW-I3-BATTERY.cpp b/Software/src/battery/BMW-I3-BATTERY.cpp index a1b576d4..15bac9f4 100644 --- a/Software/src/battery/BMW-I3-BATTERY.cpp +++ b/Software/src/battery/BMW-I3-BATTERY.cpp @@ -1118,9 +1118,9 @@ void send_can_battery() { } void setup_battery(void) { // Performs one time setup at startup -#ifdef DEBUG_VIA_USB - Serial.println("BMW i3 battery selected"); -#endif //DEBUG_VIA_USB + strncpy(datalayer.system.info.battery_protocol, "BMW i3", sizeof(datalayer.system.info.battery_protocol) - 1); + datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = + '\0'; // Ensure null termination //Before we have started up and detected which battery is in use, use 60AH values datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_60AH; @@ -1129,9 +1129,6 @@ void setup_battery(void) { // Performs one time setup at startup datalayer.system.status.battery_allows_contactor_closing = true; #ifdef DOUBLE_BATTERY -#ifdef DEBUG_VIA_USB - Serial.println("Another BMW i3 battery also selected!"); -#endif //DEBUG_VIA_USB datalayer.battery2.info.max_design_voltage_dV = datalayer.battery.info.max_design_voltage_dV; datalayer.battery2.info.min_design_voltage_dV = datalayer.battery.info.min_design_voltage_dV; datalayer.battery2.info.max_cell_voltage_deviation_mV = datalayer.battery.info.max_cell_voltage_deviation_mV; diff --git a/Software/src/battery/BMW-IX-BATTERY.cpp b/Software/src/battery/BMW-IX-BATTERY.cpp index a994e27d..5712d591 100644 --- a/Software/src/battery/BMW-IX-BATTERY.cpp +++ b/Software/src/battery/BMW-IX-BATTERY.cpp @@ -778,9 +778,10 @@ void send_can_battery() { //} //We can always send CAN as the iX BMS will wake up on vehicle comms void setup_battery(void) { // Performs one time setup at startup -#ifdef DEBUG_VIA_USB - Serial.println("BMW iX battery selected"); -#endif //DEBUG_VIA_USB + strncpy(datalayer.system.info.battery_protocol, "BMW iX and i4-7 platform", + sizeof(datalayer.system.info.battery_protocol) - 1); + datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = + '\0'; // Ensure null termination //Before we have started up and detected which battery is in use, use 108S values datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; diff --git a/Software/src/battery/BYD-ATTO-3-BATTERY.cpp b/Software/src/battery/BYD-ATTO-3-BATTERY.cpp index 9a1e1f9b..4e7ad1fd 100644 --- a/Software/src/battery/BYD-ATTO-3-BATTERY.cpp +++ b/Software/src/battery/BYD-ATTO-3-BATTERY.cpp @@ -399,9 +399,9 @@ void send_can_battery() { } void setup_battery(void) { // Performs one time setup at startup -#ifdef DEBUG_VIA_USB - Serial.println("BYD Atto 3 battery selected"); -#endif + strncpy(datalayer.system.info.battery_protocol, "BYD Atto 3", sizeof(datalayer.system.info.battery_protocol) - 1); + datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = + '\0'; // Ensure null termination datalayer.battery.info.number_of_cells = 126; datalayer.battery.info.chemistry = battery_chemistry_enum::LFP; datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; diff --git a/Software/src/battery/CELLPOWER-BMS.cpp b/Software/src/battery/CELLPOWER-BMS.cpp index ec448312..604f2e65 100644 --- a/Software/src/battery/CELLPOWER-BMS.cpp +++ b/Software/src/battery/CELLPOWER-BMS.cpp @@ -333,9 +333,9 @@ void send_can_battery() { } void setup_battery(void) { // Performs one time setup at startup -#ifdef DEBUG_VIA_USB - Serial.println("Cellpower BMS selected"); -#endif + strncpy(datalayer.system.info.battery_protocol, "Cellpower BMS", sizeof(datalayer.system.info.battery_protocol) - 1); + datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = + '\0'; // Ensure null termination datalayer.system.status.battery_allows_contactor_closing = true; datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV; diff --git a/Software/src/battery/CHADEMO-BATTERY.cpp b/Software/src/battery/CHADEMO-BATTERY.cpp index 90ecd950..d582f690 100644 --- a/Software/src/battery/CHADEMO-BATTERY.cpp +++ b/Software/src/battery/CHADEMO-BATTERY.cpp @@ -1031,9 +1031,11 @@ void handle_chademo_sequence() { } void setup_battery(void) { // Performs one time setup at startup -#ifdef DEBUG_VIA_USB - Serial.println("Chademo battery selected"); -#endif + + strncpy(datalayer.system.info.battery_protocol, "Chademo V2X mode", + sizeof(datalayer.system.info.battery_protocol) - 1); + datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = + '\0'; // Ensure null termination CHADEMO_Status = CHADEMO_IDLE; diff --git a/Software/src/battery/IMIEV-CZERO-ION-BATTERY.cpp b/Software/src/battery/IMIEV-CZERO-ION-BATTERY.cpp index 43ef0b07..f24490cc 100644 --- a/Software/src/battery/IMIEV-CZERO-ION-BATTERY.cpp +++ b/Software/src/battery/IMIEV-CZERO-ION-BATTERY.cpp @@ -224,9 +224,10 @@ void send_can_battery() { } void setup_battery(void) { // Performs one time setup at startup -#ifdef DEBUG_VIA_USB - Serial.println("Mitsubishi i-MiEV / Citroen C-Zero / Peugeot Ion battery selected"); -#endif + strncpy(datalayer.system.info.battery_protocol, "I-Miev / C-Zero / Ion Triplet", + sizeof(datalayer.system.info.battery_protocol) - 1); + datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = + '\0'; // Ensure null termination datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV; datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV; diff --git a/Software/src/battery/JAGUAR-IPACE-BATTERY.cpp b/Software/src/battery/JAGUAR-IPACE-BATTERY.cpp index a083a8ce..3733810e 100644 --- a/Software/src/battery/JAGUAR-IPACE-BATTERY.cpp +++ b/Software/src/battery/JAGUAR-IPACE-BATTERY.cpp @@ -254,9 +254,9 @@ void send_can_battery() { } void setup_battery(void) { // Performs one time setup at startup -#ifdef DEBUG_VIA_USB - Serial.println("Jaguar iPace 90kWh battery selected"); -#endif + strncpy(datalayer.system.info.battery_protocol, "Jaguar I-PACE", sizeof(datalayer.system.info.battery_protocol) - 1); + datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = + '\0'; // Ensure null termination datalayer.battery.info.number_of_cells = 108; datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; diff --git a/Software/src/battery/KIA-E-GMP-BATTERY.cpp b/Software/src/battery/KIA-E-GMP-BATTERY.cpp index 19db7992..0a810e9f 100644 --- a/Software/src/battery/KIA-E-GMP-BATTERY.cpp +++ b/Software/src/battery/KIA-E-GMP-BATTERY.cpp @@ -1037,14 +1037,14 @@ void send_can_battery() { } void setup_battery(void) { // Performs one time setup at startup -#ifdef DEBUG_VIA_USB - Serial.println("Hyundai E-GMP (Electric Global Modular Platform) battery selected"); -#endif + strncpy(datalayer.system.info.battery_protocol, "Kia/Hyundai EGMP platform", + sizeof(datalayer.system.info.battery_protocol) - 1); + datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = + '\0'; // Ensure null termination startMillis = millis(); // Record the starting time datalayer.system.status.battery_allows_contactor_closing = true; - datalayer.battery.info.number_of_cells = 192; // TODO: will vary depending on battery datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV; diff --git a/Software/src/battery/KIA-HYUNDAI-64-BATTERY.cpp b/Software/src/battery/KIA-HYUNDAI-64-BATTERY.cpp index b3f935a6..b25268d6 100644 --- a/Software/src/battery/KIA-HYUNDAI-64-BATTERY.cpp +++ b/Software/src/battery/KIA-HYUNDAI-64-BATTERY.cpp @@ -534,9 +534,10 @@ void send_can_battery() { } void setup_battery(void) { // Performs one time setup at startup -#ifdef DEBUG_VIA_USB - Serial.println("Kia Niro / Hyundai Kona 64kWh battery selected"); -#endif + strncpy(datalayer.system.info.battery_protocol, "Kia/Hyundai 64/40kWh battery", + sizeof(datalayer.system.info.battery_protocol) - 1); + datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = + '\0'; // Ensure null termination datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_98S_DV; //Start with 98S value. Precised later datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_90S_DV; //Start with 90S value. Precised later datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV; diff --git a/Software/src/battery/KIA-HYUNDAI-HYBRID-BATTERY.cpp b/Software/src/battery/KIA-HYUNDAI-HYBRID-BATTERY.cpp index 49a8dae6..f6f88c52 100644 --- a/Software/src/battery/KIA-HYUNDAI-HYBRID-BATTERY.cpp +++ b/Software/src/battery/KIA-HYUNDAI-HYBRID-BATTERY.cpp @@ -257,9 +257,11 @@ void send_can_battery() { } void setup_battery(void) { // Performs one time setup at startup -#ifdef DEBUG_VIA_USB - Serial.println("Kia/Hyundai Hybrid battery selected"); -#endif + strncpy(datalayer.system.info.battery_protocol, "Kia/Hyundai Hybrid", + sizeof(datalayer.system.info.battery_protocol) - 1); + datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = + '\0'; // Ensure null termination + datalayer.battery.info.number_of_cells = 56; // HEV , TODO: Make dynamic according to HEV/PHEV datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV; diff --git a/Software/src/battery/MG-5-BATTERY.cpp b/Software/src/battery/MG-5-BATTERY.cpp index 8da1b9a2..94b8ec38 100644 --- a/Software/src/battery/MG-5-BATTERY.cpp +++ b/Software/src/battery/MG-5-BATTERY.cpp @@ -135,9 +135,9 @@ void send_can_battery() { } void setup_battery(void) { // Performs one time setup at startup -#ifdef DEBUG_VIA_USB - Serial.println("MG 5 battery selected"); -#endif + strncpy(datalayer.system.info.battery_protocol, "MG 5 battery", sizeof(datalayer.system.info.battery_protocol) - 1); + datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = + '\0'; // Ensure null termination datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV; diff --git a/Software/src/battery/NISSAN-LEAF-BATTERY.cpp b/Software/src/battery/NISSAN-LEAF-BATTERY.cpp index f32a9ba6..bb0d6f91 100644 --- a/Software/src/battery/NISSAN-LEAF-BATTERY.cpp +++ b/Software/src/battery/NISSAN-LEAF-BATTERY.cpp @@ -1224,9 +1224,10 @@ uint16_t Temp_fromRAW_to_F(uint16_t temperature) { //This function feels horrib } void setup_battery(void) { // Performs one time setup at startup -#ifdef DEBUG_VIA_USB - Serial.println("Nissan LEAF battery selected"); -#endif + strncpy(datalayer.system.info.battery_protocol, "Nissan LEAF battery", + sizeof(datalayer.system.info.battery_protocol) - 1); + datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = + '\0'; // Ensure null termination datalayer.battery.info.number_of_cells = 96; datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; diff --git a/Software/src/battery/PYLON-BATTERY.cpp b/Software/src/battery/PYLON-BATTERY.cpp index 16906722..dfa0dfe0 100644 --- a/Software/src/battery/PYLON-BATTERY.cpp +++ b/Software/src/battery/PYLON-BATTERY.cpp @@ -175,9 +175,10 @@ void send_can_battery() { } void setup_battery(void) { // Performs one time setup at startup -#ifdef DEBUG_VIA_USB - Serial.println("Pylon battery selected"); -#endif + strncpy(datalayer.system.info.battery_protocol, "Pylon compatible battery", + sizeof(datalayer.system.info.battery_protocol) - 1); + datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = + '\0'; // Ensure null termination datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV; diff --git a/Software/src/battery/RANGE-ROVER-PHEV-BATTERY.cpp b/Software/src/battery/RANGE-ROVER-PHEV-BATTERY.cpp index 4f6eb16c..ce6ea714 100644 --- a/Software/src/battery/RANGE-ROVER-PHEV-BATTERY.cpp +++ b/Software/src/battery/RANGE-ROVER-PHEV-BATTERY.cpp @@ -313,9 +313,10 @@ void send_can_battery() { } void setup_battery(void) { // Performs one time setup at startup -#ifdef DEBUG_VIA_USB - Serial.println("Range Rover PHEV battery (L494 / L405) selected"); -#endif //DEBUG_VIA_USB + strncpy(datalayer.system.info.battery_protocol, "Range Rover 13kWh PHEV battery (L494/L405)", + sizeof(datalayer.system.info.battery_protocol) - 1); + datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = + '\0'; // Ensure null termination datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV; diff --git a/Software/src/battery/RENAULT-KANGOO-BATTERY.cpp b/Software/src/battery/RENAULT-KANGOO-BATTERY.cpp index 00b4e12b..f3d51887 100644 --- a/Software/src/battery/RENAULT-KANGOO-BATTERY.cpp +++ b/Software/src/battery/RENAULT-KANGOO-BATTERY.cpp @@ -234,9 +234,10 @@ void send_can_battery() { } void setup_battery(void) { // Performs one time setup at startup -#ifdef DEBUG_VIA_USB - Serial.println("Renault Kangoo battery selected"); -#endif + + strncpy(datalayer.system.info.battery_protocol, "Renault Kangoo", sizeof(datalayer.system.info.battery_protocol) - 1); + datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = + '\0'; // Ensure null termination datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV; diff --git a/Software/src/battery/RENAULT-TWIZY.cpp b/Software/src/battery/RENAULT-TWIZY.cpp index 69a83316..74c9ea91 100644 --- a/Software/src/battery/RENAULT-TWIZY.cpp +++ b/Software/src/battery/RENAULT-TWIZY.cpp @@ -132,10 +132,9 @@ void send_can_battery() { } void setup_battery(void) { // Performs one time setup at startup -#ifdef DEBUG_VIA_USB - Serial.println("Renault Twizy battery selected"); -#endif - + strncpy(datalayer.system.info.battery_protocol, "Renault Twizy", sizeof(datalayer.system.info.battery_protocol) - 1); + datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = + '\0'; // Ensure null termination datalayer.battery.info.number_of_cells = 14; datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV; diff --git a/Software/src/battery/RENAULT-ZOE-GEN1-BATTERY.cpp b/Software/src/battery/RENAULT-ZOE-GEN1-BATTERY.cpp index 1044729f..27050919 100644 --- a/Software/src/battery/RENAULT-ZOE-GEN1-BATTERY.cpp +++ b/Software/src/battery/RENAULT-ZOE-GEN1-BATTERY.cpp @@ -518,9 +518,10 @@ void send_can_battery() { } void setup_battery(void) { // Performs one time setup at startup -#ifdef DEBUG_VIA_USB - Serial.println("Renault Zoe 22/40kWh battery selected"); -#endif + strncpy(datalayer.system.info.battery_protocol, "Renault Zoe Gen1 22/40kWh", + sizeof(datalayer.system.info.battery_protocol) - 1); + datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = + '\0'; // Ensure null termination datalayer.system.status.battery_allows_contactor_closing = true; datalayer.battery.info.number_of_cells = 96; datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; diff --git a/Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.cpp b/Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.cpp index 50b09d2e..bafbdaf3 100644 --- a/Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.cpp +++ b/Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.cpp @@ -385,9 +385,10 @@ void send_can_battery() { } void setup_battery(void) { // Performs one time setup at startup -#ifdef DEBUG_VIA_USB - Serial.println("Renault Zoe 50kWh battery selected"); -#endif + strncpy(datalayer.system.info.battery_protocol, "Renault Zoe Gen2 50kWh", + sizeof(datalayer.system.info.battery_protocol) - 1); + datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = + '\0'; // Ensure null termination datalayer.system.status.battery_allows_contactor_closing = true; datalayer.battery.info.number_of_cells = 96; datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; diff --git a/Software/src/battery/RJXZS-BMS.cpp b/Software/src/battery/RJXZS-BMS.cpp index b75934c8..86b3d983 100644 --- a/Software/src/battery/RJXZS-BMS.cpp +++ b/Software/src/battery/RJXZS-BMS.cpp @@ -570,10 +570,10 @@ void send_can_battery() { } void setup_battery(void) { // Performs one time setup at startup -#ifdef DEBUG_VIA_USB - Serial.println("RJXZS BMS selected"); -#endif - + strncpy(datalayer.system.info.battery_protocol, "RJXZS BMS, DIY battery", + sizeof(datalayer.system.info.battery_protocol) - 1); + datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = + '\0'; // Ensure null termination datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV; datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV; diff --git a/Software/src/battery/SANTA-FE-PHEV-BATTERY.cpp b/Software/src/battery/SANTA-FE-PHEV-BATTERY.cpp index 36b1a649..2180628e 100644 --- a/Software/src/battery/SANTA-FE-PHEV-BATTERY.cpp +++ b/Software/src/battery/SANTA-FE-PHEV-BATTERY.cpp @@ -402,9 +402,9 @@ uint8_t CalculateCRC8(CAN_frame rx_frame) { } void setup_battery(void) { // Performs one time setup at startup -#ifdef DEBUG_VIA_USB - Serial.println("Hyundai Santa Fe PHEV battery selected"); -#endif + strncpy(datalayer.system.info.battery_protocol, "Santa Fe PHEV", sizeof(datalayer.system.info.battery_protocol) - 1); + datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = + '\0'; // Ensure null termination datalayer.battery.info.number_of_cells = 96; datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV; diff --git a/Software/src/battery/SERIAL-LINK-RECEIVER-FROM-BATTERY.cpp b/Software/src/battery/SERIAL-LINK-RECEIVER-FROM-BATTERY.cpp index 0f4ac135..25a97ddd 100644 --- a/Software/src/battery/SERIAL-LINK-RECEIVER-FROM-BATTERY.cpp +++ b/Software/src/battery/SERIAL-LINK-RECEIVER-FROM-BATTERY.cpp @@ -219,7 +219,10 @@ void update_values_serial_link() { } void setup_battery(void) { - Serial.println("SERIAL_DATA_LINK_RECEIVER selected"); + strncpy(datalayer.system.info.battery_protocol, "Serial link to another LilyGo board", + sizeof(datalayer.system.info.battery_protocol) - 1); + datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = + '\0'; // Ensure null termination } // Needed to make the compiler happy void update_values_battery() {} diff --git a/Software/src/battery/TESLA-BATTERY.cpp b/Software/src/battery/TESLA-BATTERY.cpp index eaa01e1b..357864be 100644 --- a/Software/src/battery/TESLA-BATTERY.cpp +++ b/Software/src/battery/TESLA-BATTERY.cpp @@ -1249,13 +1249,13 @@ void printDebugIfActive(uint8_t symbol, const char* message) { } void setup_battery(void) { // Performs one time setup at startup -#ifdef DEBUG_VIA_USB - Serial.println("Tesla Model S/3/X/Y battery selected"); -#endif - datalayer.system.status.battery_allows_contactor_closing = true; #ifdef TESLA_MODEL_SX_BATTERY // Always use NCM/A mode on S/X packs + strncpy(datalayer.system.info.battery_protocol, "Tesla Model S/X", + sizeof(datalayer.system.info.battery_protocol) - 1); + datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = + '\0'; // Ensure null termination datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_SX_NCMA; datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_SX_NCMA; datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_NCA_NCM; @@ -1271,6 +1271,10 @@ void setup_battery(void) { // Performs one time setup at startup #endif // TESLA_MODEL_SX_BATTERY #ifdef TESLA_MODEL_3Y_BATTERY // Model 3/Y can be either LFP or NCM/A + strncpy(datalayer.system.info.battery_protocol, "Tesla Model 3/Y", + sizeof(datalayer.system.info.battery_protocol) - 1); + datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = + '\0'; // Ensure null termination #ifdef LFP_CHEMISTRY datalayer.battery.info.chemistry = battery_chemistry_enum::LFP; datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_3Y_LFP; diff --git a/Software/src/battery/TEST-FAKE-BATTERY.cpp b/Software/src/battery/TEST-FAKE-BATTERY.cpp index fdc35294..c964eb54 100644 --- a/Software/src/battery/TEST-FAKE-BATTERY.cpp +++ b/Software/src/battery/TEST-FAKE-BATTERY.cpp @@ -145,9 +145,10 @@ void send_can_battery() { void setup_battery(void) { // Performs one time setup at startup randomSeed(analogRead(0)); -#ifdef DEBUG_VIA_USB - Serial.println("Test mode with fake battery selected"); -#endif + strncpy(datalayer.system.info.battery_protocol, "Fake battery for testing purposes", + sizeof(datalayer.system.info.battery_protocol) - 1); + datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = + '\0'; // Ensure null termination datalayer.battery.info.max_design_voltage_dV = 4040; // 404.4V, over this, charging is not possible (goes into forced discharge) diff --git a/Software/src/battery/VOLVO-SPA-BATTERY.cpp b/Software/src/battery/VOLVO-SPA-BATTERY.cpp index efdbc0a0..fc71de42 100644 --- a/Software/src/battery/VOLVO-SPA-BATTERY.cpp +++ b/Software/src/battery/VOLVO-SPA-BATTERY.cpp @@ -332,10 +332,10 @@ void send_can_battery() { } void setup_battery(void) { // Performs one time setup at startup -#ifdef DEBUG_VIA_USB - Serial.println("Volvo SPA XC40 Recharge / Polestar2 78kWh battery selected"); -#endif - + strncpy(datalayer.system.info.battery_protocol, "Volvo / Polestar 78kWh battery", + sizeof(datalayer.system.info.battery_protocol) - 1); + datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = + '\0'; // Ensure null termination datalayer.battery.info.number_of_cells = 108; datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV; diff --git a/Software/src/datalayer/datalayer.h b/Software/src/datalayer/datalayer.h index 5b198158..f925d30c 100644 --- a/Software/src/datalayer/datalayer.h +++ b/Software/src/datalayer/datalayer.h @@ -127,7 +127,10 @@ typedef struct { } DATALAYER_SHUNT_TYPE; typedef struct { - // TODO + /** array with type of battery used, for displaying on webserver */ + char battery_protocol[64] = {0}; + /** array with type of inverter used, for displaying on webserver */ + char inverter_protocol[64] = {0}; } DATALAYER_SYSTEM_INFO_TYPE; typedef struct { diff --git a/Software/src/devboard/webserver/webserver.cpp b/Software/src/devboard/webserver/webserver.cpp index 8eab7631..b6de87a0 100644 --- a/Software/src/devboard/webserver/webserver.cpp +++ b/Software/src/devboard/webserver/webserver.cpp @@ -486,6 +486,7 @@ String processor(const String& var) { // Display which components are used content += "

Inverter protocol: "; + content += datalayer.system.info.inverter_protocol; #ifdef BYD_CAN content += "BYD Battery-Box Premium HVS over CAN Bus"; #endif // BYD_CAN @@ -514,83 +515,8 @@ String processor(const String& var) { content += "SolaX Triple Power LFP over CAN bus"; #endif // SOLAX_CAN content += "

"; - content += "

Battery protocol: "; -#ifdef BMW_I3_BATTERY - content += "BMW i3"; -#endif // BMW_I3_BATTERY -#ifdef BMW_IX_BATTERY - content += "BMW iX and i4-7 platform"; -#endif // BMW_IX_BATTERY -#ifdef BYD_ATTO_3_BATTERY - content += "BYD Atto 3"; -#endif // BYD_ATTO_3_BATTERY -#ifdef CELLPOWER_BMS - content += "Cellpower BMS"; -#endif // CELLPOWER_BMS -#ifdef CHADEMO_BATTERY - content += "Chademo V2X mode"; -#endif // CHADEMO_BATTERY -#ifdef IMIEV_CZERO_ION_BATTERY - content += "I-Miev / C-Zero / Ion Triplet"; -#endif // IMIEV_CZERO_ION_BATTERY -#ifdef JAGUAR_IPACE_BATTERY - content += "Jaguar I-PACE"; -#endif // JAGUAR_IPACE_BATTERY -#ifdef KIA_HYUNDAI_64_BATTERY - content += "Kia/Hyundai 64kWh"; -#endif // KIA_HYUNDAI_64_BATTERY -#ifdef KIA_E_GMP_BATTERY - content += "Kia/Hyundai EGMP platform"; -#endif // KIA_E_GMP_BATTERY -#ifdef KIA_HYUNDAI_HYBRID_BATTERY - content += "Kia/Hyundai Hybrid"; -#endif // KIA_HYUNDAI_HYBRID_BATTERY -#ifdef MG_5_BATTERY - content += "MG 5"; -#endif // MG_5_BATTERY -#ifdef NISSAN_LEAF_BATTERY - content += "Nissan LEAF"; -#endif // NISSAN_LEAF_BATTERY -#ifdef PYLON_BATTERY - content += "Pylon compatible battery"; -#endif // PYLON_BATTERY -#ifdef RJXZS_BMS - content += "RJXZS BMS, DIY battery"; -#endif // RJXZS_BMS -#ifdef RANGE_ROVER_PHEV_BATTERY - content += "Range Rover 13kWh PHEV battery (L494/L405)"; -#endif //RANGE_ROVER_PHEV_BATTERY -#ifdef RENAULT_KANGOO_BATTERY - content += "Renault Kangoo"; -#endif // RENAULT_KANGOO_BATTERY -#ifdef RENAULT_TWIZY_BATTERY - content += "Renault Twizy"; -#endif // RENAULT_TWIZY_BATTERY -#ifdef RENAULT_ZOE_GEN1_BATTERY - content += "Renault Zoe Gen1 22/40"; -#endif // RENAULT_ZOE_GEN1_BATTERY -#ifdef RENAULT_ZOE_GEN2_BATTERY - content += "Renault Zoe Gen2 50"; -#endif // RENAULT_ZOE_GEN2_BATTERY -#ifdef SANTA_FE_PHEV_BATTERY - content += "Santa Fe PHEV"; -#endif // SANTA_FE_PHEV_BATTERY -#ifdef SERIAL_LINK_RECEIVER - content += "Serial link to another LilyGo board"; -#endif // SERIAL_LINK_RECEIVER -#ifdef TESLA_MODEL_SX_BATTERY - content += "Tesla Model S/X"; -#endif // TESLA_MODEL_SX_BATTERY -#ifdef TESLA_MODEL_3Y_BATTERY - content += "Tesla Model 3/Y"; -#endif // TESLA_MODEL_3Y_BATTERY -#ifdef VOLVO_SPA_BATTERY - content += "Volvo / Polestar 78kWh battery"; -#endif // VOLVO_SPA_BATTERY -#ifdef TEST_FAKE_BATTERY - content += "Fake battery for testing purposes"; -#endif // TEST_FAKE_BATTERY + content += datalayer.system.info.battery_protocol; #ifdef DOUBLE_BATTERY content += " (Double battery)"; if (datalayer.battery.info.chemistry == battery_chemistry_enum::LFP) { From 950b0bbb269d8b4f2218037ad3a2adb42ec99cb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Thu, 14 Nov 2024 18:40:09 +0200 Subject: [PATCH 153/210] Remove comment --- Software/src/battery/BMW-I3-BATTERY.cpp | 3 +-- Software/src/battery/BMW-IX-BATTERY.cpp | 3 +-- Software/src/battery/BYD-ATTO-3-BATTERY.cpp | 3 +-- Software/src/battery/CELLPOWER-BMS.cpp | 3 +-- Software/src/battery/CHADEMO-BATTERY.cpp | 3 +-- Software/src/battery/IMIEV-CZERO-ION-BATTERY.cpp | 3 +-- Software/src/battery/JAGUAR-IPACE-BATTERY.cpp | 3 +-- Software/src/battery/KIA-E-GMP-BATTERY.cpp | 3 +-- Software/src/battery/KIA-HYUNDAI-64-BATTERY.cpp | 3 +-- Software/src/battery/KIA-HYUNDAI-HYBRID-BATTERY.cpp | 3 +-- Software/src/battery/MG-5-BATTERY.cpp | 3 +-- Software/src/battery/NISSAN-LEAF-BATTERY.cpp | 3 +-- Software/src/battery/PYLON-BATTERY.cpp | 3 +-- Software/src/battery/RANGE-ROVER-PHEV-BATTERY.cpp | 3 +-- Software/src/battery/RENAULT-KANGOO-BATTERY.cpp | 3 +-- Software/src/battery/RENAULT-TWIZY.cpp | 3 +-- Software/src/battery/RENAULT-ZOE-GEN1-BATTERY.cpp | 3 +-- Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.cpp | 3 +-- Software/src/battery/RJXZS-BMS.cpp | 3 +-- Software/src/battery/SANTA-FE-PHEV-BATTERY.cpp | 3 +-- Software/src/battery/SERIAL-LINK-RECEIVER-FROM-BATTERY.cpp | 3 +-- Software/src/battery/TESLA-BATTERY.cpp | 6 ++---- Software/src/battery/TEST-FAKE-BATTERY.cpp | 3 +-- Software/src/battery/VOLVO-SPA-BATTERY.cpp | 3 +-- 24 files changed, 25 insertions(+), 50 deletions(-) diff --git a/Software/src/battery/BMW-I3-BATTERY.cpp b/Software/src/battery/BMW-I3-BATTERY.cpp index 15bac9f4..2000558e 100644 --- a/Software/src/battery/BMW-I3-BATTERY.cpp +++ b/Software/src/battery/BMW-I3-BATTERY.cpp @@ -1119,8 +1119,7 @@ void send_can_battery() { void setup_battery(void) { // Performs one time setup at startup strncpy(datalayer.system.info.battery_protocol, "BMW i3", sizeof(datalayer.system.info.battery_protocol) - 1); - datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = - '\0'; // Ensure null termination + datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = '\0'; //Before we have started up and detected which battery is in use, use 60AH values datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_60AH; diff --git a/Software/src/battery/BMW-IX-BATTERY.cpp b/Software/src/battery/BMW-IX-BATTERY.cpp index 5712d591..4a0846a5 100644 --- a/Software/src/battery/BMW-IX-BATTERY.cpp +++ b/Software/src/battery/BMW-IX-BATTERY.cpp @@ -780,8 +780,7 @@ void send_can_battery() { void setup_battery(void) { // Performs one time setup at startup strncpy(datalayer.system.info.battery_protocol, "BMW iX and i4-7 platform", sizeof(datalayer.system.info.battery_protocol) - 1); - datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = - '\0'; // Ensure null termination + datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = '\0'; //Before we have started up and detected which battery is in use, use 108S values datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; diff --git a/Software/src/battery/BYD-ATTO-3-BATTERY.cpp b/Software/src/battery/BYD-ATTO-3-BATTERY.cpp index 4e7ad1fd..18040f9a 100644 --- a/Software/src/battery/BYD-ATTO-3-BATTERY.cpp +++ b/Software/src/battery/BYD-ATTO-3-BATTERY.cpp @@ -400,8 +400,7 @@ void send_can_battery() { void setup_battery(void) { // Performs one time setup at startup strncpy(datalayer.system.info.battery_protocol, "BYD Atto 3", sizeof(datalayer.system.info.battery_protocol) - 1); - datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = - '\0'; // Ensure null termination + datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = '\0'; datalayer.battery.info.number_of_cells = 126; datalayer.battery.info.chemistry = battery_chemistry_enum::LFP; datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; diff --git a/Software/src/battery/CELLPOWER-BMS.cpp b/Software/src/battery/CELLPOWER-BMS.cpp index 604f2e65..d0b94943 100644 --- a/Software/src/battery/CELLPOWER-BMS.cpp +++ b/Software/src/battery/CELLPOWER-BMS.cpp @@ -334,8 +334,7 @@ void send_can_battery() { void setup_battery(void) { // Performs one time setup at startup strncpy(datalayer.system.info.battery_protocol, "Cellpower BMS", sizeof(datalayer.system.info.battery_protocol) - 1); - datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = - '\0'; // Ensure null termination + datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = '\0'; datalayer.system.status.battery_allows_contactor_closing = true; datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV; diff --git a/Software/src/battery/CHADEMO-BATTERY.cpp b/Software/src/battery/CHADEMO-BATTERY.cpp index d582f690..afcc31a5 100644 --- a/Software/src/battery/CHADEMO-BATTERY.cpp +++ b/Software/src/battery/CHADEMO-BATTERY.cpp @@ -1034,8 +1034,7 @@ void setup_battery(void) { // Performs one time setup at startup strncpy(datalayer.system.info.battery_protocol, "Chademo V2X mode", sizeof(datalayer.system.info.battery_protocol) - 1); - datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = - '\0'; // Ensure null termination + datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = '\0'; CHADEMO_Status = CHADEMO_IDLE; diff --git a/Software/src/battery/IMIEV-CZERO-ION-BATTERY.cpp b/Software/src/battery/IMIEV-CZERO-ION-BATTERY.cpp index f24490cc..c1ced536 100644 --- a/Software/src/battery/IMIEV-CZERO-ION-BATTERY.cpp +++ b/Software/src/battery/IMIEV-CZERO-ION-BATTERY.cpp @@ -226,8 +226,7 @@ void send_can_battery() { void setup_battery(void) { // Performs one time setup at startup strncpy(datalayer.system.info.battery_protocol, "I-Miev / C-Zero / Ion Triplet", sizeof(datalayer.system.info.battery_protocol) - 1); - datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = - '\0'; // Ensure null termination + datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = '\0'; datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV; datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV; diff --git a/Software/src/battery/JAGUAR-IPACE-BATTERY.cpp b/Software/src/battery/JAGUAR-IPACE-BATTERY.cpp index 3733810e..a8c2234d 100644 --- a/Software/src/battery/JAGUAR-IPACE-BATTERY.cpp +++ b/Software/src/battery/JAGUAR-IPACE-BATTERY.cpp @@ -255,8 +255,7 @@ void send_can_battery() { void setup_battery(void) { // Performs one time setup at startup strncpy(datalayer.system.info.battery_protocol, "Jaguar I-PACE", sizeof(datalayer.system.info.battery_protocol) - 1); - datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = - '\0'; // Ensure null termination + datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = '\0'; datalayer.battery.info.number_of_cells = 108; datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; diff --git a/Software/src/battery/KIA-E-GMP-BATTERY.cpp b/Software/src/battery/KIA-E-GMP-BATTERY.cpp index 0a810e9f..4ec47bbf 100644 --- a/Software/src/battery/KIA-E-GMP-BATTERY.cpp +++ b/Software/src/battery/KIA-E-GMP-BATTERY.cpp @@ -1039,8 +1039,7 @@ void send_can_battery() { void setup_battery(void) { // Performs one time setup at startup strncpy(datalayer.system.info.battery_protocol, "Kia/Hyundai EGMP platform", sizeof(datalayer.system.info.battery_protocol) - 1); - datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = - '\0'; // Ensure null termination + datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = '\0'; startMillis = millis(); // Record the starting time diff --git a/Software/src/battery/KIA-HYUNDAI-64-BATTERY.cpp b/Software/src/battery/KIA-HYUNDAI-64-BATTERY.cpp index b25268d6..989ed911 100644 --- a/Software/src/battery/KIA-HYUNDAI-64-BATTERY.cpp +++ b/Software/src/battery/KIA-HYUNDAI-64-BATTERY.cpp @@ -536,8 +536,7 @@ void send_can_battery() { void setup_battery(void) { // Performs one time setup at startup strncpy(datalayer.system.info.battery_protocol, "Kia/Hyundai 64/40kWh battery", sizeof(datalayer.system.info.battery_protocol) - 1); - datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = - '\0'; // Ensure null termination + datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = '\0'; datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_98S_DV; //Start with 98S value. Precised later datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_90S_DV; //Start with 90S value. Precised later datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV; diff --git a/Software/src/battery/KIA-HYUNDAI-HYBRID-BATTERY.cpp b/Software/src/battery/KIA-HYUNDAI-HYBRID-BATTERY.cpp index f6f88c52..030ed454 100644 --- a/Software/src/battery/KIA-HYUNDAI-HYBRID-BATTERY.cpp +++ b/Software/src/battery/KIA-HYUNDAI-HYBRID-BATTERY.cpp @@ -259,8 +259,7 @@ void send_can_battery() { void setup_battery(void) { // Performs one time setup at startup strncpy(datalayer.system.info.battery_protocol, "Kia/Hyundai Hybrid", sizeof(datalayer.system.info.battery_protocol) - 1); - datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = - '\0'; // Ensure null termination + datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = '\0'; datalayer.battery.info.number_of_cells = 56; // HEV , TODO: Make dynamic according to HEV/PHEV datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; diff --git a/Software/src/battery/MG-5-BATTERY.cpp b/Software/src/battery/MG-5-BATTERY.cpp index 94b8ec38..bb50dd50 100644 --- a/Software/src/battery/MG-5-BATTERY.cpp +++ b/Software/src/battery/MG-5-BATTERY.cpp @@ -136,8 +136,7 @@ void send_can_battery() { void setup_battery(void) { // Performs one time setup at startup strncpy(datalayer.system.info.battery_protocol, "MG 5 battery", sizeof(datalayer.system.info.battery_protocol) - 1); - datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = - '\0'; // Ensure null termination + datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = '\0'; datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV; diff --git a/Software/src/battery/NISSAN-LEAF-BATTERY.cpp b/Software/src/battery/NISSAN-LEAF-BATTERY.cpp index bb0d6f91..fde3b41c 100644 --- a/Software/src/battery/NISSAN-LEAF-BATTERY.cpp +++ b/Software/src/battery/NISSAN-LEAF-BATTERY.cpp @@ -1226,8 +1226,7 @@ uint16_t Temp_fromRAW_to_F(uint16_t temperature) { //This function feels horrib void setup_battery(void) { // Performs one time setup at startup strncpy(datalayer.system.info.battery_protocol, "Nissan LEAF battery", sizeof(datalayer.system.info.battery_protocol) - 1); - datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = - '\0'; // Ensure null termination + datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = '\0'; datalayer.battery.info.number_of_cells = 96; datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; diff --git a/Software/src/battery/PYLON-BATTERY.cpp b/Software/src/battery/PYLON-BATTERY.cpp index dfa0dfe0..a805e3b8 100644 --- a/Software/src/battery/PYLON-BATTERY.cpp +++ b/Software/src/battery/PYLON-BATTERY.cpp @@ -177,8 +177,7 @@ void send_can_battery() { void setup_battery(void) { // Performs one time setup at startup strncpy(datalayer.system.info.battery_protocol, "Pylon compatible battery", sizeof(datalayer.system.info.battery_protocol) - 1); - datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = - '\0'; // Ensure null termination + datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = '\0'; datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV; diff --git a/Software/src/battery/RANGE-ROVER-PHEV-BATTERY.cpp b/Software/src/battery/RANGE-ROVER-PHEV-BATTERY.cpp index ce6ea714..101d41a1 100644 --- a/Software/src/battery/RANGE-ROVER-PHEV-BATTERY.cpp +++ b/Software/src/battery/RANGE-ROVER-PHEV-BATTERY.cpp @@ -315,8 +315,7 @@ void send_can_battery() { void setup_battery(void) { // Performs one time setup at startup strncpy(datalayer.system.info.battery_protocol, "Range Rover 13kWh PHEV battery (L494/L405)", sizeof(datalayer.system.info.battery_protocol) - 1); - datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = - '\0'; // Ensure null termination + datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = '\0'; datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV; diff --git a/Software/src/battery/RENAULT-KANGOO-BATTERY.cpp b/Software/src/battery/RENAULT-KANGOO-BATTERY.cpp index f3d51887..1f7b5cb0 100644 --- a/Software/src/battery/RENAULT-KANGOO-BATTERY.cpp +++ b/Software/src/battery/RENAULT-KANGOO-BATTERY.cpp @@ -236,8 +236,7 @@ void send_can_battery() { void setup_battery(void) { // Performs one time setup at startup strncpy(datalayer.system.info.battery_protocol, "Renault Kangoo", sizeof(datalayer.system.info.battery_protocol) - 1); - datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = - '\0'; // Ensure null termination + datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = '\0'; datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV; diff --git a/Software/src/battery/RENAULT-TWIZY.cpp b/Software/src/battery/RENAULT-TWIZY.cpp index 74c9ea91..cce08880 100644 --- a/Software/src/battery/RENAULT-TWIZY.cpp +++ b/Software/src/battery/RENAULT-TWIZY.cpp @@ -133,8 +133,7 @@ void send_can_battery() { void setup_battery(void) { // Performs one time setup at startup strncpy(datalayer.system.info.battery_protocol, "Renault Twizy", sizeof(datalayer.system.info.battery_protocol) - 1); - datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = - '\0'; // Ensure null termination + datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = '\0'; datalayer.battery.info.number_of_cells = 14; datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV; diff --git a/Software/src/battery/RENAULT-ZOE-GEN1-BATTERY.cpp b/Software/src/battery/RENAULT-ZOE-GEN1-BATTERY.cpp index 27050919..ed451064 100644 --- a/Software/src/battery/RENAULT-ZOE-GEN1-BATTERY.cpp +++ b/Software/src/battery/RENAULT-ZOE-GEN1-BATTERY.cpp @@ -520,8 +520,7 @@ void send_can_battery() { void setup_battery(void) { // Performs one time setup at startup strncpy(datalayer.system.info.battery_protocol, "Renault Zoe Gen1 22/40kWh", sizeof(datalayer.system.info.battery_protocol) - 1); - datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = - '\0'; // Ensure null termination + datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = '\0'; datalayer.system.status.battery_allows_contactor_closing = true; datalayer.battery.info.number_of_cells = 96; datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; diff --git a/Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.cpp b/Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.cpp index bafbdaf3..fe7525bd 100644 --- a/Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.cpp +++ b/Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.cpp @@ -387,8 +387,7 @@ void send_can_battery() { void setup_battery(void) { // Performs one time setup at startup strncpy(datalayer.system.info.battery_protocol, "Renault Zoe Gen2 50kWh", sizeof(datalayer.system.info.battery_protocol) - 1); - datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = - '\0'; // Ensure null termination + datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = '\0'; datalayer.system.status.battery_allows_contactor_closing = true; datalayer.battery.info.number_of_cells = 96; datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; diff --git a/Software/src/battery/RJXZS-BMS.cpp b/Software/src/battery/RJXZS-BMS.cpp index 86b3d983..e9f4c070 100644 --- a/Software/src/battery/RJXZS-BMS.cpp +++ b/Software/src/battery/RJXZS-BMS.cpp @@ -572,8 +572,7 @@ void send_can_battery() { void setup_battery(void) { // Performs one time setup at startup strncpy(datalayer.system.info.battery_protocol, "RJXZS BMS, DIY battery", sizeof(datalayer.system.info.battery_protocol) - 1); - datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = - '\0'; // Ensure null termination + datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = '\0'; datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV; datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV; diff --git a/Software/src/battery/SANTA-FE-PHEV-BATTERY.cpp b/Software/src/battery/SANTA-FE-PHEV-BATTERY.cpp index 2180628e..39add8d9 100644 --- a/Software/src/battery/SANTA-FE-PHEV-BATTERY.cpp +++ b/Software/src/battery/SANTA-FE-PHEV-BATTERY.cpp @@ -403,8 +403,7 @@ uint8_t CalculateCRC8(CAN_frame rx_frame) { void setup_battery(void) { // Performs one time setup at startup strncpy(datalayer.system.info.battery_protocol, "Santa Fe PHEV", sizeof(datalayer.system.info.battery_protocol) - 1); - datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = - '\0'; // Ensure null termination + datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = '\0'; datalayer.battery.info.number_of_cells = 96; datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV; diff --git a/Software/src/battery/SERIAL-LINK-RECEIVER-FROM-BATTERY.cpp b/Software/src/battery/SERIAL-LINK-RECEIVER-FROM-BATTERY.cpp index 25a97ddd..141edead 100644 --- a/Software/src/battery/SERIAL-LINK-RECEIVER-FROM-BATTERY.cpp +++ b/Software/src/battery/SERIAL-LINK-RECEIVER-FROM-BATTERY.cpp @@ -221,8 +221,7 @@ void update_values_serial_link() { void setup_battery(void) { strncpy(datalayer.system.info.battery_protocol, "Serial link to another LilyGo board", sizeof(datalayer.system.info.battery_protocol) - 1); - datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = - '\0'; // Ensure null termination + datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = '\0'; } // Needed to make the compiler happy void update_values_battery() {} diff --git a/Software/src/battery/TESLA-BATTERY.cpp b/Software/src/battery/TESLA-BATTERY.cpp index 357864be..839c0335 100644 --- a/Software/src/battery/TESLA-BATTERY.cpp +++ b/Software/src/battery/TESLA-BATTERY.cpp @@ -1254,8 +1254,7 @@ void setup_battery(void) { // Performs one time setup at startup #ifdef TESLA_MODEL_SX_BATTERY // Always use NCM/A mode on S/X packs strncpy(datalayer.system.info.battery_protocol, "Tesla Model S/X", sizeof(datalayer.system.info.battery_protocol) - 1); - datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = - '\0'; // Ensure null termination + datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = '\0'; datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_SX_NCMA; datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_SX_NCMA; datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_NCA_NCM; @@ -1273,8 +1272,7 @@ void setup_battery(void) { // Performs one time setup at startup #ifdef TESLA_MODEL_3Y_BATTERY // Model 3/Y can be either LFP or NCM/A strncpy(datalayer.system.info.battery_protocol, "Tesla Model 3/Y", sizeof(datalayer.system.info.battery_protocol) - 1); - datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = - '\0'; // Ensure null termination + datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = '\0'; #ifdef LFP_CHEMISTRY datalayer.battery.info.chemistry = battery_chemistry_enum::LFP; datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_3Y_LFP; diff --git a/Software/src/battery/TEST-FAKE-BATTERY.cpp b/Software/src/battery/TEST-FAKE-BATTERY.cpp index c964eb54..bb49e0a2 100644 --- a/Software/src/battery/TEST-FAKE-BATTERY.cpp +++ b/Software/src/battery/TEST-FAKE-BATTERY.cpp @@ -147,8 +147,7 @@ void setup_battery(void) { // Performs one time setup at startup strncpy(datalayer.system.info.battery_protocol, "Fake battery for testing purposes", sizeof(datalayer.system.info.battery_protocol) - 1); - datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = - '\0'; // Ensure null termination + datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = '\0'; datalayer.battery.info.max_design_voltage_dV = 4040; // 404.4V, over this, charging is not possible (goes into forced discharge) diff --git a/Software/src/battery/VOLVO-SPA-BATTERY.cpp b/Software/src/battery/VOLVO-SPA-BATTERY.cpp index fc71de42..e94e8069 100644 --- a/Software/src/battery/VOLVO-SPA-BATTERY.cpp +++ b/Software/src/battery/VOLVO-SPA-BATTERY.cpp @@ -334,8 +334,7 @@ void send_can_battery() { void setup_battery(void) { // Performs one time setup at startup strncpy(datalayer.system.info.battery_protocol, "Volvo / Polestar 78kWh battery", sizeof(datalayer.system.info.battery_protocol) - 1); - datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = - '\0'; // Ensure null termination + datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = '\0'; datalayer.battery.info.number_of_cells = 108; datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV; From 8bba61405378f6db806485bb7cefcdfadc0e3f63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Thu, 14 Nov 2024 19:36:37 +0200 Subject: [PATCH 154/210] Add init function for inverters --- Software/Software.ino | 17 ++---------- Software/src/battery/BMW-I3-BATTERY.cpp | 4 +-- Software/src/battery/BMW-IX-BATTERY.cpp | 5 ++-- Software/src/battery/BYD-ATTO-3-BATTERY.cpp | 4 +-- Software/src/battery/CELLPOWER-BMS.cpp | 4 +-- Software/src/battery/CHADEMO-BATTERY.cpp | 5 ++-- .../src/battery/IMIEV-CZERO-ION-BATTERY.cpp | 5 ++-- Software/src/battery/JAGUAR-IPACE-BATTERY.cpp | 5 ++-- Software/src/battery/KIA-E-GMP-BATTERY.cpp | 5 ++-- .../src/battery/KIA-HYUNDAI-64-BATTERY.cpp | 5 ++-- .../battery/KIA-HYUNDAI-HYBRID-BATTERY.cpp | 5 ++-- Software/src/battery/MG-5-BATTERY.cpp | 4 +-- Software/src/battery/NISSAN-LEAF-BATTERY.cpp | 5 ++-- Software/src/battery/PYLON-BATTERY.cpp | 6 ++--- .../src/battery/RANGE-ROVER-PHEV-BATTERY.cpp | 6 ++--- .../src/battery/RENAULT-KANGOO-BATTERY.cpp | 4 +-- Software/src/battery/RENAULT-TWIZY.cpp | 4 +-- .../src/battery/RENAULT-ZOE-GEN1-BATTERY.cpp | 5 ++-- .../src/battery/RENAULT-ZOE-GEN2-BATTERY.cpp | 5 ++-- Software/src/battery/RJXZS-BMS.cpp | 5 ++-- .../src/battery/SANTA-FE-PHEV-BATTERY.cpp | 4 +-- .../SERIAL-LINK-RECEIVER-FROM-BATTERY.cpp | 5 ++-- Software/src/battery/TESLA-BATTERY.cpp | 10 +++---- Software/src/battery/TEST-FAKE-BATTERY.cpp | 5 ++-- Software/src/battery/VOLVO-SPA-BATTERY.cpp | 5 ++-- Software/src/devboard/webserver/webserver.cpp | 27 ------------------- Software/src/inverter/AFORE-CAN.cpp | 4 +++ Software/src/inverter/AFORE-CAN.h | 3 +-- Software/src/inverter/BYD-CAN.cpp | 4 +++ Software/src/inverter/BYD-CAN.h | 1 + Software/src/inverter/BYD-MODBUS.cpp | 4 +++ Software/src/inverter/BYD-MODBUS.h | 1 + Software/src/inverter/BYD-SMA.cpp | 6 +++++ Software/src/inverter/BYD-SMA.h | 1 + Software/src/inverter/FOXESS-CAN.cpp | 4 +++ Software/src/inverter/FOXESS-CAN.h | 1 + Software/src/inverter/PYLON-CAN.cpp | 4 +++ Software/src/inverter/PYLON-CAN.h | 1 + Software/src/inverter/PYLON-LV-CAN.cpp | 4 +++ Software/src/inverter/PYLON-LV-CAN.h | 1 + .../SERIAL-LINK-TRANSMITTER-INVERTER.cpp | 8 ++++++ .../SERIAL-LINK-TRANSMITTER-INVERTER.h | 1 + Software/src/inverter/SMA-CAN.cpp | 5 ++++ Software/src/inverter/SMA-CAN.h | 1 + Software/src/inverter/SMA-TRIPOWER-CAN.cpp | 5 ++++ Software/src/inverter/SMA-TRIPOWER-CAN.h | 1 + Software/src/inverter/SOFAR-CAN.cpp | 5 ++++ Software/src/inverter/SOFAR-CAN.h | 1 + Software/src/inverter/SOLAX-CAN.cpp | 5 ++++ Software/src/inverter/SOLAX-CAN.h | 1 + 50 files changed, 122 insertions(+), 114 deletions(-) diff --git a/Software/Software.ino b/Software/Software.ino index babef4dd..303442a8 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -555,26 +555,13 @@ void init_rs485() { } void init_inverter() { -#ifdef SOLAX_CAN - datalayer.system.status.inverter_allows_contactor_closing = false; // The inverter needs to allow first - intervalUpdateValues = 800; // This protocol also requires the values to be updated faster -#endif -#ifdef FOXESS_CAN - intervalUpdateValues = 950; // This protocol also requires the values to be updated faster -#endif -#ifdef BYD_SMA - datalayer.system.status.inverter_allows_contactor_closing = false; // The inverter needs to allow first - pinMode(INVERTER_CONTACTOR_ENABLE_PIN, INPUT); -#endif + // Inform user what inverter is used and perform setup + setup_inverter(); } void init_battery() { // Inform user what battery is used and perform setup setup_battery(); - -#ifdef CHADEMO_BATTERY - intervalUpdateValues = 800; // This mode requires the values to be updated faster -#endif } #ifdef EQUIPMENT_STOP_BUTTON diff --git a/Software/src/battery/BMW-I3-BATTERY.cpp b/Software/src/battery/BMW-I3-BATTERY.cpp index 2000558e..b15cb31e 100644 --- a/Software/src/battery/BMW-I3-BATTERY.cpp +++ b/Software/src/battery/BMW-I3-BATTERY.cpp @@ -1118,8 +1118,8 @@ void send_can_battery() { } void setup_battery(void) { // Performs one time setup at startup - strncpy(datalayer.system.info.battery_protocol, "BMW i3", sizeof(datalayer.system.info.battery_protocol) - 1); - datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = '\0'; + strncpy(datalayer.system.info.battery_protocol, "BMW i3", 63); + datalayer.system.info.battery_protocol[63] = '\0'; //Before we have started up and detected which battery is in use, use 60AH values datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_60AH; diff --git a/Software/src/battery/BMW-IX-BATTERY.cpp b/Software/src/battery/BMW-IX-BATTERY.cpp index 4a0846a5..8d9f358d 100644 --- a/Software/src/battery/BMW-IX-BATTERY.cpp +++ b/Software/src/battery/BMW-IX-BATTERY.cpp @@ -778,9 +778,8 @@ void send_can_battery() { //} //We can always send CAN as the iX BMS will wake up on vehicle comms void setup_battery(void) { // Performs one time setup at startup - strncpy(datalayer.system.info.battery_protocol, "BMW iX and i4-7 platform", - sizeof(datalayer.system.info.battery_protocol) - 1); - datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = '\0'; + strncpy(datalayer.system.info.battery_protocol, "BMW iX and i4-7 platform", 63); + datalayer.system.info.battery_protocol[63] = '\0'; //Before we have started up and detected which battery is in use, use 108S values datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; diff --git a/Software/src/battery/BYD-ATTO-3-BATTERY.cpp b/Software/src/battery/BYD-ATTO-3-BATTERY.cpp index 18040f9a..bfbc92f3 100644 --- a/Software/src/battery/BYD-ATTO-3-BATTERY.cpp +++ b/Software/src/battery/BYD-ATTO-3-BATTERY.cpp @@ -399,8 +399,8 @@ void send_can_battery() { } void setup_battery(void) { // Performs one time setup at startup - strncpy(datalayer.system.info.battery_protocol, "BYD Atto 3", sizeof(datalayer.system.info.battery_protocol) - 1); - datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = '\0'; + strncpy(datalayer.system.info.battery_protocol, "BYD Atto 3", 63); + datalayer.system.info.battery_protocol[63] = '\0'; datalayer.battery.info.number_of_cells = 126; datalayer.battery.info.chemistry = battery_chemistry_enum::LFP; datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; diff --git a/Software/src/battery/CELLPOWER-BMS.cpp b/Software/src/battery/CELLPOWER-BMS.cpp index d0b94943..80c981c6 100644 --- a/Software/src/battery/CELLPOWER-BMS.cpp +++ b/Software/src/battery/CELLPOWER-BMS.cpp @@ -333,8 +333,8 @@ void send_can_battery() { } void setup_battery(void) { // Performs one time setup at startup - strncpy(datalayer.system.info.battery_protocol, "Cellpower BMS", sizeof(datalayer.system.info.battery_protocol) - 1); - datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = '\0'; + strncpy(datalayer.system.info.battery_protocol, "Cellpower BMS", 63); + datalayer.system.info.battery_protocol[63] = '\0'; datalayer.system.status.battery_allows_contactor_closing = true; datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV; diff --git a/Software/src/battery/CHADEMO-BATTERY.cpp b/Software/src/battery/CHADEMO-BATTERY.cpp index afcc31a5..9e8d9e8c 100644 --- a/Software/src/battery/CHADEMO-BATTERY.cpp +++ b/Software/src/battery/CHADEMO-BATTERY.cpp @@ -1032,9 +1032,8 @@ void handle_chademo_sequence() { void setup_battery(void) { // Performs one time setup at startup - strncpy(datalayer.system.info.battery_protocol, "Chademo V2X mode", - sizeof(datalayer.system.info.battery_protocol) - 1); - datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = '\0'; + strncpy(datalayer.system.info.battery_protocol, "Chademo V2X mode", 63); + datalayer.system.info.battery_protocol[63] = '\0'; CHADEMO_Status = CHADEMO_IDLE; diff --git a/Software/src/battery/IMIEV-CZERO-ION-BATTERY.cpp b/Software/src/battery/IMIEV-CZERO-ION-BATTERY.cpp index c1ced536..05e6e7c6 100644 --- a/Software/src/battery/IMIEV-CZERO-ION-BATTERY.cpp +++ b/Software/src/battery/IMIEV-CZERO-ION-BATTERY.cpp @@ -224,9 +224,8 @@ void send_can_battery() { } void setup_battery(void) { // Performs one time setup at startup - strncpy(datalayer.system.info.battery_protocol, "I-Miev / C-Zero / Ion Triplet", - sizeof(datalayer.system.info.battery_protocol) - 1); - datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = '\0'; + strncpy(datalayer.system.info.battery_protocol, "I-Miev / C-Zero / Ion Triplet", 63); + datalayer.system.info.battery_protocol[63] = '\0'; datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV; datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV; diff --git a/Software/src/battery/JAGUAR-IPACE-BATTERY.cpp b/Software/src/battery/JAGUAR-IPACE-BATTERY.cpp index a8c2234d..eb13017c 100644 --- a/Software/src/battery/JAGUAR-IPACE-BATTERY.cpp +++ b/Software/src/battery/JAGUAR-IPACE-BATTERY.cpp @@ -254,9 +254,8 @@ void send_can_battery() { } void setup_battery(void) { // Performs one time setup at startup - strncpy(datalayer.system.info.battery_protocol, "Jaguar I-PACE", sizeof(datalayer.system.info.battery_protocol) - 1); - datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = '\0'; - + strncpy(datalayer.system.info.battery_protocol, "Jaguar I-PACE", 63); + datalayer.system.info.battery_protocol[63] = '\0'; datalayer.battery.info.number_of_cells = 108; datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV; diff --git a/Software/src/battery/KIA-E-GMP-BATTERY.cpp b/Software/src/battery/KIA-E-GMP-BATTERY.cpp index 4ec47bbf..a0715a26 100644 --- a/Software/src/battery/KIA-E-GMP-BATTERY.cpp +++ b/Software/src/battery/KIA-E-GMP-BATTERY.cpp @@ -1037,9 +1037,8 @@ void send_can_battery() { } void setup_battery(void) { // Performs one time setup at startup - strncpy(datalayer.system.info.battery_protocol, "Kia/Hyundai EGMP platform", - sizeof(datalayer.system.info.battery_protocol) - 1); - datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = '\0'; + strncpy(datalayer.system.info.battery_protocol, "Kia/Hyundai EGMP platform", 63); + datalayer.system.info.battery_protocol[63] = '\0'; startMillis = millis(); // Record the starting time diff --git a/Software/src/battery/KIA-HYUNDAI-64-BATTERY.cpp b/Software/src/battery/KIA-HYUNDAI-64-BATTERY.cpp index 989ed911..3a6663c6 100644 --- a/Software/src/battery/KIA-HYUNDAI-64-BATTERY.cpp +++ b/Software/src/battery/KIA-HYUNDAI-64-BATTERY.cpp @@ -534,9 +534,8 @@ void send_can_battery() { } void setup_battery(void) { // Performs one time setup at startup - strncpy(datalayer.system.info.battery_protocol, "Kia/Hyundai 64/40kWh battery", - sizeof(datalayer.system.info.battery_protocol) - 1); - datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = '\0'; + strncpy(datalayer.system.info.battery_protocol, "Kia/Hyundai 64/40kWh battery", 63); + datalayer.system.info.battery_protocol[63] = '\0'; datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_98S_DV; //Start with 98S value. Precised later datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_90S_DV; //Start with 90S value. Precised later datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV; diff --git a/Software/src/battery/KIA-HYUNDAI-HYBRID-BATTERY.cpp b/Software/src/battery/KIA-HYUNDAI-HYBRID-BATTERY.cpp index 030ed454..c37fcb15 100644 --- a/Software/src/battery/KIA-HYUNDAI-HYBRID-BATTERY.cpp +++ b/Software/src/battery/KIA-HYUNDAI-HYBRID-BATTERY.cpp @@ -257,9 +257,8 @@ void send_can_battery() { } void setup_battery(void) { // Performs one time setup at startup - strncpy(datalayer.system.info.battery_protocol, "Kia/Hyundai Hybrid", - sizeof(datalayer.system.info.battery_protocol) - 1); - datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = '\0'; + strncpy(datalayer.system.info.battery_protocol, "Kia/Hyundai Hybrid", 63); + datalayer.system.info.battery_protocol[63] = '\0'; datalayer.battery.info.number_of_cells = 56; // HEV , TODO: Make dynamic according to HEV/PHEV datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; diff --git a/Software/src/battery/MG-5-BATTERY.cpp b/Software/src/battery/MG-5-BATTERY.cpp index bb50dd50..35c8ee96 100644 --- a/Software/src/battery/MG-5-BATTERY.cpp +++ b/Software/src/battery/MG-5-BATTERY.cpp @@ -135,8 +135,8 @@ void send_can_battery() { } void setup_battery(void) { // Performs one time setup at startup - strncpy(datalayer.system.info.battery_protocol, "MG 5 battery", sizeof(datalayer.system.info.battery_protocol) - 1); - datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = '\0'; + strncpy(datalayer.system.info.battery_protocol, "MG 5 battery", 63); + datalayer.system.info.battery_protocol[63] = '\0'; datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV; diff --git a/Software/src/battery/NISSAN-LEAF-BATTERY.cpp b/Software/src/battery/NISSAN-LEAF-BATTERY.cpp index fde3b41c..79538070 100644 --- a/Software/src/battery/NISSAN-LEAF-BATTERY.cpp +++ b/Software/src/battery/NISSAN-LEAF-BATTERY.cpp @@ -1224,9 +1224,8 @@ uint16_t Temp_fromRAW_to_F(uint16_t temperature) { //This function feels horrib } void setup_battery(void) { // Performs one time setup at startup - strncpy(datalayer.system.info.battery_protocol, "Nissan LEAF battery", - sizeof(datalayer.system.info.battery_protocol) - 1); - datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = '\0'; + strncpy(datalayer.system.info.battery_protocol, "Nissan LEAF battery", 63); + datalayer.system.info.battery_protocol[63] = '\0'; datalayer.battery.info.number_of_cells = 96; datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; diff --git a/Software/src/battery/PYLON-BATTERY.cpp b/Software/src/battery/PYLON-BATTERY.cpp index a805e3b8..ec61901d 100644 --- a/Software/src/battery/PYLON-BATTERY.cpp +++ b/Software/src/battery/PYLON-BATTERY.cpp @@ -175,10 +175,8 @@ void send_can_battery() { } void setup_battery(void) { // Performs one time setup at startup - strncpy(datalayer.system.info.battery_protocol, "Pylon compatible battery", - sizeof(datalayer.system.info.battery_protocol) - 1); - datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = '\0'; - + strncpy(datalayer.system.info.battery_protocol, "Pylon compatible battery", 63); + datalayer.system.info.battery_protocol[63] = '\0'; datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV; datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV; diff --git a/Software/src/battery/RANGE-ROVER-PHEV-BATTERY.cpp b/Software/src/battery/RANGE-ROVER-PHEV-BATTERY.cpp index 101d41a1..4ca3a330 100644 --- a/Software/src/battery/RANGE-ROVER-PHEV-BATTERY.cpp +++ b/Software/src/battery/RANGE-ROVER-PHEV-BATTERY.cpp @@ -313,10 +313,8 @@ void send_can_battery() { } void setup_battery(void) { // Performs one time setup at startup - strncpy(datalayer.system.info.battery_protocol, "Range Rover 13kWh PHEV battery (L494/L405)", - sizeof(datalayer.system.info.battery_protocol) - 1); - datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = '\0'; - + strncpy(datalayer.system.info.battery_protocol, "Range Rover 13kWh PHEV battery (L494/L405)", 63); + datalayer.system.info.battery_protocol[63] = '\0'; datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV; datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV; diff --git a/Software/src/battery/RENAULT-KANGOO-BATTERY.cpp b/Software/src/battery/RENAULT-KANGOO-BATTERY.cpp index 1f7b5cb0..096cb629 100644 --- a/Software/src/battery/RENAULT-KANGOO-BATTERY.cpp +++ b/Software/src/battery/RENAULT-KANGOO-BATTERY.cpp @@ -235,8 +235,8 @@ void send_can_battery() { void setup_battery(void) { // Performs one time setup at startup - strncpy(datalayer.system.info.battery_protocol, "Renault Kangoo", sizeof(datalayer.system.info.battery_protocol) - 1); - datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = '\0'; + strncpy(datalayer.system.info.battery_protocol, "Renault Kangoo", 63); + datalayer.system.info.battery_protocol[63] = '\0'; datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV; diff --git a/Software/src/battery/RENAULT-TWIZY.cpp b/Software/src/battery/RENAULT-TWIZY.cpp index cce08880..b0140eff 100644 --- a/Software/src/battery/RENAULT-TWIZY.cpp +++ b/Software/src/battery/RENAULT-TWIZY.cpp @@ -132,8 +132,8 @@ void send_can_battery() { } void setup_battery(void) { // Performs one time setup at startup - strncpy(datalayer.system.info.battery_protocol, "Renault Twizy", sizeof(datalayer.system.info.battery_protocol) - 1); - datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = '\0'; + strncpy(datalayer.system.info.battery_protocol, "Renault Twizy", 63); + datalayer.system.info.battery_protocol[63] = '\0'; datalayer.battery.info.number_of_cells = 14; datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV; diff --git a/Software/src/battery/RENAULT-ZOE-GEN1-BATTERY.cpp b/Software/src/battery/RENAULT-ZOE-GEN1-BATTERY.cpp index ed451064..ef9f54f8 100644 --- a/Software/src/battery/RENAULT-ZOE-GEN1-BATTERY.cpp +++ b/Software/src/battery/RENAULT-ZOE-GEN1-BATTERY.cpp @@ -518,9 +518,8 @@ void send_can_battery() { } void setup_battery(void) { // Performs one time setup at startup - strncpy(datalayer.system.info.battery_protocol, "Renault Zoe Gen1 22/40kWh", - sizeof(datalayer.system.info.battery_protocol) - 1); - datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = '\0'; + strncpy(datalayer.system.info.battery_protocol, "Renault Zoe Gen1 22/40kWh", 63); + datalayer.system.info.battery_protocol[63] = '\0'; datalayer.system.status.battery_allows_contactor_closing = true; datalayer.battery.info.number_of_cells = 96; datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; diff --git a/Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.cpp b/Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.cpp index fe7525bd..08ac57bf 100644 --- a/Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.cpp +++ b/Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.cpp @@ -385,9 +385,8 @@ void send_can_battery() { } void setup_battery(void) { // Performs one time setup at startup - strncpy(datalayer.system.info.battery_protocol, "Renault Zoe Gen2 50kWh", - sizeof(datalayer.system.info.battery_protocol) - 1); - datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = '\0'; + strncpy(datalayer.system.info.battery_protocol, "Renault Zoe Gen2 50kWh", 63); + datalayer.system.info.battery_protocol[63] = '\0'; datalayer.system.status.battery_allows_contactor_closing = true; datalayer.battery.info.number_of_cells = 96; datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; diff --git a/Software/src/battery/RJXZS-BMS.cpp b/Software/src/battery/RJXZS-BMS.cpp index e9f4c070..85b96434 100644 --- a/Software/src/battery/RJXZS-BMS.cpp +++ b/Software/src/battery/RJXZS-BMS.cpp @@ -570,9 +570,8 @@ void send_can_battery() { } void setup_battery(void) { // Performs one time setup at startup - strncpy(datalayer.system.info.battery_protocol, "RJXZS BMS, DIY battery", - sizeof(datalayer.system.info.battery_protocol) - 1); - datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = '\0'; + strncpy(datalayer.system.info.battery_protocol, "RJXZS BMS, DIY battery", 63); + datalayer.system.info.battery_protocol[63] = '\0'; datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV; datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV; diff --git a/Software/src/battery/SANTA-FE-PHEV-BATTERY.cpp b/Software/src/battery/SANTA-FE-PHEV-BATTERY.cpp index 39add8d9..e6f27acc 100644 --- a/Software/src/battery/SANTA-FE-PHEV-BATTERY.cpp +++ b/Software/src/battery/SANTA-FE-PHEV-BATTERY.cpp @@ -402,8 +402,8 @@ uint8_t CalculateCRC8(CAN_frame rx_frame) { } void setup_battery(void) { // Performs one time setup at startup - strncpy(datalayer.system.info.battery_protocol, "Santa Fe PHEV", sizeof(datalayer.system.info.battery_protocol) - 1); - datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = '\0'; + strncpy(datalayer.system.info.battery_protocol, "Santa Fe PHEV", 63); + datalayer.system.info.battery_protocol[63] = '\0'; datalayer.battery.info.number_of_cells = 96; datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV; diff --git a/Software/src/battery/SERIAL-LINK-RECEIVER-FROM-BATTERY.cpp b/Software/src/battery/SERIAL-LINK-RECEIVER-FROM-BATTERY.cpp index 141edead..41697c33 100644 --- a/Software/src/battery/SERIAL-LINK-RECEIVER-FROM-BATTERY.cpp +++ b/Software/src/battery/SERIAL-LINK-RECEIVER-FROM-BATTERY.cpp @@ -219,9 +219,8 @@ void update_values_serial_link() { } void setup_battery(void) { - strncpy(datalayer.system.info.battery_protocol, "Serial link to another LilyGo board", - sizeof(datalayer.system.info.battery_protocol) - 1); - datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = '\0'; + strncpy(datalayer.system.info.battery_protocol, "Serial link to another LilyGo board", 63); + datalayer.system.info.battery_protocol[63] = '\0'; } // Needed to make the compiler happy void update_values_battery() {} diff --git a/Software/src/battery/TESLA-BATTERY.cpp b/Software/src/battery/TESLA-BATTERY.cpp index 839c0335..6e4ea6b1 100644 --- a/Software/src/battery/TESLA-BATTERY.cpp +++ b/Software/src/battery/TESLA-BATTERY.cpp @@ -1252,9 +1252,8 @@ void setup_battery(void) { // Performs one time setup at startup datalayer.system.status.battery_allows_contactor_closing = true; #ifdef TESLA_MODEL_SX_BATTERY // Always use NCM/A mode on S/X packs - strncpy(datalayer.system.info.battery_protocol, "Tesla Model S/X", - sizeof(datalayer.system.info.battery_protocol) - 1); - datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = '\0'; + strncpy(datalayer.system.info.battery_protocol, "Tesla Model S/X", 63); + datalayer.system.info.battery_protocol[63] = '\0'; datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_SX_NCMA; datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_SX_NCMA; datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_NCA_NCM; @@ -1270,9 +1269,8 @@ void setup_battery(void) { // Performs one time setup at startup #endif // TESLA_MODEL_SX_BATTERY #ifdef TESLA_MODEL_3Y_BATTERY // Model 3/Y can be either LFP or NCM/A - strncpy(datalayer.system.info.battery_protocol, "Tesla Model 3/Y", - sizeof(datalayer.system.info.battery_protocol) - 1); - datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = '\0'; + strncpy(datalayer.system.info.battery_protocol, "Tesla Model 3/Y", 63); + datalayer.system.info.battery_protocol[63] = '\0'; #ifdef LFP_CHEMISTRY datalayer.battery.info.chemistry = battery_chemistry_enum::LFP; datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_3Y_LFP; diff --git a/Software/src/battery/TEST-FAKE-BATTERY.cpp b/Software/src/battery/TEST-FAKE-BATTERY.cpp index bb49e0a2..e69ddfb2 100644 --- a/Software/src/battery/TEST-FAKE-BATTERY.cpp +++ b/Software/src/battery/TEST-FAKE-BATTERY.cpp @@ -145,9 +145,8 @@ void send_can_battery() { void setup_battery(void) { // Performs one time setup at startup randomSeed(analogRead(0)); - strncpy(datalayer.system.info.battery_protocol, "Fake battery for testing purposes", - sizeof(datalayer.system.info.battery_protocol) - 1); - datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = '\0'; + strncpy(datalayer.system.info.battery_protocol, "Fake battery for testing purposes", 63); + datalayer.system.info.battery_protocol[63] = '\0'; datalayer.battery.info.max_design_voltage_dV = 4040; // 404.4V, over this, charging is not possible (goes into forced discharge) diff --git a/Software/src/battery/VOLVO-SPA-BATTERY.cpp b/Software/src/battery/VOLVO-SPA-BATTERY.cpp index e94e8069..839665c8 100644 --- a/Software/src/battery/VOLVO-SPA-BATTERY.cpp +++ b/Software/src/battery/VOLVO-SPA-BATTERY.cpp @@ -332,9 +332,8 @@ void send_can_battery() { } void setup_battery(void) { // Performs one time setup at startup - strncpy(datalayer.system.info.battery_protocol, "Volvo / Polestar 78kWh battery", - sizeof(datalayer.system.info.battery_protocol) - 1); - datalayer.system.info.battery_protocol[sizeof(datalayer.system.info.battery_protocol) - 1] = '\0'; + strncpy(datalayer.system.info.battery_protocol, "Volvo / Polestar 78kWh battery", 63); + datalayer.system.info.battery_protocol[63] = '\0'; datalayer.battery.info.number_of_cells = 108; datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV; diff --git a/Software/src/devboard/webserver/webserver.cpp b/Software/src/devboard/webserver/webserver.cpp index b6de87a0..5c2cb2c1 100644 --- a/Software/src/devboard/webserver/webserver.cpp +++ b/Software/src/devboard/webserver/webserver.cpp @@ -487,33 +487,6 @@ String processor(const String& var) { // Display which components are used content += "

Inverter protocol: "; content += datalayer.system.info.inverter_protocol; -#ifdef BYD_CAN - content += "BYD Battery-Box Premium HVS over CAN Bus"; -#endif // BYD_CAN -#ifdef BYD_MODBUS - content += "BYD 11kWh HVM battery over Modbus RTU"; -#endif // BYD_MODBUS -#ifdef FOXESS_CAN - content += "FoxESS compatible HV2600/ECS4100 battery"; -#endif // FOXESS_CAN -#ifdef PYLON_CAN - content += "Pylontech battery over CAN bus"; -#endif // PYLON_CAN -#ifdef PYLON_LV_CAN - content += "Pylontech LV battery over CAN bus"; -#endif // PYLON_LV_CAN -#ifdef SERIAL_LINK_TRANSMITTER - content += "Serial link to another LilyGo board"; -#endif // SERIAL_LINK_TRANSMITTER -#ifdef SMA_CAN - content += "BYD Battery-Box H 8.9kWh, 7 mod over CAN bus"; -#endif // SMA_CAN -#ifdef SOFAR_CAN - content += "Sofar Energy Storage Inverter High Voltage BMS General Protocol (Extended Frame) over CAN bus"; -#endif // SOFAR_CAN -#ifdef SOLAX_CAN - content += "SolaX Triple Power LFP over CAN bus"; -#endif // SOLAX_CAN content += "

"; content += "

Battery protocol: "; content += datalayer.system.info.battery_protocol; diff --git a/Software/src/inverter/AFORE-CAN.cpp b/Software/src/inverter/AFORE-CAN.cpp index 74435116..a8d551d7 100644 --- a/Software/src/inverter/AFORE-CAN.cpp +++ b/Software/src/inverter/AFORE-CAN.cpp @@ -233,4 +233,8 @@ void send_can_inverter() { time_to_send_info = false; } } +void setup_inverter(void) { // Performs one time setup at startup over CAN bus + strncpy(datalayer.system.info.inverter_protocol, "Afore battery over CAN", 63); + datalayer.system.info.inverter_protocol[63] = '\0'; +} #endif diff --git a/Software/src/inverter/AFORE-CAN.h b/Software/src/inverter/AFORE-CAN.h index cc9f8548..9829befc 100644 --- a/Software/src/inverter/AFORE-CAN.h +++ b/Software/src/inverter/AFORE-CAN.h @@ -4,8 +4,7 @@ #define CAN_INVERTER_SELECTED -void send_system_data(); -void send_setup_info(); void transmit_can(CAN_frame* tx_frame, int interface); +void setup_inverter(void); #endif diff --git a/Software/src/inverter/BYD-CAN.cpp b/Software/src/inverter/BYD-CAN.cpp index ae0bb560..e11de744 100644 --- a/Software/src/inverter/BYD-CAN.cpp +++ b/Software/src/inverter/BYD-CAN.cpp @@ -219,4 +219,8 @@ void send_intial_data() { transmit_can(&BYD_3D0_2, can_config.inverter); transmit_can(&BYD_3D0_3, can_config.inverter); } +void setup_inverter(void) { // Performs one time setup at startup over CAN bus + strncpy(datalayer.system.info.inverter_protocol, "BYD Battery-Box Premium HVS over CAN Bus", 63); + datalayer.system.info.inverter_protocol[63] = '\0'; +} #endif diff --git a/Software/src/inverter/BYD-CAN.h b/Software/src/inverter/BYD-CAN.h index 6a45317b..5a90d6ba 100644 --- a/Software/src/inverter/BYD-CAN.h +++ b/Software/src/inverter/BYD-CAN.h @@ -8,5 +8,6 @@ void send_intial_data(); void transmit_can(CAN_frame* tx_frame, int interface); +void setup_inverter(void); #endif diff --git a/Software/src/inverter/BYD-MODBUS.cpp b/Software/src/inverter/BYD-MODBUS.cpp index 7cee6b8d..814e24e4 100644 --- a/Software/src/inverter/BYD-MODBUS.cpp +++ b/Software/src/inverter/BYD-MODBUS.cpp @@ -143,4 +143,8 @@ void verify_inverter_modbus() { history_index = (history_index + 1) % HISTORY_LENGTH; } } +void setup_inverter(void) { // Performs one time setup at startup over CAN bus + strncpy(datalayer.system.info.inverter_protocol, "BYD 11kWh HVM battery over Modbus RTU", 63); + datalayer.system.info.inverter_protocol[63] = '\0'; +} #endif diff --git a/Software/src/inverter/BYD-MODBUS.h b/Software/src/inverter/BYD-MODBUS.h index 3d014daa..487c9784 100644 --- a/Software/src/inverter/BYD-MODBUS.h +++ b/Software/src/inverter/BYD-MODBUS.h @@ -14,4 +14,5 @@ void verify_temperature_modbus(); void verify_inverter_modbus(); void handle_update_data_modbusp201_byd(); void handle_update_data_modbusp301_byd(); +void setup_inverter(void); #endif diff --git a/Software/src/inverter/BYD-SMA.cpp b/Software/src/inverter/BYD-SMA.cpp index 230b61e7..34e33063 100644 --- a/Software/src/inverter/BYD-SMA.cpp +++ b/Software/src/inverter/BYD-SMA.cpp @@ -251,4 +251,10 @@ void send_can_inverter() { } } } +void setup_inverter(void) { // Performs one time setup at startup over CAN bus + strncpy(datalayer.system.info.inverter_protocol, "BYD Battery-Box HVS over SMA CAN", 63); + datalayer.system.info.inverter_protocol[63] = '\0'; + datalayer.system.status.inverter_allows_contactor_closing = false; // The inverter needs to allow first + pinMode(INVERTER_CONTACTOR_ENABLE_PIN, INPUT); +} #endif diff --git a/Software/src/inverter/BYD-SMA.h b/Software/src/inverter/BYD-SMA.h index b71a60fa..e787191d 100644 --- a/Software/src/inverter/BYD-SMA.h +++ b/Software/src/inverter/BYD-SMA.h @@ -8,5 +8,6 @@ #define STOP_STATE 0x02 void transmit_can(CAN_frame* tx_frame, int interface); +void setup_inverter(void); #endif diff --git a/Software/src/inverter/FOXESS-CAN.cpp b/Software/src/inverter/FOXESS-CAN.cpp index e4b3b34c..e23f9006 100644 --- a/Software/src/inverter/FOXESS-CAN.cpp +++ b/Software/src/inverter/FOXESS-CAN.cpp @@ -737,4 +737,8 @@ void receive_can_inverter(CAN_frame rx_frame) { } } } +void setup_inverter(void) { // Performs one time setup at startup over CAN bus + strncpy(datalayer.system.info.inverter_protocol, "FoxESS compatible HV2600/ECS4100 battery", 63); + datalayer.system.info.inverter_protocol[63] = '\0'; +} #endif diff --git a/Software/src/inverter/FOXESS-CAN.h b/Software/src/inverter/FOXESS-CAN.h index d9a3da2d..365a559d 100644 --- a/Software/src/inverter/FOXESS-CAN.h +++ b/Software/src/inverter/FOXESS-CAN.h @@ -5,5 +5,6 @@ #define CAN_INVERTER_SELECTED void transmit_can(CAN_frame* tx_frame, int interface); +void setup_inverter(void); #endif diff --git a/Software/src/inverter/PYLON-CAN.cpp b/Software/src/inverter/PYLON-CAN.cpp index 2b8e036c..2145b3e7 100644 --- a/Software/src/inverter/PYLON-CAN.cpp +++ b/Software/src/inverter/PYLON-CAN.cpp @@ -477,4 +477,8 @@ void send_system_data() { //System equipment information transmit_can(&PYLON_4291, can_config.inverter); #endif } +void setup_inverter(void) { // Performs one time setup at startup over CAN bus + strncpy(datalayer.system.info.inverter_protocol, "Pylontech battery over CAN bus", 63); + datalayer.system.info.inverter_protocol[63] = '\0'; +} #endif diff --git a/Software/src/inverter/PYLON-CAN.h b/Software/src/inverter/PYLON-CAN.h index 86bb4afa..6b39afcd 100644 --- a/Software/src/inverter/PYLON-CAN.h +++ b/Software/src/inverter/PYLON-CAN.h @@ -7,5 +7,6 @@ void send_system_data(); void send_setup_info(); void transmit_can(CAN_frame* tx_frame, int interface); +void setup_inverter(void); #endif diff --git a/Software/src/inverter/PYLON-LV-CAN.cpp b/Software/src/inverter/PYLON-LV-CAN.cpp index 458ac7a0..e5eb2e87 100644 --- a/Software/src/inverter/PYLON-LV-CAN.cpp +++ b/Software/src/inverter/PYLON-LV-CAN.cpp @@ -133,4 +133,8 @@ void send_can_inverter() { transmit_can(&PYLON_35E, can_config.inverter); } } +void setup_inverter(void) { // Performs one time setup at startup over CAN bus + strncpy(datalayer.system.info.inverter_protocol, "Pylontech LV battery over CAN bus", 63); + datalayer.system.info.inverter_protocol[63] = '\0'; +} #endif diff --git a/Software/src/inverter/PYLON-LV-CAN.h b/Software/src/inverter/PYLON-LV-CAN.h index 45376d2b..ca6922eb 100644 --- a/Software/src/inverter/PYLON-LV-CAN.h +++ b/Software/src/inverter/PYLON-LV-CAN.h @@ -12,5 +12,6 @@ void send_system_data(); void send_setup_info(); void transmit_can(CAN_frame* tx_frame, int interface); +void setup_inverter(void); #endif diff --git a/Software/src/inverter/SERIAL-LINK-TRANSMITTER-INVERTER.cpp b/Software/src/inverter/SERIAL-LINK-TRANSMITTER-INVERTER.cpp index 6d3b5071..aa0a7ec4 100644 --- a/Software/src/inverter/SERIAL-LINK-TRANSMITTER-INVERTER.cpp +++ b/Software/src/inverter/SERIAL-LINK-TRANSMITTER-INVERTER.cpp @@ -159,6 +159,10 @@ void printSendingValues() { Serial.print(datalayer.battery.status.soh_pptt); Serial.print(" Voltage: "); Serial.print(datalayer.battery.status.voltage_dV); + void setup_inverter(void) { // Performs one time setup at startup over CAN bus + strncpy(datalayer.system.info.inverter_protocol, "Serial link to another LilyGo board", 63); + datalayer.system.info.inverter_protocol[63] = '\0'; + } Serial.print(" Current: "); Serial.print(datalayer.battery.status.current_dA); Serial.print(" Capacity: "); @@ -190,4 +194,8 @@ void printSendingValues() { Serial.println(""); } +void setup_inverter(void) { // Performs one time setup at startup over CAN bus + strncpy(datalayer.system.info.inverter_protocol, "Serial link to another LilyGo board", 63); + datalayer.system.info.inverter_protocol[63] = '\0'; +} #endif diff --git a/Software/src/inverter/SERIAL-LINK-TRANSMITTER-INVERTER.h b/Software/src/inverter/SERIAL-LINK-TRANSMITTER-INVERTER.h index 14ae08d7..1637e6bd 100644 --- a/Software/src/inverter/SERIAL-LINK-TRANSMITTER-INVERTER.h +++ b/Software/src/inverter/SERIAL-LINK-TRANSMITTER-INVERTER.h @@ -6,5 +6,6 @@ #include "../lib/mackelec-SerialDataLink/SerialDataLink.h" void manageSerialLinkTransmitter(); +void setup_inverter(void); #endif diff --git a/Software/src/inverter/SMA-CAN.cpp b/Software/src/inverter/SMA-CAN.cpp index cecae60c..5831406d 100644 --- a/Software/src/inverter/SMA-CAN.cpp +++ b/Software/src/inverter/SMA-CAN.cpp @@ -249,4 +249,9 @@ void send_can_inverter() { } } } + +void setup_inverter(void) { // Performs one time setup at startup over CAN bus + strncpy(datalayer.system.info.inverter_protocol, "SMA CAN", 63); + datalayer.system.info.inverter_protocol[63] = '\0'; +} #endif diff --git a/Software/src/inverter/SMA-CAN.h b/Software/src/inverter/SMA-CAN.h index 73b9f160..111044a4 100644 --- a/Software/src/inverter/SMA-CAN.h +++ b/Software/src/inverter/SMA-CAN.h @@ -8,5 +8,6 @@ #define STOP_STATE 0x02 void transmit_can(CAN_frame* tx_frame, int interface); +void setup_inverter(void); #endif diff --git a/Software/src/inverter/SMA-TRIPOWER-CAN.cpp b/Software/src/inverter/SMA-TRIPOWER-CAN.cpp index 9c0cc279..7a242dae 100644 --- a/Software/src/inverter/SMA-TRIPOWER-CAN.cpp +++ b/Software/src/inverter/SMA-TRIPOWER-CAN.cpp @@ -320,4 +320,9 @@ void send_tripower_init() { transmit_can(&SMA_017, can_config.inverter); // Battery Manufacturer transmit_can(&SMA_018, can_config.inverter); // Battery Name } + +void setup_inverter(void) { // Performs one time setup at startup over CAN bus + strncpy(datalayer.system.info.inverter_protocol, "SMA Tripower CAN", 63); + datalayer.system.info.inverter_protocol[63] = '\0'; +} #endif diff --git a/Software/src/inverter/SMA-TRIPOWER-CAN.h b/Software/src/inverter/SMA-TRIPOWER-CAN.h index e12692da..90967001 100644 --- a/Software/src/inverter/SMA-TRIPOWER-CAN.h +++ b/Software/src/inverter/SMA-TRIPOWER-CAN.h @@ -6,5 +6,6 @@ void send_tripower_init(); void transmit_can(CAN_frame* tx_frame, int interface); +void setup_inverter(void); #endif diff --git a/Software/src/inverter/SOFAR-CAN.cpp b/Software/src/inverter/SOFAR-CAN.cpp index eb13b7c5..838ebf93 100644 --- a/Software/src/inverter/SOFAR-CAN.cpp +++ b/Software/src/inverter/SOFAR-CAN.cpp @@ -263,4 +263,9 @@ void send_can_inverter() { transmit_can(&SOFAR_35A, can_config.inverter); } } + +void setup_inverter(void) { // Performs one time setup at startup over CAN bus + strncpy(datalayer.system.info.inverter_protocol, "Sofar BMS (Extended Frame) over CAN bus", 63); + datalayer.system.info.inverter_protocol[63] = '\0'; +} #endif diff --git a/Software/src/inverter/SOFAR-CAN.h b/Software/src/inverter/SOFAR-CAN.h index 6d43222f..7a80bf62 100644 --- a/Software/src/inverter/SOFAR-CAN.h +++ b/Software/src/inverter/SOFAR-CAN.h @@ -5,5 +5,6 @@ #define CAN_INVERTER_SELECTED void transmit_can(CAN_frame* tx_frame, int interface); +void setup_inverter(void); #endif diff --git a/Software/src/inverter/SOLAX-CAN.cpp b/Software/src/inverter/SOLAX-CAN.cpp index 790c8524..9bef56ec 100644 --- a/Software/src/inverter/SOLAX-CAN.cpp +++ b/Software/src/inverter/SOLAX-CAN.cpp @@ -252,4 +252,9 @@ void receive_can_inverter(CAN_frame rx_frame) { #endif } } +void setup_inverter(void) { // Performs one time setup at startup + strncpy(datalayer.system.info.inverter_protocol, "SolaX Triple Power LFP over CAN bus", 63); + datalayer.system.info.inverter_protocol[63] = '\0'; + datalayer.system.status.inverter_allows_contactor_closing = false; // The inverter needs to allow first +} #endif diff --git a/Software/src/inverter/SOLAX-CAN.h b/Software/src/inverter/SOLAX-CAN.h index 6ad461a7..68373ec2 100644 --- a/Software/src/inverter/SOLAX-CAN.h +++ b/Software/src/inverter/SOLAX-CAN.h @@ -15,5 +15,6 @@ #define UPDATING_FW 4 void transmit_can(CAN_frame* tx_frame, int interface); +void setup_inverter(void); #endif From 3b546d1466bf623e157a99b3845840eb033876da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Thu, 14 Nov 2024 21:24:49 +0200 Subject: [PATCH 155/210] Add all inv/bat to Github workflow --- .github/workflows/compile-all-batteries.yml | 13 ++++++------- .github/workflows/compile-all-inverters.yml | 2 ++ Software/src/devboard/webserver/webserver.cpp | 2 +- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/.github/workflows/compile-all-batteries.yml b/.github/workflows/compile-all-batteries.yml index 1cd8955e..287b75f2 100644 --- a/.github/workflows/compile-all-batteries.yml +++ b/.github/workflows/compile-all-batteries.yml @@ -34,19 +34,25 @@ jobs: # These are the batteries for which the code will be compiled. battery: - BMW_I3_BATTERY + - BMW_IX_BATTERY - BYD_ATTO_3_BATTERY - CELLPOWER_BMS - CHADEMO_BATTERY - IMIEV_CZERO_ION_BATTERY - JAGUAR_IPACE_BATTERY - KIA_HYUNDAI_64_BATTERY + - KIA_E_GMP_BATTERY - KIA_HYUNDAI_HYBRID_BATTERY + - MG_5_BATTERY - NISSAN_LEAF_BATTERY - PYLON_BATTERY - RJXZS_BMS + - RANGE_ROVER_PHEV_BATTERY - RENAULT_KANGOO_BATTERY + - RENAULT_TWIZY_BATTERY - RENAULT_ZOE_GEN1_BATTERY - RENAULT_ZOE_GEN2_BATTERY + - SANTA_FE_PHEV_BATTERY - TESLA_MODEL_3Y_BATTERY - VOLVO_SPA_BATTERY - TEST_FAKE_BATTERY @@ -54,13 +60,6 @@ jobs: # These are the emulated inverter communication protocols for which the code will be compiled. inverter: - BYD_CAN -# - BYD_MODBUS -# - PYLON_CAN -# - SMA_CAN -# - SMA_TRIPOWER_CAN -# - SOFAR_CAN -# - SOLAX_CAN - # This is the platform GitHub will use to run our workflow. runs-on: ubuntu-latest diff --git a/.github/workflows/compile-all-inverters.yml b/.github/workflows/compile-all-inverters.yml index 85beb384..deb0ccc9 100644 --- a/.github/workflows/compile-all-inverters.yml +++ b/.github/workflows/compile-all-inverters.yml @@ -42,10 +42,12 @@ jobs: # - TESLA_MODEL_3Y_BATTERY # These are the emulated inverter communication protocols for which the code will be compiled. inverter: + - AFORE_CAN - BYD_CAN - BYD_SMA - BYD_MODBUS - FOXESS_CAN + - PYLON_LV_CAN - PYLON_CAN - SMA_CAN - SMA_TRIPOWER_CAN diff --git a/Software/src/devboard/webserver/webserver.cpp b/Software/src/devboard/webserver/webserver.cpp index 5c2cb2c1..b7e36273 100644 --- a/Software/src/devboard/webserver/webserver.cpp +++ b/Software/src/devboard/webserver/webserver.cpp @@ -492,10 +492,10 @@ String processor(const String& var) { content += datalayer.system.info.battery_protocol; #ifdef DOUBLE_BATTERY content += " (Double battery)"; +#endif // DOUBLE_BATTERY if (datalayer.battery.info.chemistry == battery_chemistry_enum::LFP) { content += " (LFP)"; } -#endif // DOUBLE_BATTERY content += "

"; #if defined CHEVYVOLT_CHARGER || defined NISSANLEAF_CHARGER From 0b278034d419f3e73a3b788ee027c79925621882 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Thu, 14 Nov 2024 22:25:10 +0200 Subject: [PATCH 156/210] Remove compilation error, runtime event catches egmp CAN-FD --- Software/Software.ino | 25 +++++-------------- Software/src/include.h | 6 ----- .../SERIAL-LINK-TRANSMITTER-INVERTER.h | 2 ++ 3 files changed, 8 insertions(+), 25 deletions(-) diff --git a/Software/Software.ino b/Software/Software.ino index 303442a8..957d4537 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -70,13 +70,11 @@ volatile bool send_ok = 0; static const uint32_t QUARTZ_FREQUENCY = CRYSTAL_FREQUENCY_MHZ * 1000000UL; //MHZ configured in USER_SETTINGS.h ACAN2515 can(MCP2515_CS, SPI, MCP2515_INT); static ACAN2515_Buffer16 gBuffer; -#endif +#endif //DUAL_CAN #ifdef CAN_FD #include "src/lib/pierremolinaro-ACAN2517FD/ACAN2517FD.h" ACAN2517FD canfd(MCP2517_CS, SPI, MCP2517_INT); -#else -typedef char CANFDMessage; -#endif +#endif //CAN_FD // ModbusRTU parameters #ifdef MODBUS_INVERTER_SELECTED @@ -172,11 +170,10 @@ void setup() { init_rs485(); init_serialDataLink(); - - init_inverter(); - - init_battery(); - +#if defined(CAN_INVERTER_SELECTED) || defined(MODBUS_INVERTER_SELECTED) + setup_inverter(); +#endif + setup_battery(); #ifdef EQUIPMENT_STOP_BUTTON init_equipment_stop_button(); #endif @@ -554,16 +551,6 @@ void init_rs485() { #endif } -void init_inverter() { - // Inform user what inverter is used and perform setup - setup_inverter(); -} - -void init_battery() { - // Inform user what battery is used and perform setup - setup_battery(); -} - #ifdef EQUIPMENT_STOP_BUTTON void monitor_equipment_stop_button() { diff --git a/Software/src/include.h b/Software/src/include.h index 83f2290d..b29345e6 100644 --- a/Software/src/include.h +++ b/Software/src/include.h @@ -45,10 +45,4 @@ #error No battery selected! Choose one from the USER_SETTINGS.h file #endif -#ifdef KIA_E_GMP_BATTERY -#ifndef CAN_FD -#error KIA HYUNDAI EGMP BATTERIES CANNOT BE USED WITHOUT CAN FD -#endif -#endif - #endif diff --git a/Software/src/inverter/SERIAL-LINK-TRANSMITTER-INVERTER.h b/Software/src/inverter/SERIAL-LINK-TRANSMITTER-INVERTER.h index 1637e6bd..487ca103 100644 --- a/Software/src/inverter/SERIAL-LINK-TRANSMITTER-INVERTER.h +++ b/Software/src/inverter/SERIAL-LINK-TRANSMITTER-INVERTER.h @@ -1,6 +1,8 @@ #ifndef SERIAL_LINK_TRANSMITTER_INVERTER_H #define SERIAL_LINK_TRANSMITTER_INVERTER_H +#define MODBUS_INVERTER_SELECTED + #include #include "../include.h" #include "../lib/mackelec-SerialDataLink/SerialDataLink.h" From a537b7ff1b9060b69a95169381265788c03392d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Thu, 14 Nov 2024 22:33:35 +0200 Subject: [PATCH 157/210] Fix compilation issue for seriallink --- .../src/inverter/SERIAL-LINK-TRANSMITTER-INVERTER.cpp | 10 ++-------- .../src/inverter/SERIAL-LINK-TRANSMITTER-INVERTER.h | 2 -- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/Software/src/inverter/SERIAL-LINK-TRANSMITTER-INVERTER.cpp b/Software/src/inverter/SERIAL-LINK-TRANSMITTER-INVERTER.cpp index aa0a7ec4..a61ea7dc 100644 --- a/Software/src/inverter/SERIAL-LINK-TRANSMITTER-INVERTER.cpp +++ b/Software/src/inverter/SERIAL-LINK-TRANSMITTER-INVERTER.cpp @@ -128,6 +128,8 @@ void manageSerialLinkTransmitter() { static unsigned long updateDataTime = 0; if (currentTime - updateDataTime > INTERVAL_1_S) { + strncpy(datalayer.system.info.inverter_protocol, "Serial link to another LilyGo board", 63); + datalayer.system.info.inverter_protocol[63] = '\0'; updateDataTime = currentTime; dataLinkTransmit.updateData(0, datalayer.battery.status.real_soc); dataLinkTransmit.updateData(1, datalayer.battery.status.soh_pptt); @@ -159,10 +161,6 @@ void printSendingValues() { Serial.print(datalayer.battery.status.soh_pptt); Serial.print(" Voltage: "); Serial.print(datalayer.battery.status.voltage_dV); - void setup_inverter(void) { // Performs one time setup at startup over CAN bus - strncpy(datalayer.system.info.inverter_protocol, "Serial link to another LilyGo board", 63); - datalayer.system.info.inverter_protocol[63] = '\0'; - } Serial.print(" Current: "); Serial.print(datalayer.battery.status.current_dA); Serial.print(" Capacity: "); @@ -194,8 +192,4 @@ void printSendingValues() { Serial.println(""); } -void setup_inverter(void) { // Performs one time setup at startup over CAN bus - strncpy(datalayer.system.info.inverter_protocol, "Serial link to another LilyGo board", 63); - datalayer.system.info.inverter_protocol[63] = '\0'; -} #endif diff --git a/Software/src/inverter/SERIAL-LINK-TRANSMITTER-INVERTER.h b/Software/src/inverter/SERIAL-LINK-TRANSMITTER-INVERTER.h index 487ca103..1637e6bd 100644 --- a/Software/src/inverter/SERIAL-LINK-TRANSMITTER-INVERTER.h +++ b/Software/src/inverter/SERIAL-LINK-TRANSMITTER-INVERTER.h @@ -1,8 +1,6 @@ #ifndef SERIAL_LINK_TRANSMITTER_INVERTER_H #define SERIAL_LINK_TRANSMITTER_INVERTER_H -#define MODBUS_INVERTER_SELECTED - #include #include "../include.h" #include "../lib/mackelec-SerialDataLink/SerialDataLink.h" From 5d9105d9a85dd6c36f874b770b2c7e725fc21bde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Thu, 14 Nov 2024 22:54:19 +0200 Subject: [PATCH 158/210] Add more BYD inverter mappings --- Software/src/inverter/BYD-CAN.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Software/src/inverter/BYD-CAN.cpp b/Software/src/inverter/BYD-CAN.cpp index ae0bb560..58dc00e4 100644 --- a/Software/src/inverter/BYD-CAN.cpp +++ b/Software/src/inverter/BYD-CAN.cpp @@ -76,6 +76,8 @@ static uint8_t inverter_name[7] = {0}; static int16_t temperature_average = 0; static uint16_t inverter_voltage = 0; static uint16_t inverter_SOC = 0; +static int16_t inverter_current = 0; +static int16_t inverter_temperature = 0; static uint16_t remaining_capacity_ah = 0; static uint16_t fully_charged_capacity_ah = 0; static long inverter_timestamp = 0; @@ -165,6 +167,8 @@ void receive_can_inverter(CAN_frame rx_frame) { case 0x091: datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE; inverter_voltage = ((rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]) * 0.1; + inverter_current = ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]) * 0.1; + inverter_temperature = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 0.1; break; case 0x0D1: datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE; From 7f16ee6fb033b3d75d01b91a19fe4bc16a713529 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Fri, 15 Nov 2024 20:07:19 +0200 Subject: [PATCH 159/210] Wrap digitalwrite and PWMwrite in own function --- Software/Software.ino | 105 ++++++++++++++++++++------------------- Software/USER_SETTINGS.h | 1 + 2 files changed, 56 insertions(+), 50 deletions(-) diff --git a/Software/Software.ino b/Software/Software.ino index babef4dd..bba1d277 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -120,6 +120,16 @@ MyTimer check_pause_2s(INTERVAL_2_S); enum State { DISCONNECTED, PRECHARGE, NEGATIVE, POSITIVE, PRECHARGE_OFF, COMPLETED, SHUTDOWN_REQUESTED }; State contactorStatus = DISCONNECTED; +#define ON 1 +#define OFF 0 + +#ifdef NC_CONTACTORS //Normally closed contactors use inverted logic + #undef ON + #define ON 0 + #undef OFF + #define OFF 1 +#endif + #define MAX_ALLOWED_FAULT_TICKS 1000 /* NOTE: modify the precharge time constant below to account for the resistance and capacitance of the target system. * t=3RC at minimum, t=5RC ideally @@ -127,20 +137,34 @@ State contactorStatus = DISCONNECTED; #define PRECHARGE_TIME_MS 160 #define NEGATIVE_CONTACTOR_TIME_MS 1000 #define POSITIVE_CONTACTOR_TIME_MS 2000 -#ifdef PWM_CONTACTOR_CONTROL #define PWM_Freq 20000 // 20 kHz frequency, beyond audible range #define PWM_Res 10 // 10 Bit resolution 0 to 1023, maps 'nicely' to 0% 100% -#define PWM_Hold_Duty 250 -#define PWM_Off_Duty 0 -#define PWM_On_Duty 1023 +#define PWM_HOLD_DUTY 250 +#define PWM_OFF_DUTY 0 +#define PWM_ON_DUTY 1023 #define POSITIVE_PWM_Ch 0 #define NEGATIVE_PWM_Ch 1 -#endif unsigned long prechargeStartTime = 0; unsigned long negativeStartTime = 0; unsigned long timeSpentInFaultedMode = 0; #endif +void set(uint8_t pin, bool direction, uint32_t pwm_freq = 0xFFFFFFFFFF){ + #ifdef PWM_CONTACTOR_CONTROL + if(pwm_freq != 0xFFFFFFFFFF) + { + ledcWrite(pin, pwm_freq); + return; + } + #endif + if(direction == 1){ + digitalWrite(pin, HIGH); + } + else{ // 0 + digitalWrite(pin, LOW); + } +} + #ifdef EQUIPMENT_STOP_BUTTON const unsigned long equipment_button_long_press_duration = 15000; // 15 seconds for long press in case of MOMENTARY_SWITCH @@ -497,27 +521,27 @@ void init_CAN() { void init_contactors() { // Init contactor pins #ifdef CONTACTOR_CONTROL -#ifndef PWM_CONTACTOR_CONTROL - pinMode(POSITIVE_CONTACTOR_PIN, OUTPUT); - digitalWrite(POSITIVE_CONTACTOR_PIN, LOW); - pinMode(NEGATIVE_CONTACTOR_PIN, OUTPUT); - digitalWrite(NEGATIVE_CONTACTOR_PIN, LOW); -#else +#ifdef PWM_CONTACTOR_CONTROL ledcAttachChannel(POSITIVE_CONTACTOR_PIN, PWM_Freq, PWM_Res, POSITIVE_PWM_Ch); // Setup PWM Channel Frequency and Resolution ledcAttachChannel(NEGATIVE_CONTACTOR_PIN, PWM_Freq, PWM_Res, NEGATIVE_PWM_Ch); // Setup PWM Channel Frequency and Resolution - ledcWrite(POSITIVE_CONTACTOR_PIN, PWM_Off_Duty); // Set Positive PWM to 0% - ledcWrite(NEGATIVE_CONTACTOR_PIN, PWM_Off_Duty); // Set Negative PWM to 0% + ledcWrite(POSITIVE_CONTACTOR_PIN, PWM_OFF_DUTY); // Set Positive PWM to 0% + ledcWrite(NEGATIVE_CONTACTOR_PIN, PWM_OFF_DUTY); // Set Negative PWM to 0% +#else //Normal CONTACTOR_CONTROL + pinMode(POSITIVE_CONTACTOR_PIN, OUTPUT); + set(POSITIVE_CONTACTOR_PIN, OFF); + pinMode(NEGATIVE_CONTACTOR_PIN, OUTPUT); + set(NEGATIVE_CONTACTOR_PIN, OFF); #endif pinMode(PRECHARGE_PIN, OUTPUT); - digitalWrite(PRECHARGE_PIN, LOW); -#endif + set(PRECHARGE_PIN, OFF); +#endif //CONTACTOR_CONTROL // Init BMS contactor #ifdef HW_STARK // TODO: Rewrite this so LilyGo can also handle this BMS contactor pinMode(BMS_POWER, OUTPUT); - digitalWrite(BMS_POWER, HIGH); -#endif + set(BMS_POWER, ON); +#endif //HW_STARK } void init_rs485() { @@ -751,14 +775,9 @@ void handle_contactors() { contactorStatus = DISCONNECTED; } if (contactorStatus == SHUTDOWN_REQUESTED) { - digitalWrite(PRECHARGE_PIN, LOW); -#ifndef PWM_CONTACTOR_CONTROL - digitalWrite(NEGATIVE_CONTACTOR_PIN, LOW); - digitalWrite(POSITIVE_CONTACTOR_PIN, LOW); -#else - ledcWrite(NEGATIVE_CONTACTOR_PIN, PWM_Off_Duty); - ledcWrite(POSITIVE_CONTACTOR_PIN, PWM_Off_Duty); -#endif + set(PRECHARGE_PIN, OFF); + set(NEGATIVE_CONTACTOR_PIN, OFF, PWM_OFF_DUTY); + set(POSITIVE_CONTACTOR_PIN, OFF, PWM_OFF_DUTY); set_event(EVENT_ERROR_OPEN_CONTACTOR, 0); datalayer.system.status.contactor_control_closed = false; return; // A fault scenario latches the contactor control. It is not possible to recover without a powercycle (and investigation why fault occured) @@ -766,14 +785,9 @@ void handle_contactors() { // After that, check if we are OK to start turning on the battery if (contactorStatus == DISCONNECTED) { - digitalWrite(PRECHARGE_PIN, LOW); -#ifndef PWM_CONTACTOR_CONTROL - digitalWrite(NEGATIVE_CONTACTOR_PIN, LOW); - digitalWrite(POSITIVE_CONTACTOR_PIN, LOW); -#else - ledcWrite(NEGATIVE_CONTACTOR_PIN, PWM_Off_Duty); - ledcWrite(POSITIVE_CONTACTOR_PIN, PWM_Off_Duty); -#endif + set(PRECHARGE_PIN, OFF); + set(NEGATIVE_CONTACTOR_PIN, OFF, PWM_OFF_DUTY); + set(POSITIVE_CONTACTOR_PIN, OFF, PWM_OFF_DUTY); if (datalayer.system.status.battery_allows_contactor_closing && datalayer.system.status.inverter_allows_contactor_closing) { @@ -783,8 +797,9 @@ void handle_contactors() { // In case the inverter requests contactors to open, set the state accordingly if (contactorStatus == COMPLETED) { - if (!datalayer.system.status.inverter_allows_contactor_closing) + if (!datalayer.system.status.inverter_allows_contactor_closing){ contactorStatus = DISCONNECTED; + } // Skip running the state machine below if it has already completed return; } @@ -793,18 +808,14 @@ void handle_contactors() { // Handle actual state machine. This first turns on Precharge, then Negative, then Positive, and finally turns OFF precharge switch (contactorStatus) { case PRECHARGE: - digitalWrite(PRECHARGE_PIN, HIGH); + set(PRECHARGE_PIN, ON); prechargeStartTime = currentTime; contactorStatus = NEGATIVE; break; case NEGATIVE: if (currentTime - prechargeStartTime >= PRECHARGE_TIME_MS) { -#ifndef PWM_CONTACTOR_CONTROL - digitalWrite(NEGATIVE_CONTACTOR_PIN, HIGH); -#else - ledcWrite(NEGATIVE_CONTACTOR_PIN, PWM_On_Duty); -#endif + set(NEGATIVE_CONTACTOR_PIN, ON, PWM_ON_DUTY); negativeStartTime = currentTime; contactorStatus = POSITIVE; } @@ -812,22 +823,16 @@ void handle_contactors() { case POSITIVE: if (currentTime - negativeStartTime >= NEGATIVE_CONTACTOR_TIME_MS) { -#ifndef PWM_CONTACTOR_CONTROL - digitalWrite(POSITIVE_CONTACTOR_PIN, HIGH); -#else - ledcWrite(POSITIVE_CONTACTOR_PIN, PWM_On_Duty); -#endif + set(POSITIVE_CONTACTOR_PIN, ON, PWM_ON_DUTY); contactorStatus = PRECHARGE_OFF; } break; case PRECHARGE_OFF: if (currentTime - negativeStartTime >= POSITIVE_CONTACTOR_TIME_MS) { - digitalWrite(PRECHARGE_PIN, LOW); -#ifdef PWM_CONTACTOR_CONTROL - ledcWrite(NEGATIVE_CONTACTOR_PIN, PWM_Hold_Duty); - ledcWrite(POSITIVE_CONTACTOR_PIN, PWM_Hold_Duty); -#endif + set(PRECHARGE_PIN, OFF); + set(NEGATIVE_CONTACTOR_PIN, PWM_HOLD_DUTY); + set(POSITIVE_CONTACTOR_PIN, PWM_HOLD_DUTY); contactorStatus = COMPLETED; datalayer.system.status.contactor_control_closed = true; } diff --git a/Software/USER_SETTINGS.h b/Software/USER_SETTINGS.h index c6351a12..be9dfece 100644 --- a/Software/USER_SETTINGS.h +++ b/Software/USER_SETTINGS.h @@ -59,6 +59,7 @@ //#define INTERLOCK_REQUIRED //Nissan LEAF specific setting, if enabled requires both high voltage conenctors to be seated before starting //#define CONTACTOR_CONTROL //Enable this line to have pins 25,32,33 handle automatic precharge/contactor+/contactor- closing sequence //#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 DUAL_CAN //Enable this line to activate an isolated secondary CAN Bus using add-on MCP2515 chip (Needed for some inverters / double battery) #define CRYSTAL_FREQUENCY_MHZ 8 //DUAL_CAN option, what is your MCP2515 add-on boards crystal frequency? //#define CAN_FD //Enable this line to activate an isolated secondary CAN-FD bus using add-on MCP2518FD chip / Native CANFD on Stark board From cb4a830a5730df3ce6fefb7c313fc1f3b38d7d68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Fri, 15 Nov 2024 20:54:37 +0200 Subject: [PATCH 160/210] Add second contactor closing function --- Software/Software.ino | 53 +++++++++++++++++++----------- Software/USER_SETTINGS.h | 9 +++-- Software/src/devboard/hal/hw_3LB.h | 6 ++-- 3 files changed, 42 insertions(+), 26 deletions(-) diff --git a/Software/Software.ino b/Software/Software.ino index bba1d277..234fd5ab 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -123,11 +123,11 @@ State contactorStatus = DISCONNECTED; #define ON 1 #define OFF 0 -#ifdef NC_CONTACTORS //Normally closed contactors use inverted logic - #undef ON - #define ON 0 - #undef OFF - #define OFF 1 +#ifdef NC_CONTACTORS //Normally closed contactors use inverted logic +#undef ON +#define ON 0 +#undef OFF +#define OFF 1 #endif #define MAX_ALLOWED_FAULT_TICKS 1000 @@ -149,18 +149,16 @@ unsigned long negativeStartTime = 0; unsigned long timeSpentInFaultedMode = 0; #endif -void set(uint8_t pin, bool direction, uint32_t pwm_freq = 0xFFFFFFFFFF){ - #ifdef PWM_CONTACTOR_CONTROL - if(pwm_freq != 0xFFFFFFFFFF) - { +void set(uint8_t pin, bool direction, uint32_t pwm_freq = 0xFFFFFFFFFF) { +#ifdef PWM_CONTACTOR_CONTROL + if (pwm_freq != 0xFFFFFFFFFF) { ledcWrite(pin, pwm_freq); return; } - #endif - if(direction == 1){ +#endif + if (direction == 1) { digitalWrite(pin, HIGH); - } - else{ // 0 + } else { // 0 digitalWrite(pin, LOW); } } @@ -528,7 +526,7 @@ void init_contactors() { NEGATIVE_PWM_Ch); // Setup PWM Channel Frequency and Resolution ledcWrite(POSITIVE_CONTACTOR_PIN, PWM_OFF_DUTY); // Set Positive PWM to 0% ledcWrite(NEGATIVE_CONTACTOR_PIN, PWM_OFF_DUTY); // Set Negative PWM to 0% -#else //Normal CONTACTOR_CONTROL +#else //Normal CONTACTOR_CONTROL pinMode(POSITIVE_CONTACTOR_PIN, OUTPUT); set(POSITIVE_CONTACTOR_PIN, OFF); pinMode(NEGATIVE_CONTACTOR_PIN, OUTPUT); @@ -536,12 +534,18 @@ void init_contactors() { #endif pinMode(PRECHARGE_PIN, OUTPUT); set(PRECHARGE_PIN, OFF); -#endif //CONTACTOR_CONTROL +#endif //CONTACTOR_CONTROL +#ifdef CONTACTOR_CONTROL_DOUBLE_BATTERY + pinMode(SECOND_POSITIVE_CONTACTOR_PIN, OUTPUT); + set(SECOND_POSITIVE_CONTACTOR_PIN, OFF); + pinMode(SECOND_NEGATIVE_CONTACTOR_PIN, OUTPUT); + set(SECOND_NEGATIVE_CONTACTOR_PIN, OFF); +#endif //CONTACTOR_CONTROL_DOUBLE_BATTERY // Init BMS contactor #ifdef HW_STARK // TODO: Rewrite this so LilyGo can also handle this BMS contactor pinMode(BMS_POWER, OUTPUT); - set(BMS_POWER, ON); -#endif //HW_STARK + digitalWrite(BMS_POWER, HIGH); +#endif //HW_STARK } void init_rs485() { @@ -797,9 +801,18 @@ void handle_contactors() { // In case the inverter requests contactors to open, set the state accordingly if (contactorStatus == COMPLETED) { - if (!datalayer.system.status.inverter_allows_contactor_closing){ + if (!datalayer.system.status.inverter_allows_contactor_closing) { contactorStatus = DISCONNECTED; } +#ifdef CONTACTOR_CONTROL_DOUBLE_BATTERY + if (datalayer.system.status.battery2_allows_contactor_closing) { + set(SECOND_NEGATIVE_CONTACTOR_PIN, ON); + set(SECOND_POSITIVE_CONTACTOR_PIN, ON); + } else { + set(SECOND_NEGATIVE_CONTACTOR_PIN, OFF); + set(SECOND_POSITIVE_CONTACTOR_PIN, OFF); + } +#endif //CONTACTOR_CONTROL_DOUBLE_BATTERY // Skip running the state machine below if it has already completed return; } @@ -831,8 +844,8 @@ void handle_contactors() { case PRECHARGE_OFF: if (currentTime - negativeStartTime >= POSITIVE_CONTACTOR_TIME_MS) { set(PRECHARGE_PIN, OFF); - set(NEGATIVE_CONTACTOR_PIN, PWM_HOLD_DUTY); - set(POSITIVE_CONTACTOR_PIN, PWM_HOLD_DUTY); + set(NEGATIVE_CONTACTOR_PIN, ON, PWM_HOLD_DUTY); + set(POSITIVE_CONTACTOR_PIN, ON, PWM_HOLD_DUTY); contactorStatus = COMPLETED; datalayer.system.status.contactor_control_closed = true; } diff --git a/Software/USER_SETTINGS.h b/Software/USER_SETTINGS.h index be9dfece..392bd661 100644 --- a/Software/USER_SETTINGS.h +++ b/Software/USER_SETTINGS.h @@ -53,13 +53,16 @@ //#define HW_STARK //#define HW_3LB +/* Contactor settings. If you have a battery that does not activate contactors via CAN, configure this section */ +//#define CONTACTOR_CONTROL //Enable this line to have the emulator handle automatic precharge/contactor+/contactor- closing sequence (See wiki for pins) +//#define CONTACTOR_CONTROL_DOUBLE_BATTERY //Enable this line to have the emulator hardware control secondary set of contactors for double battery setups (See wiki for pins) +//#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! + /* Other options */ //#define DEBUG_VIA_USB //Enable this line to have the USB port output serial diagnostic data while program runs (WARNING, raises CPU load, do not use for production) //#define DEBUG_CAN_DATA //Enable this line to print incoming/outgoing CAN & CAN-FD messages to USB serial (WARNING, raises CPU load, do not use for production) //#define INTERLOCK_REQUIRED //Nissan LEAF specific setting, if enabled requires both high voltage conenctors to be seated before starting -//#define CONTACTOR_CONTROL //Enable this line to have pins 25,32,33 handle automatic precharge/contactor+/contactor- closing sequence -//#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 DUAL_CAN //Enable this line to activate an isolated secondary CAN Bus using add-on MCP2515 chip (Needed for some inverters / double battery) #define CRYSTAL_FREQUENCY_MHZ 8 //DUAL_CAN option, what is your MCP2515 add-on boards crystal frequency? //#define CAN_FD //Enable this line to activate an isolated secondary CAN-FD bus using add-on MCP2518FD chip / Native CANFD on Stark board diff --git a/Software/src/devboard/hal/hw_3LB.h b/Software/src/devboard/hal/hw_3LB.h index 137d83f3..a54de2a4 100644 --- a/Software/src/devboard/hal/hw_3LB.h +++ b/Software/src/devboard/hal/hw_3LB.h @@ -52,9 +52,9 @@ #define NEGATIVE_CONTACTOR_PIN 33 #define PRECHARGE_PIN 25 -#define 2ND_POSITIVE_CONTACTOR_PIN 13 -#define 2ND_NEGATIVE_CONTACTOR_PIN 16 -#define 2ND_PRECHARGE_PIN 18 +#define SECOND_POSITIVE_CONTACTOR_PIN 13 +#define SECOND_NEGATIVE_CONTACTOR_PIN 16 +#define SECOND_PRECHARGE_PIN 18 // SMA CAN contactor pins #define INVERTER_CONTACTOR_ENABLE_PIN 36 From 115e7db33fe69fa4c1400dad3fbee592681e764b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Fri, 15 Nov 2024 22:58:36 +0200 Subject: [PATCH 161/210] Revise commands from testing round --- Software/src/battery/NISSAN-LEAF-BATTERY.cpp | 74 ++++++------------- Software/src/battery/NISSAN-LEAF-BATTERY.h | 10 +-- .../webserver/advanced_battery_html.cpp | 2 +- 3 files changed, 30 insertions(+), 56 deletions(-) diff --git a/Software/src/battery/NISSAN-LEAF-BATTERY.cpp b/Software/src/battery/NISSAN-LEAF-BATTERY.cpp index d5e78809..06f976a0 100644 --- a/Software/src/battery/NISSAN-LEAF-BATTERY.cpp +++ b/Software/src/battery/NISSAN-LEAF-BATTERY.cpp @@ -884,20 +884,7 @@ void receive_can_battery(CAN_frame rx_frame) { //Error checking if ((rx_frame.data.u8[0] == 0x03) && (rx_frame.data.u8[1] == 0x7F)) { challengeFailed = true; - Serial.print("Challenge solving failed"); } - // All CAN messages recieved from BMS will be logged via serial during development of this function - Serial.print(millis()); // Example printout, time, ID, length, data: 7553 1DB 8 FF C0 B9 EA 0 0 2 5D - Serial.print(" "); - Serial.print(rx_frame.ID, HEX); - Serial.print(" "); - Serial.print(rx_frame.DLC); - Serial.print(" "); - for (int i = 0; i < rx_frame.DLC; ++i) { - Serial.print(rx_frame.data.u8[i], HEX); - Serial.print(" "); - } - Serial.println(""); break; } @@ -1353,23 +1340,11 @@ void clearSOH(void) { default: break; } - // All CAN messages semt will be logged via serial during development of this function - Serial.print(millis()); // Example printout, time, ID, length, data: 7553 7B9 8 FF C0 B9 EA 0 0 2 5D - Serial.print(" "); - Serial.print(LEAF_CLEAR_SOH.ID, HEX); - Serial.print(" "); - Serial.print(LEAF_CLEAR_SOH.DLC); - Serial.print(" "); - for (int i = 0; i < LEAF_CLEAR_SOH.DLC; ++i) { - Serial.print(LEAF_CLEAR_SOH.data.u8[i], HEX); - Serial.print(" "); - } - Serial.println(""); } -uint32_t CyclicXorHash16Bit(uint32_t param_1, uint32_t param_2) { +unsigned int CyclicXorHash16Bit(unsigned int param_1, unsigned int param_2) { bool bVar1; - uint32_t uVar2, uVar3, uVar4, uVar5, uVar6, uVar7, uVar8, uVar9, uVar10, uVar11, iVar12; + unsigned int uVar2, uVar3, uVar4, uVar5, uVar6, uVar7, uVar8, uVar9, uVar10, uVar11, iVar12; param_1 = param_1 & 0xffff; param_2 = param_2 & 0xffff; @@ -1430,54 +1405,53 @@ uint32_t CyclicXorHash16Bit(uint32_t param_1, uint32_t param_2) { } while (bVar1); return uVar10; } - -uint32_t ComputeMaskedXorProduct(uint32_t param_1, uint32_t param_2, uint32_t param_3) { - return (param_3 ^ 0x780 | param_2 ^ 0x116) * ((param_1 & 0xffff) >> 8 ^ param_1 & 0xff) & 0xffff; +unsigned int ComputeMaskedXorProduct(unsigned int param_1, unsigned int param_2, unsigned int param_3) { + return (param_3 ^ 0x7F88 | param_2 ^ 0x8FE7) * ((param_1 & 0xffff) >> 8 ^ param_1 & 0xff) & 0xffff; } short ShortMaskedSumAndProduct(short param_1, short param_2) { unsigned short uVar1; - uVar1 = param_2 + param_1 * 0x5ba & 0xff; + uVar1 = param_2 + param_1 * 0x0006 & 0xff; return (uVar1 + param_1) * (uVar1 + param_2); } -uint32_t MaskedBitwiseRotateMultiply(uint32_t param_1, uint32_t param_2) { - uint32_t uVar1; +unsigned int MaskedBitwiseRotateMultiply(unsigned int param_1, unsigned int param_2) { + unsigned int uVar1; param_1 = param_1 & 0xffff; param_2 = param_2 & 0xffff; - uVar1 = param_2 & (param_1 | 0x5ba) & 0xf; - return ((uint32_t)param_1 >> uVar1 | param_1 << (0x10 - uVar1 & 0x1f)) * - (param_2 << uVar1 | (uint32_t)param_2 >> (0x10 - uVar1 & 0x1f)) & + uVar1 = param_2 & (param_1 | 0x0006) & 0xf; + return ((unsigned int)param_1 >> uVar1 | param_1 << (0x10 - uVar1 & 0x1f)) * + (param_2 << uVar1 | (unsigned int)param_2 >> (0x10 - uVar1 & 0x1f)) & 0xffff; } -uint32_t CryptAlgo(uint32_t param_1, uint32_t param_2, uint32_t param_3) { - uint32_t uVar1, uVar2, iVar3, iVar4; +unsigned int CryptAlgo(unsigned int param_1, unsigned int param_2, unsigned int param_3) { + unsigned int uVar1, uVar2, iVar3, iVar4; uVar1 = MaskedBitwiseRotateMultiply(param_2, param_3); uVar2 = ShortMaskedSumAndProduct(param_2, param_3); uVar1 = ComputeMaskedXorProduct(param_1, uVar1, uVar2); uVar2 = ComputeMaskedXorProduct(param_1, uVar2, uVar1); - iVar3 = CyclicXorHash16Bit(uVar1, 0xffc4); - iVar4 = CyclicXorHash16Bit(uVar2, 0xffc4); + iVar3 = CyclicXorHash16Bit(uVar1, 0x8421); + iVar4 = CyclicXorHash16Bit(uVar2, 0x8421); return iVar4 + iVar3 * 0x10000; } -void decodeChallengeData(uint32_t incomingChallenge, unsigned char* solvedChallenge) { - uint32_t uVar1, uVar2; +void decodeChallengeData(unsigned int incomingChallenge, unsigned char* solvedChallenge) { + unsigned int uVar1, uVar2; - uVar1 = CryptAlgo(0x609, 0xDD2, incomingChallenge >> 0x10); - uVar2 = CryptAlgo(incomingChallenge & 0xffff, incomingChallenge >> 0x10, 0x609); + uVar1 = CryptAlgo(0x54e9, 0x3afd, incomingChallenge >> 0x10); + uVar2 = CryptAlgo(incomingChallenge & 0xffff, incomingChallenge >> 0x10, 0x54e9); *solvedChallenge = (unsigned char)uVar1; solvedChallenge[1] = (unsigned char)uVar2; - solvedChallenge[2] = (unsigned char)((uint32_t)uVar2 >> 8); - solvedChallenge[3] = (unsigned char)((uint32_t)uVar1 >> 8); - solvedChallenge[4] = (unsigned char)((uint32_t)uVar2 >> 16); - solvedChallenge[5] = (unsigned char)((uint32_t)uVar1 >> 16); - solvedChallenge[6] = (unsigned char)((uint32_t)uVar2 >> 24); - solvedChallenge[7] = (unsigned char)((uint32_t)uVar1 >> 24); + solvedChallenge[2] = (unsigned char)((unsigned int)uVar2 >> 8); + solvedChallenge[3] = (unsigned char)((unsigned int)uVar1 >> 8); + solvedChallenge[4] = (unsigned char)((unsigned int)uVar2 >> 0x10); + solvedChallenge[5] = (unsigned char)((unsigned int)uVar1 >> 0x10); + solvedChallenge[6] = (unsigned char)((unsigned int)uVar2 >> 0x18); + solvedChallenge[7] = (unsigned char)((unsigned int)uVar1 >> 0x18); return; } diff --git a/Software/src/battery/NISSAN-LEAF-BATTERY.h b/Software/src/battery/NISSAN-LEAF-BATTERY.h index 2d29c0d7..245f0f39 100644 --- a/Software/src/battery/NISSAN-LEAF-BATTERY.h +++ b/Software/src/battery/NISSAN-LEAF-BATTERY.h @@ -16,11 +16,11 @@ void setup_battery(void); void transmit_can(CAN_frame* tx_frame, int interface); void clearSOH(void); //Cryptographic functions -void decodeChallengeData(uint32_t incomingChallenge, unsigned char* solvedChallenge); -uint32_t CyclicXorHash16Bit(uint32_t param_1, uint32_t param_2); -uint32_t ComputeMaskedXorProduct(uint32_t param_1, uint32_t param_2, uint32_t param_3); +void decodeChallengeData(unsigned int SeedInput, unsigned char* Crypt_Output_Buffer); +unsigned int CyclicXorHash16Bit(unsigned int param_1, unsigned int param_2); +unsigned int ComputeMaskedXorProduct(unsigned int param_1, unsigned int param_2, unsigned int param_3); short ShortMaskedSumAndProduct(short param_1, short param_2); -uint32_t MaskedBitwiseRotateMultiply(uint32_t param_1, uint32_t param_2); -uint32_t CryptAlgo(uint32_t param_1, uint32_t param_2, uint32_t param_3); +unsigned int MaskedBitwiseRotateMultiply(unsigned int param_1, unsigned int param_2); +unsigned int CryptAlgo(unsigned int param_1, unsigned int param_2, unsigned int param_3); #endif diff --git a/Software/src/devboard/webserver/advanced_battery_html.cpp b/Software/src/devboard/webserver/advanced_battery_html.cpp index 00202d3e..f734230a 100644 --- a/Software/src/devboard/webserver/advanced_battery_html.cpp +++ b/Software/src/devboard/webserver/advanced_battery_html.cpp @@ -356,7 +356,7 @@ String advanced_battery_processor(const String& var) { content += ""; return content; diff --git a/Software/src/devboard/webserver/webserver.cpp b/Software/src/devboard/webserver/webserver.cpp index 8eab7631..ce4ec7d4 100644 --- a/Software/src/devboard/webserver/webserver.cpp +++ b/Software/src/devboard/webserver/webserver.cpp @@ -1,6 +1,7 @@ #include "webserver.h" #include #include "../../datalayer/datalayer.h" +#include "../../datalayer/datalayer_extended.h" #include "../../lib/bblanchon-ArduinoJson/ArduinoJson.h" #include "../utils/events.h" #include "../utils/led_handler.h" @@ -231,6 +232,15 @@ void init_webserver() { } }); + // Route for resetting SOH on Nissan LEAF batteries + server.on("/resetSOH", HTTP_GET, [](AsyncWebServerRequest* request) { + if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) { + return request->requestAuthentication(); + } + datalayer_extended.nissanleaf.UserRequestSOHreset = true; + request->send(200, "text/plain", "Updated successfully"); + }); + #ifdef TEST_FAKE_BATTERY // Route for editing FakeBatteryVoltage server.on("/updateFakeBatteryVoltage", HTTP_GET, [](AsyncWebServerRequest* request) { @@ -486,117 +496,16 @@ String processor(const String& var) { // Display which components are used content += "

Inverter protocol: "; -#ifdef BYD_CAN - content += "BYD Battery-Box Premium HVS over CAN Bus"; -#endif // BYD_CAN -#ifdef BYD_MODBUS - content += "BYD 11kWh HVM battery over Modbus RTU"; -#endif // BYD_MODBUS -#ifdef FOXESS_CAN - content += "FoxESS compatible HV2600/ECS4100 battery"; -#endif // FOXESS_CAN -#ifdef PYLON_CAN - content += "Pylontech battery over CAN bus"; -#endif // PYLON_CAN -#ifdef PYLON_LV_CAN - content += "Pylontech LV battery over CAN bus"; -#endif // PYLON_LV_CAN -#ifdef SERIAL_LINK_TRANSMITTER - content += "Serial link to another LilyGo board"; -#endif // SERIAL_LINK_TRANSMITTER -#ifdef SMA_CAN - content += "BYD Battery-Box H 8.9kWh, 7 mod over CAN bus"; -#endif // SMA_CAN -#ifdef SOFAR_CAN - content += "Sofar Energy Storage Inverter High Voltage BMS General Protocol (Extended Frame) over CAN bus"; -#endif // SOFAR_CAN -#ifdef SOLAX_CAN - content += "SolaX Triple Power LFP over CAN bus"; -#endif // SOLAX_CAN + content += datalayer.system.info.inverter_protocol; content += "

"; - content += "

Battery protocol: "; -#ifdef BMW_I3_BATTERY - content += "BMW i3"; -#endif // BMW_I3_BATTERY -#ifdef BMW_IX_BATTERY - content += "BMW iX and i4-7 platform"; -#endif // BMW_IX_BATTERY -#ifdef BYD_ATTO_3_BATTERY - content += "BYD Atto 3"; -#endif // BYD_ATTO_3_BATTERY -#ifdef CELLPOWER_BMS - content += "Cellpower BMS"; -#endif // CELLPOWER_BMS -#ifdef CHADEMO_BATTERY - content += "Chademo V2X mode"; -#endif // CHADEMO_BATTERY -#ifdef IMIEV_CZERO_ION_BATTERY - content += "I-Miev / C-Zero / Ion Triplet"; -#endif // IMIEV_CZERO_ION_BATTERY -#ifdef JAGUAR_IPACE_BATTERY - content += "Jaguar I-PACE"; -#endif // JAGUAR_IPACE_BATTERY -#ifdef KIA_HYUNDAI_64_BATTERY - content += "Kia/Hyundai 64kWh"; -#endif // KIA_HYUNDAI_64_BATTERY -#ifdef KIA_E_GMP_BATTERY - content += "Kia/Hyundai EGMP platform"; -#endif // KIA_E_GMP_BATTERY -#ifdef KIA_HYUNDAI_HYBRID_BATTERY - content += "Kia/Hyundai Hybrid"; -#endif // KIA_HYUNDAI_HYBRID_BATTERY -#ifdef MG_5_BATTERY - content += "MG 5"; -#endif // MG_5_BATTERY -#ifdef NISSAN_LEAF_BATTERY - content += "Nissan LEAF"; -#endif // NISSAN_LEAF_BATTERY -#ifdef PYLON_BATTERY - content += "Pylon compatible battery"; -#endif // PYLON_BATTERY -#ifdef RJXZS_BMS - content += "RJXZS BMS, DIY battery"; -#endif // RJXZS_BMS -#ifdef RANGE_ROVER_PHEV_BATTERY - content += "Range Rover 13kWh PHEV battery (L494/L405)"; -#endif //RANGE_ROVER_PHEV_BATTERY -#ifdef RENAULT_KANGOO_BATTERY - content += "Renault Kangoo"; -#endif // RENAULT_KANGOO_BATTERY -#ifdef RENAULT_TWIZY_BATTERY - content += "Renault Twizy"; -#endif // RENAULT_TWIZY_BATTERY -#ifdef RENAULT_ZOE_GEN1_BATTERY - content += "Renault Zoe Gen1 22/40"; -#endif // RENAULT_ZOE_GEN1_BATTERY -#ifdef RENAULT_ZOE_GEN2_BATTERY - content += "Renault Zoe Gen2 50"; -#endif // RENAULT_ZOE_GEN2_BATTERY -#ifdef SANTA_FE_PHEV_BATTERY - content += "Santa Fe PHEV"; -#endif // SANTA_FE_PHEV_BATTERY -#ifdef SERIAL_LINK_RECEIVER - content += "Serial link to another LilyGo board"; -#endif // SERIAL_LINK_RECEIVER -#ifdef TESLA_MODEL_SX_BATTERY - content += "Tesla Model S/X"; -#endif // TESLA_MODEL_SX_BATTERY -#ifdef TESLA_MODEL_3Y_BATTERY - content += "Tesla Model 3/Y"; -#endif // TESLA_MODEL_3Y_BATTERY -#ifdef VOLVO_SPA_BATTERY - content += "Volvo / Polestar 78kWh battery"; -#endif // VOLVO_SPA_BATTERY -#ifdef TEST_FAKE_BATTERY - content += "Fake battery for testing purposes"; -#endif // TEST_FAKE_BATTERY + content += datalayer.system.info.battery_protocol; #ifdef DOUBLE_BATTERY content += " (Double battery)"; +#endif // DOUBLE_BATTERY if (datalayer.battery.info.chemistry == battery_chemistry_enum::LFP) { content += " (LFP)"; } -#endif // DOUBLE_BATTERY content += "

"; #if defined CHEVYVOLT_CHARGER || defined NISSANLEAF_CHARGER diff --git a/Software/src/include.h b/Software/src/include.h index 83f2290d..b29345e6 100644 --- a/Software/src/include.h +++ b/Software/src/include.h @@ -45,10 +45,4 @@ #error No battery selected! Choose one from the USER_SETTINGS.h file #endif -#ifdef KIA_E_GMP_BATTERY -#ifndef CAN_FD -#error KIA HYUNDAI EGMP BATTERIES CANNOT BE USED WITHOUT CAN FD -#endif -#endif - #endif diff --git a/Software/src/inverter/AFORE-CAN.cpp b/Software/src/inverter/AFORE-CAN.cpp index 74435116..a8d551d7 100644 --- a/Software/src/inverter/AFORE-CAN.cpp +++ b/Software/src/inverter/AFORE-CAN.cpp @@ -233,4 +233,8 @@ void send_can_inverter() { time_to_send_info = false; } } +void setup_inverter(void) { // Performs one time setup at startup over CAN bus + strncpy(datalayer.system.info.inverter_protocol, "Afore battery over CAN", 63); + datalayer.system.info.inverter_protocol[63] = '\0'; +} #endif diff --git a/Software/src/inverter/AFORE-CAN.h b/Software/src/inverter/AFORE-CAN.h index cc9f8548..9829befc 100644 --- a/Software/src/inverter/AFORE-CAN.h +++ b/Software/src/inverter/AFORE-CAN.h @@ -4,8 +4,7 @@ #define CAN_INVERTER_SELECTED -void send_system_data(); -void send_setup_info(); void transmit_can(CAN_frame* tx_frame, int interface); +void setup_inverter(void); #endif diff --git a/Software/src/inverter/BYD-CAN.cpp b/Software/src/inverter/BYD-CAN.cpp index 5cf999a3..4ffa7b20 100644 --- a/Software/src/inverter/BYD-CAN.cpp +++ b/Software/src/inverter/BYD-CAN.cpp @@ -7,13 +7,6 @@ static unsigned long previousMillis2s = 0; // will store last time a 2s CAN Message was send static unsigned long previousMillis10s = 0; // will store last time a 10s CAN Message was send static unsigned long previousMillis60s = 0; // will store last time a 60s CAN Message was send -static uint8_t char1_151 = 0; -static uint8_t char2_151 = 0; -static uint8_t char3_151 = 0; -static uint8_t char4_151 = 0; -static uint8_t char5_151 = 0; -static uint8_t char6_151 = 0; -static uint8_t char7_151 = 0; CAN_frame BYD_250 = {.FD = false, .ext_ID = false, @@ -79,9 +72,12 @@ CAN_frame BYD_210 = {.FD = false, .ID = 0x210, .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; +static uint8_t inverter_name[7] = {0}; static int16_t temperature_average = 0; static uint16_t inverter_voltage = 0; static uint16_t inverter_SOC = 0; +static int16_t inverter_current = 0; +static int16_t inverter_temperature = 0; static uint16_t remaining_capacity_ah = 0; static uint16_t fully_charged_capacity_ah = 0; static long inverter_timestamp = 0; @@ -146,15 +142,12 @@ void update_values_can_inverter() { //This function maps all the values fetched BYD_210.data.u8[3] = (datalayer.battery.status.temperature_min_dC & 0x00FF); #ifdef DEBUG_VIA_USB - if (char1_151 != 0) { + if (inverter_name[0] != 0) { Serial.print("Detected inverter: "); - Serial.print((char)char1_151); - Serial.print((char)char2_151); - Serial.print((char)char3_151); - Serial.print((char)char4_151); - Serial.print((char)char5_151); - Serial.print((char)char6_151); - Serial.println((char)char7_151); + for (uint8_t i = 0; i < 7; i++) { + Serial.print((char)inverter_name[i]); + } + Serial.println(); } #endif } @@ -166,27 +159,25 @@ void receive_can_inverter(CAN_frame rx_frame) { if (rx_frame.data.u8[0] & 0x01) { //Battery requests identification send_intial_data(); } else { // We can identify what inverter type we are connected to - char1_151 = rx_frame.data.u8[1]; - char2_151 = rx_frame.data.u8[2]; - char3_151 = rx_frame.data.u8[3]; - char4_151 = rx_frame.data.u8[4]; - char5_151 = rx_frame.data.u8[5]; - char6_151 = rx_frame.data.u8[6]; - char7_151 = rx_frame.data.u8[7]; + for (uint8_t i = 0; i < 7; i++) { + inverter_name[i] = rx_frame.data.u8[i + 1]; + } } break; case 0x091: datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE; - inverter_voltage = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]) * 0.1; + inverter_voltage = ((rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]) * 0.1; + inverter_current = ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]) * 0.1; + inverter_temperature = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 0.1; break; case 0x0D1: datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE; - inverter_SOC = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]) * 0.1; + inverter_SOC = ((rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]) * 0.1; break; case 0x111: datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE; - inverter_timestamp = ((rx_frame.data.u8[3] << 24) | (rx_frame.data.u8[2] << 16) | (rx_frame.data.u8[1] << 8) | - rx_frame.data.u8[0]); + inverter_timestamp = ((rx_frame.data.u8[0] << 24) | (rx_frame.data.u8[1] << 16) | (rx_frame.data.u8[2] << 8) | + rx_frame.data.u8[3]); break; default: break; @@ -232,4 +223,8 @@ void send_intial_data() { transmit_can(&BYD_3D0_2, can_config.inverter); transmit_can(&BYD_3D0_3, can_config.inverter); } +void setup_inverter(void) { // Performs one time setup at startup over CAN bus + strncpy(datalayer.system.info.inverter_protocol, "BYD Battery-Box Premium HVS over CAN Bus", 63); + datalayer.system.info.inverter_protocol[63] = '\0'; +} #endif diff --git a/Software/src/inverter/BYD-CAN.h b/Software/src/inverter/BYD-CAN.h index 6a45317b..5a90d6ba 100644 --- a/Software/src/inverter/BYD-CAN.h +++ b/Software/src/inverter/BYD-CAN.h @@ -8,5 +8,6 @@ void send_intial_data(); void transmit_can(CAN_frame* tx_frame, int interface); +void setup_inverter(void); #endif diff --git a/Software/src/inverter/BYD-MODBUS.cpp b/Software/src/inverter/BYD-MODBUS.cpp index 7cee6b8d..814e24e4 100644 --- a/Software/src/inverter/BYD-MODBUS.cpp +++ b/Software/src/inverter/BYD-MODBUS.cpp @@ -143,4 +143,8 @@ void verify_inverter_modbus() { history_index = (history_index + 1) % HISTORY_LENGTH; } } +void setup_inverter(void) { // Performs one time setup at startup over CAN bus + strncpy(datalayer.system.info.inverter_protocol, "BYD 11kWh HVM battery over Modbus RTU", 63); + datalayer.system.info.inverter_protocol[63] = '\0'; +} #endif diff --git a/Software/src/inverter/BYD-MODBUS.h b/Software/src/inverter/BYD-MODBUS.h index 3d014daa..487c9784 100644 --- a/Software/src/inverter/BYD-MODBUS.h +++ b/Software/src/inverter/BYD-MODBUS.h @@ -14,4 +14,5 @@ void verify_temperature_modbus(); void verify_inverter_modbus(); void handle_update_data_modbusp201_byd(); void handle_update_data_modbusp301_byd(); +void setup_inverter(void); #endif diff --git a/Software/src/inverter/BYD-SMA.cpp b/Software/src/inverter/BYD-SMA.cpp index 230b61e7..34e33063 100644 --- a/Software/src/inverter/BYD-SMA.cpp +++ b/Software/src/inverter/BYD-SMA.cpp @@ -251,4 +251,10 @@ void send_can_inverter() { } } } +void setup_inverter(void) { // Performs one time setup at startup over CAN bus + strncpy(datalayer.system.info.inverter_protocol, "BYD Battery-Box HVS over SMA CAN", 63); + datalayer.system.info.inverter_protocol[63] = '\0'; + datalayer.system.status.inverter_allows_contactor_closing = false; // The inverter needs to allow first + pinMode(INVERTER_CONTACTOR_ENABLE_PIN, INPUT); +} #endif diff --git a/Software/src/inverter/BYD-SMA.h b/Software/src/inverter/BYD-SMA.h index b71a60fa..e787191d 100644 --- a/Software/src/inverter/BYD-SMA.h +++ b/Software/src/inverter/BYD-SMA.h @@ -8,5 +8,6 @@ #define STOP_STATE 0x02 void transmit_can(CAN_frame* tx_frame, int interface); +void setup_inverter(void); #endif diff --git a/Software/src/inverter/FOXESS-CAN.cpp b/Software/src/inverter/FOXESS-CAN.cpp index e4b3b34c..e23f9006 100644 --- a/Software/src/inverter/FOXESS-CAN.cpp +++ b/Software/src/inverter/FOXESS-CAN.cpp @@ -737,4 +737,8 @@ void receive_can_inverter(CAN_frame rx_frame) { } } } +void setup_inverter(void) { // Performs one time setup at startup over CAN bus + strncpy(datalayer.system.info.inverter_protocol, "FoxESS compatible HV2600/ECS4100 battery", 63); + datalayer.system.info.inverter_protocol[63] = '\0'; +} #endif diff --git a/Software/src/inverter/FOXESS-CAN.h b/Software/src/inverter/FOXESS-CAN.h index d9a3da2d..365a559d 100644 --- a/Software/src/inverter/FOXESS-CAN.h +++ b/Software/src/inverter/FOXESS-CAN.h @@ -5,5 +5,6 @@ #define CAN_INVERTER_SELECTED void transmit_can(CAN_frame* tx_frame, int interface); +void setup_inverter(void); #endif diff --git a/Software/src/inverter/PYLON-CAN.cpp b/Software/src/inverter/PYLON-CAN.cpp index 2b8e036c..2145b3e7 100644 --- a/Software/src/inverter/PYLON-CAN.cpp +++ b/Software/src/inverter/PYLON-CAN.cpp @@ -477,4 +477,8 @@ void send_system_data() { //System equipment information transmit_can(&PYLON_4291, can_config.inverter); #endif } +void setup_inverter(void) { // Performs one time setup at startup over CAN bus + strncpy(datalayer.system.info.inverter_protocol, "Pylontech battery over CAN bus", 63); + datalayer.system.info.inverter_protocol[63] = '\0'; +} #endif diff --git a/Software/src/inverter/PYLON-CAN.h b/Software/src/inverter/PYLON-CAN.h index 86bb4afa..6b39afcd 100644 --- a/Software/src/inverter/PYLON-CAN.h +++ b/Software/src/inverter/PYLON-CAN.h @@ -7,5 +7,6 @@ void send_system_data(); void send_setup_info(); void transmit_can(CAN_frame* tx_frame, int interface); +void setup_inverter(void); #endif diff --git a/Software/src/inverter/PYLON-LV-CAN.cpp b/Software/src/inverter/PYLON-LV-CAN.cpp index 458ac7a0..e5eb2e87 100644 --- a/Software/src/inverter/PYLON-LV-CAN.cpp +++ b/Software/src/inverter/PYLON-LV-CAN.cpp @@ -133,4 +133,8 @@ void send_can_inverter() { transmit_can(&PYLON_35E, can_config.inverter); } } +void setup_inverter(void) { // Performs one time setup at startup over CAN bus + strncpy(datalayer.system.info.inverter_protocol, "Pylontech LV battery over CAN bus", 63); + datalayer.system.info.inverter_protocol[63] = '\0'; +} #endif diff --git a/Software/src/inverter/PYLON-LV-CAN.h b/Software/src/inverter/PYLON-LV-CAN.h index 45376d2b..ca6922eb 100644 --- a/Software/src/inverter/PYLON-LV-CAN.h +++ b/Software/src/inverter/PYLON-LV-CAN.h @@ -12,5 +12,6 @@ void send_system_data(); void send_setup_info(); void transmit_can(CAN_frame* tx_frame, int interface); +void setup_inverter(void); #endif diff --git a/Software/src/inverter/SERIAL-LINK-TRANSMITTER-INVERTER.cpp b/Software/src/inverter/SERIAL-LINK-TRANSMITTER-INVERTER.cpp index 6d3b5071..a61ea7dc 100644 --- a/Software/src/inverter/SERIAL-LINK-TRANSMITTER-INVERTER.cpp +++ b/Software/src/inverter/SERIAL-LINK-TRANSMITTER-INVERTER.cpp @@ -128,6 +128,8 @@ void manageSerialLinkTransmitter() { static unsigned long updateDataTime = 0; if (currentTime - updateDataTime > INTERVAL_1_S) { + strncpy(datalayer.system.info.inverter_protocol, "Serial link to another LilyGo board", 63); + datalayer.system.info.inverter_protocol[63] = '\0'; updateDataTime = currentTime; dataLinkTransmit.updateData(0, datalayer.battery.status.real_soc); dataLinkTransmit.updateData(1, datalayer.battery.status.soh_pptt); diff --git a/Software/src/inverter/SERIAL-LINK-TRANSMITTER-INVERTER.h b/Software/src/inverter/SERIAL-LINK-TRANSMITTER-INVERTER.h index 14ae08d7..1637e6bd 100644 --- a/Software/src/inverter/SERIAL-LINK-TRANSMITTER-INVERTER.h +++ b/Software/src/inverter/SERIAL-LINK-TRANSMITTER-INVERTER.h @@ -6,5 +6,6 @@ #include "../lib/mackelec-SerialDataLink/SerialDataLink.h" void manageSerialLinkTransmitter(); +void setup_inverter(void); #endif diff --git a/Software/src/inverter/SMA-CAN.cpp b/Software/src/inverter/SMA-CAN.cpp index cecae60c..5831406d 100644 --- a/Software/src/inverter/SMA-CAN.cpp +++ b/Software/src/inverter/SMA-CAN.cpp @@ -249,4 +249,9 @@ void send_can_inverter() { } } } + +void setup_inverter(void) { // Performs one time setup at startup over CAN bus + strncpy(datalayer.system.info.inverter_protocol, "SMA CAN", 63); + datalayer.system.info.inverter_protocol[63] = '\0'; +} #endif diff --git a/Software/src/inverter/SMA-CAN.h b/Software/src/inverter/SMA-CAN.h index 73b9f160..111044a4 100644 --- a/Software/src/inverter/SMA-CAN.h +++ b/Software/src/inverter/SMA-CAN.h @@ -8,5 +8,6 @@ #define STOP_STATE 0x02 void transmit_can(CAN_frame* tx_frame, int interface); +void setup_inverter(void); #endif diff --git a/Software/src/inverter/SMA-TRIPOWER-CAN.cpp b/Software/src/inverter/SMA-TRIPOWER-CAN.cpp index 9c0cc279..7a242dae 100644 --- a/Software/src/inverter/SMA-TRIPOWER-CAN.cpp +++ b/Software/src/inverter/SMA-TRIPOWER-CAN.cpp @@ -320,4 +320,9 @@ void send_tripower_init() { transmit_can(&SMA_017, can_config.inverter); // Battery Manufacturer transmit_can(&SMA_018, can_config.inverter); // Battery Name } + +void setup_inverter(void) { // Performs one time setup at startup over CAN bus + strncpy(datalayer.system.info.inverter_protocol, "SMA Tripower CAN", 63); + datalayer.system.info.inverter_protocol[63] = '\0'; +} #endif diff --git a/Software/src/inverter/SMA-TRIPOWER-CAN.h b/Software/src/inverter/SMA-TRIPOWER-CAN.h index e12692da..90967001 100644 --- a/Software/src/inverter/SMA-TRIPOWER-CAN.h +++ b/Software/src/inverter/SMA-TRIPOWER-CAN.h @@ -6,5 +6,6 @@ void send_tripower_init(); void transmit_can(CAN_frame* tx_frame, int interface); +void setup_inverter(void); #endif diff --git a/Software/src/inverter/SOFAR-CAN.cpp b/Software/src/inverter/SOFAR-CAN.cpp index eb13b7c5..838ebf93 100644 --- a/Software/src/inverter/SOFAR-CAN.cpp +++ b/Software/src/inverter/SOFAR-CAN.cpp @@ -263,4 +263,9 @@ void send_can_inverter() { transmit_can(&SOFAR_35A, can_config.inverter); } } + +void setup_inverter(void) { // Performs one time setup at startup over CAN bus + strncpy(datalayer.system.info.inverter_protocol, "Sofar BMS (Extended Frame) over CAN bus", 63); + datalayer.system.info.inverter_protocol[63] = '\0'; +} #endif diff --git a/Software/src/inverter/SOFAR-CAN.h b/Software/src/inverter/SOFAR-CAN.h index 6d43222f..7a80bf62 100644 --- a/Software/src/inverter/SOFAR-CAN.h +++ b/Software/src/inverter/SOFAR-CAN.h @@ -5,5 +5,6 @@ #define CAN_INVERTER_SELECTED void transmit_can(CAN_frame* tx_frame, int interface); +void setup_inverter(void); #endif diff --git a/Software/src/inverter/SOLAX-CAN.cpp b/Software/src/inverter/SOLAX-CAN.cpp index 790c8524..9bef56ec 100644 --- a/Software/src/inverter/SOLAX-CAN.cpp +++ b/Software/src/inverter/SOLAX-CAN.cpp @@ -252,4 +252,9 @@ void receive_can_inverter(CAN_frame rx_frame) { #endif } } +void setup_inverter(void) { // Performs one time setup at startup + strncpy(datalayer.system.info.inverter_protocol, "SolaX Triple Power LFP over CAN bus", 63); + datalayer.system.info.inverter_protocol[63] = '\0'; + datalayer.system.status.inverter_allows_contactor_closing = false; // The inverter needs to allow first +} #endif diff --git a/Software/src/inverter/SOLAX-CAN.h b/Software/src/inverter/SOLAX-CAN.h index 6ad461a7..68373ec2 100644 --- a/Software/src/inverter/SOLAX-CAN.h +++ b/Software/src/inverter/SOLAX-CAN.h @@ -15,5 +15,6 @@ #define UPDATING_FW 4 void transmit_can(CAN_frame* tx_frame, int interface); +void setup_inverter(void); #endif From e45d1bb931fdb530be9ece9fc43e798e52599d19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Sun, 17 Nov 2024 19:15:51 +0200 Subject: [PATCH 168/210] Add SMA skeleton --- Software/src/inverter/SMA-48V-CAN.cpp | 184 ++++++++++++++++++++++++++ Software/src/inverter/SMA-48V-CAN.h | 13 ++ 2 files changed, 197 insertions(+) create mode 100644 Software/src/inverter/SMA-48V-CAN.cpp create mode 100644 Software/src/inverter/SMA-48V-CAN.h diff --git a/Software/src/inverter/SMA-48V-CAN.cpp b/Software/src/inverter/SMA-48V-CAN.cpp new file mode 100644 index 00000000..4c9a37ce --- /dev/null +++ b/Software/src/inverter/SMA-48V-CAN.cpp @@ -0,0 +1,184 @@ +#include "../include.h" +#ifdef SMA_CAN +#include "../datalayer/datalayer.h" +#include "SMA-CAN.h" + +/* SMA 48V CAN protocol: +CAN 2.0A +500kBit/sec +11-Bit Identifiers */ + +/* Do not change code below unless you are sure what you are doing */ +static unsigned long previousMillis100ms = 0; + +//Actual content messages +CAN_frame SMA_558 = {.FD = false, + .ext_ID = false, + .DLC = 8, + .ID = 0x558, + .data = {0x03, 0x12, 0x00, 0x04, 0x00, 0x59, 0x07, 0x07}}; //7x BYD modules, Vendor ID 7 BYD +CAN_frame SMA_598 = {.FD = false, + .ext_ID = false, + .DLC = 8, + .ID = 0x598, + .data = {0x00, 0x00, 0x12, 0x34, 0x5A, 0xDE, 0x07, 0x4F}}; //B0-4 Serial, rest unknown +CAN_frame SMA_5D8 = {.FD = false, + .ext_ID = false, + .DLC = 8, + .ID = 0x5D8, + .data = {0x00, 0x42, 0x59, 0x44, 0x00, 0x00, 0x00, 0x00}}; //B Y D +CAN_frame SMA_618_1 = {.FD = false, + .ext_ID = false, + .DLC = 8, + .ID = 0x618, + .data = {0x00, 0x42, 0x61, 0x74, 0x74, 0x65, 0x72, 0x79}}; //0 B A T T E R Y +CAN_frame SMA_618_2 = {.FD = false, + .ext_ID = false, + .DLC = 8, + .ID = 0x618, + .data = {0x01, 0x2D, 0x42, 0x6F, 0x78, 0x20, 0x48, 0x39}}; //1 - B O X H +CAN_frame SMA_618_3 = {.FD = false, + .ext_ID = false, + .DLC = 8, + .ID = 0x618, + .data = {0x02, 0x2E, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00}}; //2 - 0 +CAN_frame SMA_358 = {.FD = false, + .ext_ID = false, + .DLC = 8, + .ID = 0x358, + .data = {0x0F, 0x6C, 0x06, 0x20, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame SMA_3D8 = {.FD = false, + .ext_ID = false, + .DLC = 8, + .ID = 0x3D8, + .data = {0x04, 0x10, 0x27, 0x10, 0x00, 0x18, 0xF9, 0x00}}; +CAN_frame SMA_458 = {.FD = false, + .ext_ID = false, + .DLC = 8, + .ID = 0x458, + .data = {0x00, 0x00, 0x06, 0x75, 0x00, 0x00, 0x05, 0xD6}}; +CAN_frame SMA_518 = {.FD = false, + .ext_ID = false, + .DLC = 8, + .ID = 0x518, + .data = {0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF}}; +CAN_frame SMA_4D8 = {.FD = false, + .ext_ID = false, + .DLC = 8, + .ID = 0x4D8, + .data = {0x09, 0xFD, 0x00, 0x00, 0x00, 0xA8, 0x02, 0x08}}; +CAN_frame SMA_158 = {.FD = false, + .ext_ID = false, + .DLC = 8, + .ID = 0x158, + .data = {0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x6A, 0xAA, 0xAA}}; + +static int16_t temperature_average = 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 + //Calculate values + + temperature_average = + ((datalayer.battery.status.temperature_max_dC + datalayer.battery.status.temperature_min_dC) / 2); + + if (datalayer.battery.status.voltage_dV > 10) { // Only update value when we have voltage available to avoid div0 + ampere_hours_remaining = + ((datalayer.battery.status.reported_remaining_capacity_Wh / datalayer.battery.status.voltage_dV) * + 100); //(WH[10000] * V+1[3600])*100 = 270 (27.0Ah) + } + + //Map values to CAN messages + //Battery charge voltage (eg 400.0V = 4000 , 16bits long) + SMA_351.data.u8[0] = ((datalayer.battery.info.max_design_voltage_dV - 20) >> 8); + SMA_351.data.u8[1] = ((datalayer.battery.info.max_design_voltage_dV - 20) & 0x00FF); + //Discharge limited current, 500 = 50A, (0.1, A) + SMA_351.data.u8[2] = (datalayer.battery.status.max_discharge_current_dA >> 8); + SMA_351.data.u8[3] = (datalayer.battery.status.max_discharge_current_dA & 0x00FF); + //Charge limited current, 125 =12.5A (0.1, A) + SMA_351.data.u8[4] = (datalayer.battery.status.max_charge_current_dA >> 8); + SMA_351.data.u8[5] = (datalayer.battery.status.max_charge_current_dA & 0x00FF); + //Discharge voltage (eg 300.0V = 3000 , 16bits long) + SMA_351.data.u8[6] = ((datalayer.battery.info.min_design_voltage_dV + 20) >> 8); + SMA_351.data.u8[7] = ((datalayer.battery.info.min_design_voltage_dV + 20) & 0x00FF); + + //SOC (100.00%) + SMA_3D8.data.u8[0] = (datalayer.battery.status.reported_soc >> 8); + SMA_3D8.data.u8[1] = (datalayer.battery.status.reported_soc & 0x00FF); + //StateOfHealth (100.00%) + SMA_3D8.data.u8[2] = (datalayer.battery.status.soh_pptt >> 8); + SMA_3D8.data.u8[3] = (datalayer.battery.status.soh_pptt & 0x00FF); + //State of charge (AH, 0.1) + SMA_3D8.data.u8[4] = (ampere_hours_remaining >> 8); + SMA_3D8.data.u8[5] = (ampere_hours_remaining & 0x00FF); + + //Voltage (370.0) + SMA_4D8.data.u8[0] = (datalayer.battery.status.voltage_dV >> 8); + SMA_4D8.data.u8[1] = (datalayer.battery.status.voltage_dV & 0x00FF); + //Current (TODO: signed OK?) + SMA_4D8.data.u8[2] = (datalayer.battery.status.current_dA >> 8); + SMA_4D8.data.u8[3] = (datalayer.battery.status.current_dA & 0x00FF); + //Temperature average + SMA_4D8.data.u8[4] = (temperature_average >> 8); + SMA_4D8.data.u8[5] = (temperature_average & 0x00FF); + //Battery ready + if (datalayer.battery.status.bms_status == ACTIVE) { + SMA_4D8.data.u8[6] = READY_STATE; + } else { + SMA_4D8.data.u8[6] = STOP_STATE; + } +} + +void receive_can_inverter(CAN_frame rx_frame) { + switch (rx_frame.ID) { + case 0x305: + datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE; + //Frame0-1 Battery Voltage + //Frame2-3 Battery Current + //Frame4-5 Battery Temperature + //Frame6-7 SOC Battery + break; + case 0x306: + datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE; + //Frame0-1 SOH Battery + //Frame2 Charging procedure + //Frame3 Operating state + //Frame4-5 Active error message + //Frame6-7 Battery charge voltage setpoint + break; + default: + break; + } +} + +void send_can_inverter() { + unsigned long currentMillis = millis(); + + // Send CAN Message every 100ms if Enable line is HIGH + if (datalayer.system.status.inverter_allows_contactor_closing) { + if (currentMillis - previousMillis100ms >= 100) { + previousMillis100ms = currentMillis; + + transmit_can(&SMA_351, can_config.inverter); + transmit_can(&SMA_355, can_config.inverter); + transmit_can(&SMA_356, can_config.inverter); + transmit_can(&SMA_35A, can_config.inverter); + transmit_can(&SMA_35B, can_config.inverter); + transmit_can(&SMA_35E, can_config.inverter); + transmit_can(&SMA_35F, can_config.inverter); + + //Remote quick stop (optional) + if (datalayer.battery.status.bms_status == FAULT) { + transmit_can(&SMA_00F, can_config.inverter); + //After receiving this message, Sunny Island will immediately go into standby. + //Please send start command, to start again. Manual start is also possible. + } + } + } +} + +void setup_inverter(void) { // Performs one time setup at startup over CAN bus + strncpy(datalayer.system.info.inverter_protocol, "SMA 48V CAN", 63); + datalayer.system.info.inverter_protocol[63] = '\0'; +} +#endif diff --git a/Software/src/inverter/SMA-48V-CAN.h b/Software/src/inverter/SMA-48V-CAN.h new file mode 100644 index 00000000..111044a4 --- /dev/null +++ b/Software/src/inverter/SMA-48V-CAN.h @@ -0,0 +1,13 @@ +#ifndef SMA_CAN_H +#define SMA_CAN_H +#include "../include.h" + +#define CAN_INVERTER_SELECTED + +#define READY_STATE 0x03 +#define STOP_STATE 0x02 + +void transmit_can(CAN_frame* tx_frame, int interface); +void setup_inverter(void); + +#endif From f864f2ae36488023ad392aac8059e9cb20dd9c96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Sun, 17 Nov 2024 19:28:43 +0200 Subject: [PATCH 169/210] Add string to show what SOC method used --- Software/src/battery/BYD-ATTO-3-BATTERY.cpp | 6 ++++++ Software/src/datalayer/datalayer_extended.h | 3 +++ Software/src/devboard/webserver/advanced_battery_html.cpp | 2 ++ 3 files changed, 11 insertions(+) diff --git a/Software/src/battery/BYD-ATTO-3-BATTERY.cpp b/Software/src/battery/BYD-ATTO-3-BATTERY.cpp index 763ba32d..8d995291 100644 --- a/Software/src/battery/BYD-ATTO-3-BATTERY.cpp +++ b/Software/src/battery/BYD-ATTO-3-BATTERY.cpp @@ -15,6 +15,7 @@ static unsigned long previousMillis50 = 0; // will store last time a 50ms CAN Message was send static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send static unsigned long previousMillis500 = 0; // will store last time a 500ms CAN Message was send +static bool SOC_method = false; static uint8_t counter_50ms = 0; static uint8_t counter_100ms = 0; static uint8_t frame6_counter = 0xB; @@ -61,6 +62,8 @@ static uint16_t BMS2_highest_cell_voltage_mV = 3300; #define POLL_FOR_BATTERY_CELL_MV_MAX 0x2D #define POLL_FOR_BATTERY_CELL_MV_MIN 0x2B #define UNKNOWN_POLL_1 0xFC +#define ESTIMATED 0 +#define MEASURED 1 static uint16_t poll_state = POLL_FOR_BATTERY_SOC; CAN_frame ATTO_3_12D = {.FD = false, @@ -113,8 +116,10 @@ void update_values_battery() { //This function maps all the values fetched via // We instead estimate the SOC% based on the battery voltage. // This is a bad solution, you wont be able to use 100% of the battery datalayer.battery.status.real_soc = estimateSOC(datalayer.battery.status.voltage_dV); + SOC_method = ESTIMATED; #else // Pack is not crashed, we can use periodically transmitted SOC datalayer.battery.status.real_soc = highprecision_SOC * 100; + SOC_method = MEASURED; #endif datalayer.battery.status.current_dA = -BMS_current; @@ -151,6 +156,7 @@ void update_values_battery() { //This function maps all the values fetched via datalayer.battery.status.temperature_max_dC = battery_calc_max_temperature * 10; // Update webserver datalayer + datalayer_extended.bydAtto3.SOC_method = SOC_method; datalayer_extended.bydAtto3.SOC_estimated = datalayer.battery.status.real_soc; //Once we implement switching logic, remember to change from where the estimated is taken datalayer_extended.bydAtto3.SOC_highprec = battery_highprecision_SOC; diff --git a/Software/src/datalayer/datalayer_extended.h b/Software/src/datalayer/datalayer_extended.h index 2009d60b..c4c8f33c 100644 --- a/Software/src/datalayer/datalayer_extended.h +++ b/Software/src/datalayer/datalayer_extended.h @@ -71,6 +71,9 @@ typedef struct { } DATALAYER_INFO_BMWI3; typedef struct { + /** bool */ + /** Which SOC method currently used. 0 = Estimated, 1 = Measured */ + bool SOC_method = 0; /** uint16_t */ /** SOC% estimate. Estimated from total pack voltage */ uint16_t SOC_estimated = 0; diff --git a/Software/src/devboard/webserver/advanced_battery_html.cpp b/Software/src/devboard/webserver/advanced_battery_html.cpp index 5db01127..4d2e5a77 100644 --- a/Software/src/devboard/webserver/advanced_battery_html.cpp +++ b/Software/src/devboard/webserver/advanced_battery_html.cpp @@ -275,6 +275,8 @@ String advanced_battery_processor(const String& var) { #endif //CELLPOWER_BMS #ifdef BYD_ATTO_3_BATTERY + static const char* SOCmethod[2] = {"Estimated from voltage", "Measured by BMS"}; + content += "

SOC method used: " + String(SOCmethod[datalayer_extended.bydAtto3.SOC_method]) + "

"; content += "

SOC estimated: " + String(datalayer_extended.bydAtto3.SOC_estimated) + "

"; content += "

SOC highprec: " + String(datalayer_extended.bydAtto3.SOC_highprec) + "

"; content += "

SOC OBD2: " + String(datalayer_extended.bydAtto3.SOC_polled) + "

"; From bfebe18876dc7a8baf10898d8d3753a709f0e692 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Sun, 17 Nov 2024 19:46:24 +0200 Subject: [PATCH 170/210] Update Software.ino --- Software/Software.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Software/Software.ino b/Software/Software.ino index 957d4537..8798af47 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -53,7 +53,7 @@ Preferences settings; // Store user settings // The current software version, shown on webserver -const char* version_number = "7.7.dev"; +const char* version_number = "7.7.0"; // Interval settings uint16_t intervalUpdateValues = INTERVAL_1_S; // Interval at which to update inverter values / Modbus registers From 478d0014ade5d88e325c1769c5a25bf7166c4f30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Sun, 17 Nov 2024 20:09:17 +0200 Subject: [PATCH 171/210] Update Software.ino --- Software/Software.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Software/Software.ino b/Software/Software.ino index 8798af47..08f7a76f 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -53,7 +53,7 @@ Preferences settings; // Store user settings // The current software version, shown on webserver -const char* version_number = "7.7.0"; +const char* version_number = "7.8.dev"; // Interval settings uint16_t intervalUpdateValues = INTERVAL_1_S; // Interval at which to update inverter values / Modbus registers From 18e09918207aee37fcc76380885d9485607dc2e0 Mon Sep 17 00:00:00 2001 From: NJbubo Date: Sun, 17 Nov 2024 21:24:04 +0100 Subject: [PATCH 172/210] =?UTF-8?q?Removed=20the=20intervalUpdateValues=20?= =?UTF-8?q?=E2=80=8B=E2=80=8B=3D=20800=20line=20(commented).?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Software/src/battery/CHADEMO-BATTERY.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Software/src/battery/CHADEMO-BATTERY.cpp b/Software/src/battery/CHADEMO-BATTERY.cpp index f445724d..5207806c 100644 --- a/Software/src/battery/CHADEMO-BATTERY.cpp +++ b/Software/src/battery/CHADEMO-BATTERY.cpp @@ -1032,7 +1032,7 @@ void handle_chademo_sequence() { void setup_battery(void) { // Performs one time setup at startup - intervalUpdateValues = 800; // This mode requires the values to be updated faster +// intervalUpdateValues = 800; // This mode requires the values to be updated faster pinMode(CHADEMO_PIN_2, OUTPUT); digitalWrite(CHADEMO_PIN_2, LOW); pinMode(CHADEMO_PIN_10, OUTPUT); From 7eaeb7e15e157151c77bdeb91d7deece2d110053 Mon Sep 17 00:00:00 2001 From: josiahhiggs <79869367+josiahhiggs@users.noreply.github.com> Date: Mon, 18 Nov 2024 22:28:33 +1300 Subject: [PATCH 173/210] Tesla webserver Add multiplex messages to Tesla Battery, update datalayer exptended and add lines for advanced battery. --- Software/src/battery/TESLA-BATTERY.cpp | 314 ++++++++++++------ Software/src/datalayer/datalayer_extended.h | 28 +- .../webserver/advanced_battery_html.cpp | 53 +++ 3 files changed, 296 insertions(+), 99 deletions(-) diff --git a/Software/src/battery/TESLA-BATTERY.cpp b/Software/src/battery/TESLA-BATTERY.cpp index 6e4ea6b1..c914fc89 100644 --- a/Software/src/battery/TESLA-BATTERY.cpp +++ b/Software/src/battery/TESLA-BATTERY.cpp @@ -25,20 +25,21 @@ CAN_frame TESLA_221_2 = { static uint16_t sendContactorClosingMessagesStill = 300; static uint32_t battery_total_discharge = 0; static uint32_t battery_total_charge = 0; -static uint16_t battery_volts = 0; // V -static int16_t battery_amps = 0; // A -static uint16_t battery_raw_amps = 0; // A -static int16_t battery_max_temp = 0; // C* -static int16_t battery_min_temp = 0; // C* -static uint16_t battery_energy_buffer = 0; -static uint16_t battery_energy_to_charge_complete = 0; -static uint16_t battery_expected_energy_remaining = 0; -static uint8_t battery_full_charge_complete = 0; -static uint16_t battery_ideal_energy_remaining = 0; -static uint16_t battery_nominal_energy_remaining = 0; -static uint16_t battery_nominal_full_pack_energy = 600; -static uint16_t battery_beginning_of_life = 600; -static uint16_t battery_charge_time_remaining = 0; // Minutes +static uint16_t battery_volts = 0; // V +static int16_t battery_amps = 0; // A +static uint16_t battery_raw_amps = 0; // A +static int16_t battery_max_temp = 0; // C* +static int16_t battery_min_temp = 0; // C* +static uint16_t battery_energy_buffer = 0; // kWh +static uint16_t battery_energy_to_charge_complete = 0; // kWh +static uint16_t battery_expected_energy_remaining = 0; // kWh +static uint8_t battery_full_charge_complete = 0; // kWh +static uint8_t battery_fully_charged = 0; // kWh +static uint16_t battery_ideal_energy_remaining = 0; // kWh +static uint16_t battery_nominal_energy_remaining = 0; // kWh +static uint16_t battery_nominal_full_pack_energy = 600; // Kwh +static uint16_t battery_beginning_of_life = 600; // kWh +static uint16_t battery_charge_time_remaining = 0; // Minutes static uint16_t battery_regenerative_limit = 0; static uint16_t battery_discharge_limit = 0; static uint16_t battery_max_heat_park = 0; @@ -47,12 +48,13 @@ static uint16_t battery_max_discharge_current = 0; static uint16_t battery_max_charge_current = 0; static uint16_t battery_bms_max_voltage = 0; static uint16_t battery_bms_min_voltage = 0; -static uint16_t battery_high_voltage = 0; -static uint16_t battery_low_voltage = 0; -static uint16_t battery_output_current = 0; +static uint16_t battery_dcdcHvBusVolt = 0; // Change name from battery_high_voltage to battery_dcdcHvBusVolt +static uint16_t battery_dcdcLvBusVolt = 0; // Change name from battery_low_voltage to battery_dcdcLvBusVolt +static uint16_t battery_dcdcLvOutputCurrent = + 0; // Change name from battery_output_current to battery_dcdcLvOutputCurrent static uint16_t battery_soc_min = 0; static uint16_t battery_soc_max = 0; -static uint16_t battery_soc_vi = 0; +static uint16_t battery_soc_ui = 0; //Change name from battery_soc_vi to reflect DBC battery_soc_ui static uint16_t battery_soc_ave = 0; static uint16_t battery_cell_max_v = 3700; static uint16_t battery_cell_min_v = 3700; @@ -66,6 +68,12 @@ static uint8_t battery_packContPositiveState = 0; static uint8_t battery_packContactorSetState = 0; static uint8_t battery_packCtrsClosingAllowed = 0; static uint8_t battery_pyroTestInProgress = 0; +static uint8_t battery_battTempPct = 0; +static uint32_t battery_packMass = 0; +static uint32_t battery_platformMaxBusVoltage = 0; +static uint32_t battery_packConfigMultiplexer = 0; +static uint32_t battery_moduleType = 0; +static uint32_t battery_reservedConfig = 0; //Fault codes static uint8_t battery_WatchdogReset = 0; //Warns if the processor has experienced a reset due to watchdog reset. static uint8_t battery_PowerLossReset = 0; //Warns if the processor has experienced a reset due to power loss. @@ -138,6 +146,7 @@ static uint16_t battery2_energy_buffer = 0; static uint16_t battery2_energy_to_charge_complete = 0; static uint16_t battery2_expected_energy_remaining = 0; static uint8_t battery2_full_charge_complete = 0; +static uint8_t battery2_fully_charged = 0; static uint16_t battery2_ideal_energy_remaining = 0; static uint16_t battery2_nominal_energy_remaining = 0; static uint16_t battery2_nominal_full_pack_energy = 600; @@ -151,12 +160,12 @@ static uint16_t battery2_max_discharge_current = 0; static uint16_t battery2_max_charge_current = 0; static uint16_t battery2_bms_max_voltage = 0; static uint16_t battery2_bms_min_voltage = 0; -static uint16_t battery2_high_voltage = 0; -static uint16_t battery2_low_voltage = 0; -static uint16_t battery2_output_current = 0; +static uint16_t battery2_dcdcHvBusVolt = 0; //update name +static uint16_t battery2_dcdcLvBusVolt = 0; //update name +static uint16_t battery2_dcdcLvOutputCurrent = 0; //update name static uint16_t battery2_soc_min = 0; static uint16_t battery2_soc_max = 0; -static uint16_t battery2_soc_vi = 0; +static uint16_t battery2_soc_ui = 0; static uint16_t battery2_soc_ave = 0; static uint16_t battery2_cell_max_v = 3700; static uint16_t battery2_cell_min_v = 3700; @@ -170,6 +179,12 @@ static uint8_t battery2_packContPositiveState = 0; static uint8_t battery2_packContactorSetState = 0; static uint8_t battery2_packCtrsClosingAllowed = 0; static uint8_t battery2_pyroTestInProgress = 0; +static uint8_t battery2_battTempPct = 0; +static uint32_t battery2_packMass = 0; +static uint32_t battery2_platformMaxBusVoltage = 0; +static uint32_t battery2_packConfigMultiplexer = 0; +static uint32_t battery2_moduleType = 0; +static uint32_t battery2_reservedConfig = 0; //Fault codes static uint8_t battery2_WatchdogReset = 0; //Warns if the processor has experienced a reset due to watchdog reset. static uint8_t battery2_PowerLossReset = 0; //Warns if the processor has experienced a reset due to power loss. @@ -259,7 +274,7 @@ void update_values_battery() { //This function maps all the values fetched via datalayer.battery.status.soh_pptt = 9900; //Tesla batteries do not send a SOH% value on bus. Hardcode to 99% - datalayer.battery.status.real_soc = (battery_soc_vi * 10); //increase SOC range from 0-100.0 -> 100.00 + datalayer.battery.status.real_soc = (battery_soc_ui * 10); //increase SOC range from 0-100.0 -> 100.00 datalayer.battery.status.voltage_dV = (battery_volts * 10); //One more decimal needed (370 -> 3700) @@ -277,12 +292,12 @@ void update_values_battery() { //This function maps all the values fetched via } //The allowed charge power behaves strangely. We instead estimate this value - if (battery_soc_vi > 990) { + if (battery_soc_ui > 990) { datalayer.battery.status.max_charge_power_W = FLOAT_MAX_POWER_W; - } else if (battery_soc_vi > + } else if (battery_soc_ui > RAMPDOWN_SOC) { // When real SOC is between RAMPDOWN_SOC-99%, ramp the value between Max<->0 datalayer.battery.status.max_charge_power_W = - RAMPDOWNPOWERALLOWED * (1 - (battery_soc_vi - RAMPDOWN_SOC) / (1000.0 - RAMPDOWN_SOC)); + RAMPDOWNPOWERALLOWED * (1 - (battery_soc_ui - RAMPDOWN_SOC) / (1000.0 - RAMPDOWN_SOC)); //If the cellvoltages start to reach overvoltage, only allow a small amount of power in if (datalayer.battery.info.chemistry == battery_chemistry_enum::LFP) { if (battery_cell_max_v > (MAX_CELL_VOLTAGE_LFP - FLOAT_START_MV)) { @@ -348,6 +363,33 @@ void update_values_battery() { //This function maps all the values fetched via datalayer_extended.tesla.packContactorSetState = battery_packContactorSetState; datalayer_extended.tesla.packCtrsClosingAllowed = battery_packCtrsClosingAllowed; datalayer_extended.tesla.pyroTestInProgress = battery_pyroTestInProgress; + datalayer_extended.tesla.battery_beginning_of_life = battery_beginning_of_life; //add from her down + datalayer_extended.tesla.battery_battTempPct = battery_battTempPct; + datalayer_extended.tesla.battery_dcdcLvBusVolt = battery_dcdcLvBusVolt; + datalayer_extended.tesla.battery_dcdcHvBusVolt = battery_dcdcHvBusVolt; + datalayer_extended.tesla.battery_dcdcLvOutputCurrent = battery_dcdcLvOutputCurrent; + datalayer_extended.tesla.battery_nominal_full_pack_energy = battery_nominal_full_pack_energy; + datalayer_extended.tesla.battery_nominal_energy_remaining = battery_nominal_energy_remaining; + datalayer_extended.tesla.battery_ideal_energy_remaining = battery_ideal_energy_remaining; + datalayer_extended.tesla.battery_energy_to_charge_complete = battery_energy_to_charge_complete; + datalayer_extended.tesla.battery_energy_buffer = battery_energy_buffer; + datalayer_extended.tesla.battery_full_charge_complete = battery_full_charge_complete; + datalayer_extended.tesla.battery_total_discharge = battery_total_discharge; + datalayer_extended.tesla.battery_total_charge = battery_total_charge; + datalayer_extended.tesla.battery_fully_charged = battery_fully_charged; + datalayer_extended.tesla.battery_packConfigMultiplexer = battery_packConfigMultiplexer; + datalayer_extended.tesla.battery_moduleType = battery_moduleType; + datalayer_extended.tesla.battery_reservedConfig = battery_reservedConfig; + datalayer_extended.tesla.battery_packMass = battery_packMass; + datalayer_extended.tesla.battery_platformMaxBusVoltage = battery_platformMaxBusVoltage; + datalayer_extended.tesla.battery_bms_min_voltage = battery_bms_min_voltage; + datalayer_extended.tesla.battery_bms_max_voltage = battery_bms_max_voltage; + datalayer_extended.tesla.battery_max_charge_current = battery_max_charge_current; + datalayer_extended.tesla.battery_max_discharge_current = battery_max_discharge_current; + datalayer_extended.tesla.battery_soc_ave = battery_soc_ave; + datalayer_extended.tesla.battery_soc_max = battery_soc_max; + datalayer_extended.tesla.battery_soc_min = battery_soc_min; + datalayer_extended.tesla.battery_soc_ui = battery_soc_ui; #ifdef DEBUG_VIA_USB @@ -370,7 +412,7 @@ void update_values_battery() { //This function maps all the values fetched via Serial.print("Battery values: "); Serial.print("Real SOC: "); - Serial.print(battery_soc_vi / 10.0, 1); + Serial.print(battery_soc_ui / 10.0, 1); print_int_with_units(", Battery voltage: ", battery_volts, "V"); print_int_with_units(", Battery HV current: ", (battery_amps * 0.1), "A"); Serial.print(", Fully charged?: "); @@ -394,11 +436,11 @@ void update_values_battery() { //This function maps all the values fetched via Serial.print(battery_cell_deviation_mV); Serial.println("mV."); - print_int_with_units("High Voltage Output Pins: ", battery_high_voltage, "V"); + print_int_with_units("High Voltage Output Pins: ", battery_dcdcHvBusVolt, "V"); Serial.print(", "); - print_int_with_units("Low Voltage: ", battery_low_voltage, "V"); + print_int_with_units("Low Voltage: ", battery_dcdcLvBusVolt, "V"); Serial.println(""); - print_int_with_units("DC/DC 12V current: ", battery_output_current, "A"); + print_int_with_units("DC/DC 12V current: ", battery_dcdcLvOutputCurrent, "A"); Serial.println(""); Serial.println("Values passed to the inverter: "); @@ -420,20 +462,47 @@ void receive_can_battery(CAN_frame rx_frame) { switch (rx_frame.ID) { case 0x352: - //SOC - battery_nominal_full_pack_energy = - (((rx_frame.data.u8[1] & 0x0F) << 8) | (rx_frame.data.u8[0])); //Example 752 (75.2kWh) - battery_nominal_energy_remaining = (((rx_frame.data.u8[2] & 0x3F) << 5) | ((rx_frame.data.u8[1] & 0xF8) >> 3)) * - 0.1; //Example 1247 * 0.1 = 124.7kWh - battery_expected_energy_remaining = (((rx_frame.data.u8[4] & 0x01) << 10) | (rx_frame.data.u8[3] << 2) | - ((rx_frame.data.u8[2] & 0xC0) >> 6)); //Example 622 (62.2kWh) - battery_ideal_energy_remaining = (((rx_frame.data.u8[5] & 0x0F) << 7) | ((rx_frame.data.u8[4] & 0xFE) >> 1)) * - 0.1; //Example 311 * 0.1 = 31.1kWh - battery_energy_to_charge_complete = (((rx_frame.data.u8[6] & 0x7F) << 4) | ((rx_frame.data.u8[5] & 0xF0) >> 4)) * - 0.1; //Example 147 * 0.1 = 14.7kWh - battery_energy_buffer = - (((rx_frame.data.u8[7] & 0x7F) << 1) | ((rx_frame.data.u8[6] & 0x80) >> 7)) * 0.1; //Example 1 * 0.1 = 0 - battery_full_charge_complete = ((rx_frame.data.u8[7] & 0x80) >> 7); + mux = (rx_frame.data.u8[0] & 0x02); //BMS_energyStatusIndex M : 0|2@1+ (1,0) [0|0] "" X + if (mux == 3) { + battery_nominal_full_pack_energy = //BMS_nominalFullPackEnergy : 0|11@1+ (0.1,0) [0|204.6] "KWh" //((_d[1] & (0x07U)) << 8) | (_d[0] & (0xFFU)); + (((rx_frame.data.u8[1] & 0x07) << 8) | (rx_frame.data.u8[0])); //Example 752 (75.2kWh) + battery_nominal_energy_remaining = //BMS_nominalEnergyRemaining : 11|11@1+ (0.1,0) [0|204.6] "KWh" //((_d[2] & (0x3FU)) << 5) | ((_d[1] >> 3) & (0x1FU)); + (((rx_frame.data.u8[2] & 0x3F) << 5) | + ((rx_frame.data.u8[1] & 0x1F) >> 3)); //Example 1247 * 0.1 = 124.7kWh + battery_expected_energy_remaining = //BMS_expectedEnergyRemaining : 22|11@1+ (0.1,0) [0|204.6] "KWh"// ((_d[4] & (0x01U)) << 10) | ((_d[3] & (0xFFU)) << 2) | ((_d[2] >> 6) & (0x03U)); + (((rx_frame.data.u8[4] & 0x01) << 10) | (rx_frame.data.u8[3] << 2) | + ((rx_frame.data.u8[2] & 0x03) >> 6)); //Example 622 (62.2kWh) + battery_ideal_energy_remaining = //BMS_idealEnergyRemaining : 33|11@1+ (0.1,0) [0|204.6] "KWh" //((_d[5] & (0x0FU)) << 7) | ((_d[4] >> 1) & (0x7FU)); + (((rx_frame.data.u8[5] & 0x0F) << 7) | ((rx_frame.data.u8[4] & 0x7F) >> 1)); //Example 311 * 0.1 = 31.1kWh + battery_energy_to_charge_complete = // BMS_energyToChargeComplete : 44|11@1+ (0.1,0) [0|204.6] "KWh"// ((_d[6] & (0x7FU)) << 4) | ((_d[5] >> 4) & (0x0FU)); + (((rx_frame.data.u8[6] & 0x7F) << 4) | ((rx_frame.data.u8[5] & 0x0F) << 4)); //Example 147 * 0.1 = 14.7kWh + battery_energy_buffer = //BMS_energyBuffer : 55|8@1+ (0.1,0) [0|25.4] "KWh"// ((_d[7] & (0x7FU)) << 1) | ((_d[6] >> 7) & (0x01U)); + (((rx_frame.data.u8[7] & 0x7F) << 1) | ((rx_frame.data.u8[6] & 0x01) >> 7)); //Example 1 * 0.1 = 0 + battery_full_charge_complete = //BMS_fullChargeComplete : 63|1@1+ (1,0) [0|1] ""//((_d[7] >> 7) & (0x01U)); + ((rx_frame.data.u8[7] & 0x01) >> 7); + } + if (mux == 0) { + battery_nominal_full_pack_energy = + (rx_frame.data.u8[3] | + rx_frame.data.u8[2]); //SG_ BMS_nominalFullPackEnergy m0 : 16|16@1+ (0.02,0) [0|0] "kWh" X + battery_nominal_energy_remaining = + (rx_frame.data.u8[5] | + rx_frame.data.u8[4]); //SG_ BMS_nominalEnergyRemaining m0 : 32|16@1+ (0.02,0) [0|0] "kWh" X + battery_ideal_energy_remaining = + (rx_frame.data.u8[7] | + rx_frame.data.u8[6]); //SG_ BMS_idealEnergyRemaining m0 : 48|16@1+ (0.02,0) [0|0] "kWh" X + } + if (mux == 1) { + battery_fully_charged = (rx_frame.data.u8[1] & 0x01); //SG_ BMS_fullyCharged m1 : 15|1@1+ (1,0) [0|1] "" X + battery_energy_buffer = + (rx_frame.data.u8[3] | rx_frame.data.u8[2]); //SG_ BMS_energyBuffer m1 : 16|16@1+ (0.01,0) [0|0] "kWh" X + battery_expected_energy_remaining = + (rx_frame.data.u8[5] | + rx_frame.data.u8[4]); //SG_ BMS_expectedEnergyRemaining m1 : 32|16@1+ (0.02,0) [0|0] "kWh" X + battery_energy_to_charge_complete = + (rx_frame.data.u8[7] | + rx_frame.data.u8[6]); //SG_ BMS_energyToChargeComplete m1 : 48|16@1+ (0.02,0) [0|0] "kWh" X + } break; case 0x20A: //Contactor state @@ -471,11 +540,9 @@ void receive_can_battery(CAN_frame rx_frame) { case 0x3D2: // total charge/discharge kwh battery_total_discharge = ((rx_frame.data.u8[3] << 24) | (rx_frame.data.u8[2] << 16) | - (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]) * - 0.001; + (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]); battery_total_charge = ((rx_frame.data.u8[7] << 24) | (rx_frame.data.u8[6] << 16) | (rx_frame.data.u8[5] << 8) | - rx_frame.data.u8[4]) * - 0.001; + rx_frame.data.u8[4]); break; case 0x332: //min/max hist values @@ -531,28 +598,41 @@ void receive_can_battery(CAN_frame rx_frame) { } break; case 0x2d2: - //Min / max limits - battery_bms_min_voltage = - ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]) * 0.01 * 2; //Example 24148mv * 0.01 = 241.48 V - battery_bms_max_voltage = - ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]) * 0.01 * 2; //Example 40282mv * 0.01 = 402.82 V + //Min / max limits //BMSVAlimits: //move factoring to datalayer? + battery_bms_min_voltage = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]); //Example 24148mv * 0.01 = 241.48 V + battery_bms_max_voltage = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]); //Example 40282mv * 0.01 = 402.82 V battery_max_charge_current = - (((rx_frame.data.u8[5] & 0x3F) << 8) | rx_frame.data.u8[4]) * 0.1; //Example 1301? * 0.1 = 130.1? + (((rx_frame.data.u8[5] & 0x3F) << 8) | rx_frame.data.u8[4]); //Example 1301? * 0.1 = 130.1? battery_max_discharge_current = - (((rx_frame.data.u8[7] & 0x3F) << 8) | rx_frame.data.u8[6]) * 0.128; //Example 430? * 0.128 = 55.4? + (((rx_frame.data.u8[7] & 0x3F) << 8) | rx_frame.data.u8[6]); //Example 430? * 0.128 = 55.4? break; - case 0x2b4: - battery_low_voltage = (((rx_frame.data.u8[1] & 0x03) << 8) | rx_frame.data.u8[0]) * 0.0390625; - battery_high_voltage = ((((rx_frame.data.u8[2] & 0x3F) << 6) | ((rx_frame.data.u8[1] & 0xFC) >> 2))) * 0.146484; - battery_output_current = (((rx_frame.data.u8[4] & 0x0F) << 8) | rx_frame.data.u8[3]) / 100; + case 0x2b4: //PCS_dcdcRailStatus: + battery_dcdcLvBusVolt = (((rx_frame.data.u8[1] & 0x03) << 8) | rx_frame.data.u8[0]); //update name move factoring + battery_dcdcHvBusVolt = + ((((rx_frame.data.u8[2] & 0x3F) << 6) | ((rx_frame.data.u8[1] & 0xFC) >> 2))); //update name move factoring + battery_dcdcLvOutputCurrent = + (((rx_frame.data.u8[4] & 0x0F) << 8) | rx_frame.data.u8[3]); //update name move factoring break; - case 0x292: + case 0x292: //BMS_socStatus datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; //We are getting CAN messages from the BMS battery_beginning_of_life = (((rx_frame.data.u8[6] & 0x03) << 8) | rx_frame.data.u8[5]); battery_soc_min = (((rx_frame.data.u8[1] & 0x03) << 8) | rx_frame.data.u8[0]); - battery_soc_vi = (((rx_frame.data.u8[2] & 0x0F) << 6) | ((rx_frame.data.u8[1] & 0xFC) >> 2)); + battery_soc_ui = (((rx_frame.data.u8[2] & 0x0F) << 6) | ((rx_frame.data.u8[1] & 0xFC) >> 2)); battery_soc_max = (((rx_frame.data.u8[3] & 0x3F) << 4) | ((rx_frame.data.u8[2] & 0xF0) >> 4)); battery_soc_ave = ((rx_frame.data.u8[4] << 2) | ((rx_frame.data.u8[3] & 0xC0) >> 6)); + battery_battTempPct = ((rx_frame.data.u8[7] & 0x03) << 6) | (rx_frame.data.u8[6] & (0x3F) >> 2); + break; + case 0x392: //BMS_packConfig + mux = (rx_frame.data.u8[0] & (0xFF)); + if (mux == 1) { + battery_packConfigMultiplexer = (rx_frame.data.u8[0] & (0xff)); + battery_moduleType = (rx_frame.data.u8[1] & (0x07)); + battery_packMass = (rx_frame.data.u8[2]); + battery_platformMaxBusVoltage = ((rx_frame.data.u8[4] & 0x03) << 8) | (rx_frame.data.u8[3]); + } + if (mux == 0) { + battery_reservedConfig = (rx_frame.data.u8[1] & (0x1F)); + } break; case 0x3aa: //HVP_alertMatrix1 battery_WatchdogReset = (rx_frame.data.u8[0] & 0x01); @@ -620,20 +700,47 @@ void receive_can_battery2(CAN_frame rx_frame) { switch (rx_frame.ID) { case 0x352: - //SOC - battery2_nominal_full_pack_energy = - (((rx_frame.data.u8[1] & 0x0F) << 8) | (rx_frame.data.u8[0])); //Example 752 (75.2kWh) - battery2_nominal_energy_remaining = (((rx_frame.data.u8[2] & 0x3F) << 5) | ((rx_frame.data.u8[1] & 0xF8) >> 3)) * - 0.1; //Example 1247 * 0.1 = 124.7kWh - battery2_expected_energy_remaining = (((rx_frame.data.u8[4] & 0x01) << 10) | (rx_frame.data.u8[3] << 2) | - ((rx_frame.data.u8[2] & 0xC0) >> 6)); //Example 622 (62.2kWh) - battery2_ideal_energy_remaining = (((rx_frame.data.u8[5] & 0x0F) << 7) | ((rx_frame.data.u8[4] & 0xFE) >> 1)) * - 0.1; //Example 311 * 0.1 = 31.1kWh - battery2_energy_to_charge_complete = (((rx_frame.data.u8[6] & 0x7F) << 4) | ((rx_frame.data.u8[5] & 0xF0) >> 4)) * - 0.1; //Example 147 * 0.1 = 14.7kWh - battery2_energy_buffer = - (((rx_frame.data.u8[7] & 0x7F) << 1) | ((rx_frame.data.u8[6] & 0x80) >> 7)) * 0.1; //Example 1 * 0.1 = 0 - battery2_full_charge_complete = ((rx_frame.data.u8[7] & 0x80) >> 7); + mux = (rx_frame.data.u8[0] & 0x02); //BMS_energyStatusIndex M : 0|2@1+ (1,0) [0|0] "" X + if (mux == 3) { + battery2_nominal_full_pack_energy = //BMS_nominalFullPackEnergy : 0|11@1+ (0.1,0) [0|204.6] "KWh" //((_d[1] & (0x07U)) << 8) | (_d[0] & (0xFFU)); + (((rx_frame.data.u8[1] & 0x07) << 8) | (rx_frame.data.u8[0])); //Example 752 (75.2kWh) + battery2_nominal_energy_remaining = //BMS_nominalEnergyRemaining : 11|11@1+ (0.1,0) [0|204.6] "KWh" //((_d[2] & (0x3FU)) << 5) | ((_d[1] >> 3) & (0x1FU)); + (((rx_frame.data.u8[2] & 0x3F) << 5) | + ((rx_frame.data.u8[1] & 0x1F) >> 3)); //Example 1247 * 0.1 = 124.7kWh + battery2_expected_energy_remaining = //BMS_expectedEnergyRemaining : 22|11@1+ (0.1,0) [0|204.6] "KWh"// ((_d[4] & (0x01U)) << 10) | ((_d[3] & (0xFFU)) << 2) | ((_d[2] >> 6) & (0x03U)); + (((rx_frame.data.u8[4] & 0x01) << 10) | (rx_frame.data.u8[3] << 2) | + ((rx_frame.data.u8[2] & 0x03) >> 6)); //Example 622 (62.2kWh) + battery2_ideal_energy_remaining = //BMS_idealEnergyRemaining : 33|11@1+ (0.1,0) [0|204.6] "KWh" //((_d[5] & (0x0FU)) << 7) | ((_d[4] >> 1) & (0x7FU)); + (((rx_frame.data.u8[5] & 0x0F) << 7) | ((rx_frame.data.u8[4] & 0x7F) >> 1)); //Example 311 * 0.1 = 31.1kWh + battery2_energy_to_charge_complete = // BMS_energyToChargeComplete : 44|11@1+ (0.1,0) [0|204.6] "KWh"// ((_d[6] & (0x7FU)) << 4) | ((_d[5] >> 4) & (0x0FU)); + (((rx_frame.data.u8[6] & 0x7F) << 4) | ((rx_frame.data.u8[5] & 0x0F) << 4)); //Example 147 * 0.1 = 14.7kWh + battery2_energy_buffer = //BMS_energyBuffer : 55|8@1+ (0.1,0) [0|25.4] "KWh"// ((_d[7] & (0x7FU)) << 1) | ((_d[6] >> 7) & (0x01U)); + (((rx_frame.data.u8[7] & 0x7F) << 1) | ((rx_frame.data.u8[6] & 0x01) >> 7)); //Example 1 * 0.1 = 0 + battery2_full_charge_complete = //BMS_fullChargeComplete : 63|1@1+ (1,0) [0|1] ""//((_d[7] >> 7) & (0x01U)); + ((rx_frame.data.u8[7] & 0x01) >> 7); + } + if (mux == 0) { + battery2_nominal_full_pack_energy = + (rx_frame.data.u8[3] | + rx_frame.data.u8[2]); //SG_ BMS_nominalFullPackEnergy m0 : 16|16@1+ (0.02,0) [0|0] "kWh" X + battery2_nominal_energy_remaining = + (rx_frame.data.u8[5] | + rx_frame.data.u8[4]); //SG_ BMS_nominalEnergyRemaining m0 : 32|16@1+ (0.02,0) [0|0] "kWh" X + battery2_ideal_energy_remaining = + (rx_frame.data.u8[7] | + rx_frame.data.u8[6]); //SG_ BMS_idealEnergyRemaining m0 : 48|16@1+ (0.02,0) [0|0] "kWh" X + } + if (mux == 1) { + battery2_fully_charged = (rx_frame.data.u8[1] & 0x01); //SG_ BMS_fullyCharged m1 : 15|1@1+ (1,0) [0|1] "" X + battery2_energy_buffer = + (rx_frame.data.u8[3] | rx_frame.data.u8[2]); //SG_ BMS_energyBuffer m1 : 16|16@1+ (0.01,0) [0|0] "kWh" X + battery2_expected_energy_remaining = + (rx_frame.data.u8[5] | + rx_frame.data.u8[4]); //SG_ BMS_expectedEnergyRemaining m1 : 32|16@1+ (0.02,0) [0|0] "kWh" X + battery2_energy_to_charge_complete = + (rx_frame.data.u8[7] | + rx_frame.data.u8[6]); //SG_ BMS_energyToChargeComplete m1 : 48|16@1+ (0.02,0) [0|0] "kWh" X + } break; case 0x20A: //Contactor state @@ -671,11 +778,9 @@ void receive_can_battery2(CAN_frame rx_frame) { case 0x3D2: // total charge/discharge kwh battery2_total_discharge = ((rx_frame.data.u8[3] << 24) | (rx_frame.data.u8[2] << 16) | - (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]) * - 0.001; + (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]); battery2_total_charge = ((rx_frame.data.u8[7] << 24) | (rx_frame.data.u8[6] << 16) | (rx_frame.data.u8[5] << 8) | - rx_frame.data.u8[4]) * - 0.001; + rx_frame.data.u8[4]); break; case 0x332: //min/max hist values @@ -730,29 +835,42 @@ void receive_can_battery2(CAN_frame rx_frame) { } } break; - case 0x2d2: + case 0x2d2: //BMSVAlimits: //Min / max limits battery2_bms_min_voltage = - ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]) * 0.01 * 2; //Example 24148mv * 0.01 = 241.48 V + ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]); //Example 24148mv * 0.01 = 241.48 V battery2_bms_max_voltage = - ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]) * 0.01 * 2; //Example 40282mv * 0.01 = 402.82 V + ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]); //Example 40282mv * 0.01 = 402.82 V battery2_max_charge_current = - (((rx_frame.data.u8[5] & 0x3F) << 8) | rx_frame.data.u8[4]) * 0.1; //Example 1301? * 0.1 = 130.1? + (((rx_frame.data.u8[5] & 0x3F) << 8) | rx_frame.data.u8[4]); //Example 1301? * 0.1 = 130.1? battery2_max_discharge_current = - (((rx_frame.data.u8[7] & 0x3F) << 8) | rx_frame.data.u8[6]) * 0.128; //Example 430? * 0.128 = 55.4? + (((rx_frame.data.u8[7] & 0x3F) << 8) | rx_frame.data.u8[6]); //Example 430? * 0.128 = 55.4? break; - case 0x2b4: - battery2_low_voltage = (((rx_frame.data.u8[1] & 0x03) << 8) | rx_frame.data.u8[0]) * 0.0390625; - battery2_high_voltage = ((((rx_frame.data.u8[2] & 0x3F) << 6) | ((rx_frame.data.u8[1] & 0xFC) >> 2))) * 0.146484; - battery2_output_current = (((rx_frame.data.u8[4] & 0x0F) << 8) | rx_frame.data.u8[3]) / 100; + case 0x2b4: //PCS_dcdcRailStatus: + battery2_dcdcLvBusVolt = (((rx_frame.data.u8[1] & 0x03) << 8) | rx_frame.data.u8[0]); + battery2_dcdcHvBusVolt = ((((rx_frame.data.u8[2] & 0x3F) << 6) | ((rx_frame.data.u8[1] & 0xFC) >> 2))); + battery2_dcdcLvOutputCurrent = (((rx_frame.data.u8[4] & 0x0F) << 8) | rx_frame.data.u8[3]); break; - case 0x292: + case 0x292: //BMS_socStatus datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE; //We are getting CAN messages from the BMS battery2_beginning_of_life = (((rx_frame.data.u8[6] & 0x03) << 8) | rx_frame.data.u8[5]); battery2_soc_min = (((rx_frame.data.u8[1] & 0x03) << 8) | rx_frame.data.u8[0]); - battery2_soc_vi = (((rx_frame.data.u8[2] & 0x0F) << 6) | ((rx_frame.data.u8[1] & 0xFC) >> 2)); + battery2_soc_ui = (((rx_frame.data.u8[2] & 0x0F) << 6) | ((rx_frame.data.u8[1] & 0xFC) >> 2)); battery2_soc_max = (((rx_frame.data.u8[3] & 0x3F) << 4) | ((rx_frame.data.u8[2] & 0xF0) >> 4)); battery2_soc_ave = ((rx_frame.data.u8[4] << 2) | ((rx_frame.data.u8[3] & 0xC0) >> 6)); + battery2_battTempPct = ((rx_frame.data.u8[7] & 0x03) << 6) | (rx_frame.data.u8[6] & (0x3F) >> 2); + break; + case 0x392: //BMS_packConfig + mux = (rx_frame.data.u8[0] & (0xFF)); + if (mux == 1) { + battery2_packConfigMultiplexer = (rx_frame.data.u8[0] & (0xff)); + battery2_moduleType = (rx_frame.data.u8[1] & (0x07)); + battery2_packMass = (rx_frame.data.u8[2]); + battery2_platformMaxBusVoltage = ((rx_frame.data.u8[4] & 0x03) << 8) | (rx_frame.data.u8[3]); + } + if (mux == 0) { + battery2_reservedConfig = (rx_frame.data.u8[1] & (0x1F)); + } break; case 0x3aa: //HVP_alertMatrix1 battery2_WatchdogReset = (rx_frame.data.u8[0] & 0x01); @@ -817,7 +935,7 @@ void update_values_battery2() { //This function maps all the values fetched via datalayer.battery2.status.soh_pptt = 9900; //Tesla batteries do not send a SOH% value on bus. Hardcode to 99% - datalayer.battery2.status.real_soc = (battery2_soc_vi * 10); //increase SOC range from 0-100.0 -> 100.00 + datalayer.battery2.status.real_soc = (battery2_soc_ui * 10); //increase SOC range from 0-100.0 -> 100.00 datalayer.battery2.status.voltage_dV = (battery2_volts * 10); //One more decimal needed (370 -> 3700) @@ -835,12 +953,12 @@ void update_values_battery2() { //This function maps all the values fetched via } //The allowed charge power behaves strangely. We instead estimate this value - if (battery2_soc_vi > 990) { + if (battery2_soc_ui > 990) { datalayer.battery2.status.max_charge_power_W = FLOAT_MAX_POWER_W; - } else if (battery2_soc_vi > + } else if (battery2_soc_ui > RAMPDOWN_SOC) { // When real SOC is between RAMPDOWN_SOC-99%, ramp the value between Max<->0 datalayer.battery2.status.max_charge_power_W = - MAXCHARGEPOWERALLOWED * (1 - (battery2_soc_vi - RAMPDOWN_SOC) / (1000.0 - RAMPDOWN_SOC)); + MAXCHARGEPOWERALLOWED * (1 - (battery2_soc_ui - RAMPDOWN_SOC) / (1000.0 - RAMPDOWN_SOC)); //If the cellvoltages start to reach overvoltage, only allow a small amount of power in if (datalayer.battery2.info.chemistry == battery_chemistry_enum::LFP) { if (battery2_cell_max_v > (MAX_CELL_VOLTAGE_LFP - FLOAT_START_MV)) { @@ -919,7 +1037,7 @@ void update_values_battery2() { //This function maps all the values fetched via Serial.print("Battery2 values: "); Serial.print("Real SOC: "); - Serial.print(battery2_soc_vi / 10.0, 1); + Serial.print(battery2_soc_ui / 10.0, 1); print_int_with_units(", Battery2 voltage: ", battery2_volts, "V"); print_int_with_units(", Battery2 HV current: ", (battery2_amps * 0.1), "A"); Serial.print(", Fully charged?: "); @@ -943,11 +1061,11 @@ void update_values_battery2() { //This function maps all the values fetched via Serial.print(battery2_cell_deviation_mV); Serial.println("mV."); - print_int_with_units("High Voltage Output Pins: ", battery2_high_voltage, "V"); + print_int_with_units("High Voltage Output Pins: ", battery2_dcdcHvBusVolt, "V"); Serial.print(", "); - print_int_with_units("Low Voltage: ", battery2_low_voltage, "V"); + print_int_with_units("Low Voltage: ", battery2_dcdcLvBusVolt, "V"); Serial.println(""); - print_int_with_units("DC/DC 12V current: ", battery2_output_current, "A"); + print_int_with_units("DC/DC 12V current: ", battery2_dcdcLvOutputCurrent, "A"); Serial.println(""); Serial.println("Values passed to the inverter: "); diff --git a/Software/src/datalayer/datalayer_extended.h b/Software/src/datalayer/datalayer_extended.h index 5370637c..f80c143e 100644 --- a/Software/src/datalayer/datalayer_extended.h +++ b/Software/src/datalayer/datalayer_extended.h @@ -177,7 +177,33 @@ typedef struct { /** uint8_t */ /** Pyro test in progress */ uint8_t pyroTestInProgress = 0; - + uint8_t battery_beginning_of_life = 0; + uint8_t battery_battTempPct = 0; + uint16_t battery_dcdcLvBusVolt = 0; + uint16_t battery_dcdcHvBusVolt = 0; + uint16_t battery_dcdcLvOutputCurrent = 0; + uint16_t battery_nominal_full_pack_energy = 0; + uint16_t battery_nominal_energy_remaining = 0; + uint16_t battery_ideal_energy_remaining = 0; + uint16_t battery_energy_to_charge_complete = 0; + uint16_t battery_energy_buffer = 0; + uint16_t battery_full_charge_complete = 0; + uint8_t battery_fully_charged = 0; + uint16_t battery_total_discharge = 0; + uint16_t battery_total_charge = 0; + uint16_t battery_packConfigMultiplexer = 0; + uint16_t battery_moduleType = 0; + uint16_t battery_reservedConfig = 0; + uint32_t battery_packMass = 0; + uint32_t battery_platformMaxBusVoltage = 0; + uint32_t battery_bms_min_voltage = 0; + uint32_t battery_bms_max_voltage = 0; + uint32_t battery_max_charge_current = 0; + uint32_t battery_max_discharge_current = 0; + uint32_t battery_soc_min = 0; + uint32_t battery_soc_max = 0; + uint32_t battery_soc_ave = 0; + uint32_t battery_soc_ui = 0; } DATALAYER_INFO_TESLA; typedef struct { diff --git a/Software/src/devboard/webserver/advanced_battery_html.cpp b/Software/src/devboard/webserver/advanced_battery_html.cpp index 2a301a07..c26b6aeb 100644 --- a/Software/src/devboard/webserver/advanced_battery_html.cpp +++ b/Software/src/devboard/webserver/advanced_battery_html.cpp @@ -283,6 +283,59 @@ String advanced_battery_processor(const String& var) { #endif //BYD_ATTO_3_BATTERY #ifdef TESLA_BATTERY + float beginning_of_life = static_cast(datalayer_extended.tesla.battery_beginning_of_life) * 0.1; + content += "

Battery Beginning of Life: " + String(beginning_of_life) + " kWh

"; + float battTempPct = static_cast(datalayer_extended.tesla.battery_battTempPct) * 0.4; + content += "

BattTempPct: " + String(battTempPct) + "

"; + float dcdcLvBusVolt = static_cast(datalayer_extended.tesla.battery_dcdcLvBusVolt) * 0.0390625; + content += "

PCS Lv Bus: " + String(dcdcLvBusVolt) + " V

"; + float dcdcHvBusVolt = static_cast(datalayer_extended.tesla.battery_dcdcHvBusVolt) * 0.146484; + content += "

PCS Hv Bus: " + String(dcdcHvBusVolt) + " V

"; + float dcdcLvOutputCurrent = static_cast(datalayer_extended.tesla.battery_dcdcLvOutputCurrent) * 0.1; + content += "

PCS Lv Output: " + String(dcdcLvOutputCurrent) + " A

"; + float nominal_full_pack_energy = + static_cast(datalayer_extended.tesla.battery_nominal_full_pack_energy) * 0.1; + content += "

Nominal Full Pack Energy: " + String(nominal_full_pack_energy) + " kWh

"; + float nominal_energy_remaining = + static_cast(datalayer_extended.tesla.battery_nominal_energy_remaining) * 0.1; + content += "

Nominal Energy Remaining: " + String(nominal_energy_remaining) + " kWh

"; + float ideal_energy_remaining = static_cast(datalayer_extended.tesla.battery_ideal_energy_remaining) * 0.1; + content += "

Ideal Energy Remaining: " + String(ideal_energy_remaining) + " kWh

"; + float energy_to_charge_complete = + static_cast(datalayer_extended.tesla.battery_energy_to_charge_complete) * 0.1; + content += "

Energy to Charge Complete: " + String(energy_to_charge_complete) + " kWh

"; + float energy_buffer = static_cast(datalayer_extended.tesla.battery_energy_buffer) * 0.1; + content += "

Energy Buffer: " + String(energy_buffer) + " kWh

"; + content += "

packConfigMultiplexer: " + String(datalayer_extended.tesla.battery_packConfigMultiplexer) + "

"; + content += "

moduleType: " + String(datalayer_extended.tesla.battery_moduleType) + "

"; + content += "

reserveConfig: " + String(datalayer_extended.tesla.battery_reservedConfig) + "

"; + content += "

Full Charge Complete: " + String(datalayer_extended.tesla.battery_full_charge_complete) + + "

"; // no float needed + float total_discharge = static_cast(datalayer_extended.tesla.battery_total_discharge) * 0.001; + content += "

Total Discharge: " + String(total_discharge) + " kWh

"; + float total_charge = static_cast(datalayer_extended.tesla.battery_total_charge) * 0.001; + content += "

Total Charge: " + String(total_charge) + " kWh

"; + float packMass = static_cast(datalayer_extended.tesla.battery_packMass) + 300; + content += "

Battery Pack Mass: " + String(packMass) + " KG

"; + float platformMaxBusVoltage = + static_cast(datalayer_extended.tesla.battery_platformMaxBusVoltage) * 0.1 + 375; + content += "

Platform Max Bus Voltage: " + String(platformMaxBusVoltage) + " V

"; + float bms_min_voltage = static_cast(datalayer_extended.tesla.battery_bms_min_voltage) * 0.01 * 2; + content += "

BMS Min Voltage: " + String(bms_min_voltage) + " V

"; + float bms_max_voltage = static_cast(datalayer_extended.tesla.battery_bms_max_voltage) * 0.01 * 2; + content += "

BMS Max Voltage: " + String(bms_max_voltage) + " V

"; + float max_charge_current = static_cast(datalayer_extended.tesla.battery_max_charge_current) * 0.1; + content += "

Max Charge Current: " + String(max_charge_current) + " A

"; + float max_discharge_current = static_cast(datalayer_extended.tesla.battery_max_discharge_current) * 0.128; + content += "

Max Discharge Current: " + String(max_discharge_current) + " A

"; + float soc_ave = static_cast(datalayer_extended.tesla.battery_soc_ave) * 0.1; + content += "

Battery SOC Ave: " + String(soc_ave) + "

"; + float soc_max = static_cast(datalayer_extended.tesla.battery_soc_max) * 0.1; + content += "

Battery SOC Max: " + String(soc_max) + "

"; + float soc_min = static_cast(datalayer_extended.tesla.battery_soc_min) * 0.1; + content += "

Battery SOC Min: " + String(soc_min) + "

"; + float soc_ui = static_cast(datalayer_extended.tesla.battery_soc_ui) * 0.1; + content += "

Battery SOC UI: " + String(soc_ui) + "

"; static const char* contactorText[] = {"UNKNOWN(0)", "OPEN", "CLOSING", "BLOCKED", "OPENING", "CLOSED", "UNKNOWN(6)", "WELDED", "POS_CL", "NEG_CL", "UNKNOWN(10)", "UNKNOWN(11)", "UNKNOWN(12)"}; From 21b801b98376e40f5375e6ccac17add0d37f9a41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Mon, 18 Nov 2024 13:16:35 +0200 Subject: [PATCH 174/210] Fix so that contactors open on second battery incase of FAULT --- Software/Software.ino | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Software/Software.ino b/Software/Software.ino index 234fd5ab..0e8b78bf 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -748,6 +748,8 @@ void check_interconnect_available() { clear_event(EVENT_VOLTAGE_DIFFERENCE); if (datalayer.battery.status.bms_status != FAULT) { // Only proceed if we are not in faulted state datalayer.system.status.battery2_allows_contactor_closing = true; + } else { // If main battery is in fault state, disengage the second battery + datalayer.system.status.battery2_allows_contactor_closing = false; } } else { //We are over 3.0V diff set_event(EVENT_VOLTAGE_DIFFERENCE, @@ -808,7 +810,7 @@ void handle_contactors() { if (datalayer.system.status.battery2_allows_contactor_closing) { set(SECOND_NEGATIVE_CONTACTOR_PIN, ON); set(SECOND_POSITIVE_CONTACTOR_PIN, ON); - } else { + } else { // Closing contactors on secondary battery not allowed set(SECOND_NEGATIVE_CONTACTOR_PIN, OFF); set(SECOND_POSITIVE_CONTACTOR_PIN, OFF); } From d58299e936374b61ebed9e0af7f77ac45a9bcd7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Mon, 18 Nov 2024 13:40:23 +0200 Subject: [PATCH 175/210] Fix compilation error --- Software/src/inverter/SCHNEIDER-CAN.h | 1 + 1 file changed, 1 insertion(+) diff --git a/Software/src/inverter/SCHNEIDER-CAN.h b/Software/src/inverter/SCHNEIDER-CAN.h index 7178a533..83e7c87a 100644 --- a/Software/src/inverter/SCHNEIDER-CAN.h +++ b/Software/src/inverter/SCHNEIDER-CAN.h @@ -28,5 +28,6 @@ #define COMMAND_STOP 0x08 void transmit_can(CAN_frame* tx_frame, int interface); +void setup_inverter(void); #endif From 40717942fb995b1fdb9ca7b616daae319b6843db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Mon, 18 Nov 2024 22:27:24 +0200 Subject: [PATCH 176/210] Make webserver reflect actual second battery control status --- Software/Software.ino | 6 ++++-- Software/src/datalayer/datalayer.h | 4 +++- Software/src/devboard/webserver/webserver.cpp | 15 ++++++++------- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/Software/Software.ino b/Software/Software.ino index 0e8b78bf..22d2d0eb 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -785,7 +785,7 @@ void handle_contactors() { set(NEGATIVE_CONTACTOR_PIN, OFF, PWM_OFF_DUTY); set(POSITIVE_CONTACTOR_PIN, OFF, PWM_OFF_DUTY); set_event(EVENT_ERROR_OPEN_CONTACTOR, 0); - datalayer.system.status.contactor_control_closed = false; + datalayer.system.status.contactors_engaged = false; return; // A fault scenario latches the contactor control. It is not possible to recover without a powercycle (and investigation why fault occured) } @@ -810,9 +810,11 @@ void handle_contactors() { if (datalayer.system.status.battery2_allows_contactor_closing) { set(SECOND_NEGATIVE_CONTACTOR_PIN, ON); set(SECOND_POSITIVE_CONTACTOR_PIN, ON); + datalayer.system.status.contactors_battery2_engaged = true; } else { // Closing contactors on secondary battery not allowed set(SECOND_NEGATIVE_CONTACTOR_PIN, OFF); set(SECOND_POSITIVE_CONTACTOR_PIN, OFF); + datalayer.system.status.contactors_battery2_engaged = false; } #endif //CONTACTOR_CONTROL_DOUBLE_BATTERY // Skip running the state machine below if it has already completed @@ -849,7 +851,7 @@ void handle_contactors() { set(NEGATIVE_CONTACTOR_PIN, ON, PWM_HOLD_DUTY); set(POSITIVE_CONTACTOR_PIN, ON, PWM_HOLD_DUTY); contactorStatus = COMPLETED; - datalayer.system.status.contactor_control_closed = true; + datalayer.system.status.contactors_engaged = true; } break; default: diff --git a/Software/src/datalayer/datalayer.h b/Software/src/datalayer/datalayer.h index 5b198158..150a4567 100644 --- a/Software/src/datalayer/datalayer.h +++ b/Software/src/datalayer/datalayer.h @@ -189,7 +189,9 @@ typedef struct { bool inverter_allows_contactor_closing = true; #ifdef CONTACTOR_CONTROL /** True if the contactor controlled by battery-emulator is closed */ - bool contactor_control_closed = false; + bool contactors_engaged = false; + /** True if the contactor controlled by battery-emulator is closed */ + bool contactors_battery2_engaged = false; #endif } DATALAYER_SYSTEM_STATUS_TYPE; diff --git a/Software/src/devboard/webserver/webserver.cpp b/Software/src/devboard/webserver/webserver.cpp index 8eab7631..8bd4384c 100644 --- a/Software/src/devboard/webserver/webserver.cpp +++ b/Software/src/devboard/webserver/webserver.cpp @@ -733,7 +733,7 @@ String processor(const String& var) { #ifdef CONTACTOR_CONTROL content += "

Contactors controlled by Battery-Emulator: "; - if (datalayer.system.status.contactor_control_closed) { + if (datalayer.system.status.contactors_engaged) { content += "ON"; } else { content += "OFF"; @@ -853,34 +853,35 @@ String processor(const String& var) { #ifdef CONTACTOR_CONTROL content += "

Contactors controlled by Battery-Emulator: "; - if (datalayer.system.status.contactor_control_closed) { + if (datalayer.system.status.contactors_battery2_engaged) { content += "ON"; } else { content += "OFF"; } content += "

"; - +#ifdef CONTACTOR_CONTROL_DOUBLE_BATTERY content += "

Pre Charge: "; - if (digitalRead(PRECHARGE_PIN) == HIGH) { + if (digitalRead(SECOND_PRECHARGE_PIN) == HIGH) { content += ""; } else { content += ""; } content += " Cont. Neg.: "; - if (digitalRead(NEGATIVE_CONTACTOR_PIN) == HIGH) { + if (digitalRead(SECOND_NEGATIVE_CONTACTOR_PIN) == HIGH) { content += ""; } else { content += ""; } content += " Cont. Pos.: "; - if (digitalRead(POSITIVE_CONTACTOR_PIN) == HIGH) { + if (digitalRead(SECOND_POSITIVE_CONTACTOR_PIN) == HIGH) { content += ""; } else { content += ""; } content += "

"; -#endif +#endif // CONTACTOR_CONTROL_DOUBLE_BATTERY +#endif // CONTACTOR_CONTROL content += ""; content += ""; From e681ca3191e74c858bc2532e98b862c8a0d54723 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Mon, 18 Nov 2024 22:31:25 +0200 Subject: [PATCH 177/210] Avoid voltage check to pass with init value --- Software/src/battery/NISSAN-LEAF-BATTERY.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Software/src/battery/NISSAN-LEAF-BATTERY.cpp b/Software/src/battery/NISSAN-LEAF-BATTERY.cpp index f32a9ba6..f5165a72 100644 --- a/Software/src/battery/NISSAN-LEAF-BATTERY.cpp +++ b/Software/src/battery/NISSAN-LEAF-BATTERY.cpp @@ -138,7 +138,7 @@ static uint16_t battery2_GIDS = 273; //Startup in 24kWh mode static uint16_t battery2_MAX = 0; static uint16_t battery2_Max_GIDS = 273; //Startup in 24kWh mode static uint16_t battery2_StateOfHealth = 99; //State of health % -static uint16_t battery2_Total_Voltage2 = 740; //Battery voltage (0-450V) [0.5V/bit, so actual range 0-800] +static uint16_t battery2_Total_Voltage2 = 690; //Battery voltage (0-450V) [0.5V/bit, so actual range 0-800] static int16_t battery2_Current2 = 0; //Battery current (-400-200A) [0.5A/bit, so actual range -800-400] static int16_t battery2_HistData_Temperature_MAX = 6; //-40 to 86*C static int16_t battery2_HistData_Temperature_MIN = 5; //-40 to 86*C From f2cbc26f5eab222c2d35df12a47f82faca180fdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Mon, 18 Nov 2024 22:35:30 +0200 Subject: [PATCH 178/210] Add inverter check for battery2 --- Software/Software.ino | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Software/Software.ino b/Software/Software.ino index 22d2d0eb..1ca4d6d4 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -807,7 +807,8 @@ void handle_contactors() { contactorStatus = DISCONNECTED; } #ifdef CONTACTOR_CONTROL_DOUBLE_BATTERY - if (datalayer.system.status.battery2_allows_contactor_closing) { + if (datalayer.system.status.battery2_allows_contactor_closing && + datalayer.system.status.inverter_allows_contactor_closing) { set(SECOND_NEGATIVE_CONTACTOR_PIN, ON); set(SECOND_POSITIVE_CONTACTOR_PIN, ON); datalayer.system.status.contactors_battery2_engaged = true; From 2352cf035e93ad1da821d37e4115ccb5a23e7850 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Mon, 18 Nov 2024 22:48:55 +0200 Subject: [PATCH 179/210] Branch out contactor_battery2 handling --- Software/Software.ino | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/Software/Software.ino b/Software/Software.ino index 1ca4d6d4..e97a6779 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -763,6 +763,10 @@ void handle_contactors() { datalayer.system.status.inverter_allows_contactor_closing = digitalRead(INVERTER_CONTACTOR_ENABLE_PIN); #endif +#ifdef CONTACTOR_CONTROL_DOUBLE_BATTERY + handle_contactors_battery2(); +#endif + #ifdef CONTACTOR_CONTROL // First check if we have any active errors, incase we do, turn off the battery if (datalayer.battery.status.bms_status == FAULT) { @@ -803,21 +807,10 @@ void handle_contactors() { // In case the inverter requests contactors to open, set the state accordingly if (contactorStatus == COMPLETED) { + //Incase inverter requests contactors to open, make state machine jump to Disconnected state if (!datalayer.system.status.inverter_allows_contactor_closing) { contactorStatus = DISCONNECTED; } -#ifdef CONTACTOR_CONTROL_DOUBLE_BATTERY - if (datalayer.system.status.battery2_allows_contactor_closing && - datalayer.system.status.inverter_allows_contactor_closing) { - set(SECOND_NEGATIVE_CONTACTOR_PIN, ON); - set(SECOND_POSITIVE_CONTACTOR_PIN, ON); - datalayer.system.status.contactors_battery2_engaged = true; - } else { // Closing contactors on secondary battery not allowed - set(SECOND_NEGATIVE_CONTACTOR_PIN, OFF); - set(SECOND_POSITIVE_CONTACTOR_PIN, OFF); - datalayer.system.status.contactors_battery2_engaged = false; - } -#endif //CONTACTOR_CONTROL_DOUBLE_BATTERY // Skip running the state machine below if it has already completed return; } @@ -861,6 +854,21 @@ void handle_contactors() { #endif // CONTACTOR_CONTROL } +#ifdef CONTACTOR_CONTROL_DOUBLE_BATTERY +void handle_contactors_battery2() { + if ((contactorStatus == COMPLETED) && datalayer.system.status.battery2_allows_contactor_closing && + datalayer.system.status.inverter_allows_contactor_closing) { + set(SECOND_NEGATIVE_CONTACTOR_PIN, ON); + set(SECOND_POSITIVE_CONTACTOR_PIN, ON); + datalayer.system.status.contactors_battery2_engaged = true; + } else { // Closing contactors on secondary battery not allowed + set(SECOND_NEGATIVE_CONTACTOR_PIN, OFF); + set(SECOND_POSITIVE_CONTACTOR_PIN, OFF); + datalayer.system.status.contactors_battery2_engaged = false; + } +} +#endif //CONTACTOR_CONTROL_DOUBLE_BATTERY + void update_calculated_values() { /* Calculate allowed charge/discharge currents*/ if (datalayer.battery.status.voltage_dV > 10) { From 992defca33fca54253df3745f2de902ecc26e97b Mon Sep 17 00:00:00 2001 From: josiahhiggs <79869367+josiahhiggs@users.noreply.github.com> Date: Tue, 19 Nov 2024 22:40:57 +1300 Subject: [PATCH 180/210] Factor correction Change factoring back to battery .cpp Add DBC info for reference --- Software/src/battery/TESLA-BATTERY.cpp | 232 ++++++++++-------- .../webserver/advanced_battery_html.cpp | 48 ++-- 2 files changed, 158 insertions(+), 122 deletions(-) diff --git a/Software/src/battery/TESLA-BATTERY.cpp b/Software/src/battery/TESLA-BATTERY.cpp index c914fc89..16e79559 100644 --- a/Software/src/battery/TESLA-BATTERY.cpp +++ b/Software/src/battery/TESLA-BATTERY.cpp @@ -463,90 +463,107 @@ void receive_can_battery(CAN_frame rx_frame) { switch (rx_frame.ID) { case 0x352: mux = (rx_frame.data.u8[0] & 0x02); //BMS_energyStatusIndex M : 0|2@1+ (1,0) [0|0] "" X - if (mux == 3) { - battery_nominal_full_pack_energy = //BMS_nominalFullPackEnergy : 0|11@1+ (0.1,0) [0|204.6] "KWh" //((_d[1] & (0x07U)) << 8) | (_d[0] & (0xFFU)); - (((rx_frame.data.u8[1] & 0x07) << 8) | (rx_frame.data.u8[0])); //Example 752 (75.2kWh) - battery_nominal_energy_remaining = //BMS_nominalEnergyRemaining : 11|11@1+ (0.1,0) [0|204.6] "KWh" //((_d[2] & (0x3FU)) << 5) | ((_d[1] >> 3) & (0x1FU)); - (((rx_frame.data.u8[2] & 0x3F) << 5) | - ((rx_frame.data.u8[1] & 0x1F) >> 3)); //Example 1247 * 0.1 = 124.7kWh - battery_expected_energy_remaining = //BMS_expectedEnergyRemaining : 22|11@1+ (0.1,0) [0|204.6] "KWh"// ((_d[4] & (0x01U)) << 10) | ((_d[3] & (0xFFU)) << 2) | ((_d[2] >> 6) & (0x03U)); - (((rx_frame.data.u8[4] & 0x01) << 10) | (rx_frame.data.u8[3] << 2) | - ((rx_frame.data.u8[2] & 0x03) >> 6)); //Example 622 (62.2kWh) - battery_ideal_energy_remaining = //BMS_idealEnergyRemaining : 33|11@1+ (0.1,0) [0|204.6] "KWh" //((_d[5] & (0x0FU)) << 7) | ((_d[4] >> 1) & (0x7FU)); - (((rx_frame.data.u8[5] & 0x0F) << 7) | ((rx_frame.data.u8[4] & 0x7F) >> 1)); //Example 311 * 0.1 = 31.1kWh - battery_energy_to_charge_complete = // BMS_energyToChargeComplete : 44|11@1+ (0.1,0) [0|204.6] "KWh"// ((_d[6] & (0x7FU)) << 4) | ((_d[5] >> 4) & (0x0FU)); - (((rx_frame.data.u8[6] & 0x7F) << 4) | ((rx_frame.data.u8[5] & 0x0F) << 4)); //Example 147 * 0.1 = 14.7kWh - battery_energy_buffer = //BMS_energyBuffer : 55|8@1+ (0.1,0) [0|25.4] "KWh"// ((_d[7] & (0x7FU)) << 1) | ((_d[6] >> 7) & (0x01U)); - (((rx_frame.data.u8[7] & 0x7F) << 1) | ((rx_frame.data.u8[6] & 0x01) >> 7)); //Example 1 * 0.1 = 0 - battery_full_charge_complete = //BMS_fullChargeComplete : 63|1@1+ (1,0) [0|1] ""//((_d[7] >> 7) & (0x01U)); - ((rx_frame.data.u8[7] & 0x01) >> 7); - } + + battery_nominal_full_pack_energy = //BMS_nominalFullPackEnergy : 0|11@1+ (0.1,0) [0|204.6] "KWh" //((_d[1] & (0x07U)) << 8) | (_d[0] & (0xFFU)); + (((rx_frame.data.u8[1] & 0x07) << 8) | (rx_frame.data.u8[0])) * 0.1; //Example 752 (75.2kWh) + battery_nominal_energy_remaining = //BMS_nominalEnergyRemaining : 11|11@1+ (0.1,0) [0|204.6] "KWh" //((_d[2] & (0x3FU)) << 5) | ((_d[1] >> 3) & (0x1FU)); + (((rx_frame.data.u8[2] & 0x3F) << 5) | ((rx_frame.data.u8[1] & 0x1F) >> 3)) * + 0.1; //Example 1247 * 0.1 = 124.7kWh + battery_expected_energy_remaining = //BMS_expectedEnergyRemaining : 22|11@1+ (0.1,0) [0|204.6] "KWh"// ((_d[4] & (0x01U)) << 10) | ((_d[3] & (0xFFU)) << 2) | ((_d[2] >> 6) & (0x03U)); + (((rx_frame.data.u8[4] & 0x01) << 10) | (rx_frame.data.u8[3] << 2) | ((rx_frame.data.u8[2] & 0x03) >> 6)) * + 0.1; //Example 622 (62.2kWh) + battery_ideal_energy_remaining = //BMS_idealEnergyRemaining : 33|11@1+ (0.1,0) [0|204.6] "KWh" //((_d[5] & (0x0FU)) << 7) | ((_d[4] >> 1) & (0x7FU)); + (((rx_frame.data.u8[5] & 0x0F) << 7) | ((rx_frame.data.u8[4] & 0x7F) >> 1)) * + 0.1; //Example 311 * 0.1 = 31.1kWh + battery_energy_to_charge_complete = // BMS_energyToChargeComplete : 44|11@1+ (0.1,0) [0|204.6] "KWh"// ((_d[6] & (0x7FU)) << 4) | ((_d[5] >> 4) & (0x0FU)); + (((rx_frame.data.u8[6] & 0x7F) << 4) | ((rx_frame.data.u8[5] & 0x0F) << 4)) * + 0.1; //Example 147 * 0.1 = 14.7kWh + battery_energy_buffer = //BMS_energyBuffer : 55|8@1+ (0.1,0) [0|25.4] "KWh"// ((_d[7] & (0x7FU)) << 1) | ((_d[6] >> 7) & (0x01U)); + (((rx_frame.data.u8[7] & 0x7F) << 1) | ((rx_frame.data.u8[6] & 0x01) >> 7)) * 0.1; //Example 1 * 0.1 = 0 + battery_full_charge_complete = //BMS_fullChargeComplete : 63|1@1+ (1,0) [0|1] ""//((_d[7] >> 7) & (0x01U)); + ((rx_frame.data.u8[7] & 0x01) >> 7); + if (mux == 0) { - battery_nominal_full_pack_energy = - (rx_frame.data.u8[3] | - rx_frame.data.u8[2]); //SG_ BMS_nominalFullPackEnergy m0 : 16|16@1+ (0.02,0) [0|0] "kWh" X - battery_nominal_energy_remaining = - (rx_frame.data.u8[5] | - rx_frame.data.u8[4]); //SG_ BMS_nominalEnergyRemaining m0 : 32|16@1+ (0.02,0) [0|0] "kWh" X - battery_ideal_energy_remaining = - (rx_frame.data.u8[7] | - rx_frame.data.u8[6]); //SG_ BMS_idealEnergyRemaining m0 : 48|16@1+ (0.02,0) [0|0] "kWh" X + battery_nominal_full_pack_energy = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]) * + 0.02; //BMS_nominalFullPackEnergy m0 : 16|16@1+ (0.02,0) [0|0] "kWh" X + battery_nominal_energy_remaining = ((rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4]) * + 0.02; //BMS_nominalEnergyRemaining m0 : 32|16@1+ (0.02,0) [0|0] "kWh" X + battery_ideal_energy_remaining = ((rx_frame.data.u8[7] << 8) | rx_frame.data.u8[6]) * + 0.02; //BMS_idealEnergyRemaining m0 : 48|16@1+ (0.02,0) [0|0] "kWh" X } if (mux == 1) { - battery_fully_charged = (rx_frame.data.u8[1] & 0x01); //SG_ BMS_fullyCharged m1 : 15|1@1+ (1,0) [0|1] "" X - battery_energy_buffer = - (rx_frame.data.u8[3] | rx_frame.data.u8[2]); //SG_ BMS_energyBuffer m1 : 16|16@1+ (0.01,0) [0|0] "kWh" X - battery_expected_energy_remaining = - (rx_frame.data.u8[5] | - rx_frame.data.u8[4]); //SG_ BMS_expectedEnergyRemaining m1 : 32|16@1+ (0.02,0) [0|0] "kWh" X - battery_energy_to_charge_complete = - (rx_frame.data.u8[7] | - rx_frame.data.u8[6]); //SG_ BMS_energyToChargeComplete m1 : 48|16@1+ (0.02,0) [0|0] "kWh" X + battery_fully_charged = (rx_frame.data.u8[1] & 0x01); //BMS_fullyCharged m1 : 15|1@1+ (1,0) [0|1] "" X + battery_energy_buffer = (rx_frame.data.u8[3] | rx_frame.data.u8[2]) * + 0.01; //BMS_energyBuffer m1 : 16|16@1+ (0.01,0) [0|0] "kWh" X + battery_expected_energy_remaining = (rx_frame.data.u8[5] | rx_frame.data.u8[4]) * + 0.02; //BMS_expectedEnergyRemaining m1 : 32|16@1+ (0.02,0) [0|0] "kWh" X + battery_energy_to_charge_complete = (rx_frame.data.u8[7] | rx_frame.data.u8[6]) * + 0.02; //BMS_energyToChargeComplete m1 : 48|16@1+ (0.02,0) [0|0] "kWh" X } break; - case 0x20A: - //Contactor state - battery_packContNegativeState = (rx_frame.data.u8[0] & 0x07); - battery_packContPositiveState = (rx_frame.data.u8[0] & 0x38) >> 3; - battery_contactor = (rx_frame.data.u8[1] & 0x0F); - battery_packContactorSetState = (rx_frame.data.u8[1] & 0x0F); - battery_packCtrsClosingAllowed = (rx_frame.data.u8[4] & 0x08) >> 3; - battery_pyroTestInProgress = (rx_frame.data.u8[4] & 0x20) >> 5; - battery_hvil_status = (rx_frame.data.u8[5] & 0x0F); + case 0x20A: //Contactor state //HVP_contactorState: + battery_packContNegativeState = (rx_frame.data.u8[0] & 0x07); //0|3@1+ (1,0) [0|7] "" + battery_packContPositiveState = (rx_frame.data.u8[0] & 0x38) >> 3; //3|3@1+ (1,0) [0|7] "" + battery_contactor = (rx_frame.data.u8[1] & 0x0F); //HVP_packContactorSetState : 8|4@1+ (1,0) [0|9] "" + battery_packContactorSetState = (rx_frame.data.u8[1] & 0x0F); //HVP_packContactorSetState : 8|4@1+ (1,0) [0|9] "" + battery_packCtrsClosingAllowed = (rx_frame.data.u8[4] & 0x08) >> 3; //35|1@1+ (1,0) [0|1] "" + battery_pyroTestInProgress = (rx_frame.data.u8[4] & 0x20) >> 5; //37|1@1+ (1,0) [0|1] "" + battery_hvil_status = (rx_frame.data.u8[5] & 0x0F); //40|4@1+ (1,0) [0|9] "" + //HVP_packCtrsOpenNowRequested : 33|1@1+ (1,0) [0|1] "" + //HVP_packCtrsOpenRequested : 34|1@1+ (1,0) [0|1] "" + //HVP_packCtrsRequestStatus : 30|2@1+ (1,0) [0|2] "" + //HVP_packCtrsResetRequestRequired : 32|1@1+ (1,0) [0|1] "" + //HVP_dcLinkAllowedToEnergize : 36|1@1+ (1,0) [0|1] "" Receiver + //HVP_fcContNegativeAuxOpen : 7|1@1+ (1,0) [0|1] "" Receiver + //HVP_fcContNegativeState : 12|3@1+ (1,0) [0|7] "" Receiver + //HVP_fcContPositiveAuxOpen : 6|1@1+ (1,0) [0|1] "" Receiver + //HVP_fcContPositiveState : 16|3@1+ (1,0) [0|7] "" Receiver + //HVP_fcContactorSetState : 19|4@1+ (1,0) [0|9] "" Receiver + //HVP_fcCtrsClosingAllowed : 29|1@1+ (1,0) [0|1] "" Receiver + //HVP_fcCtrsOpenNowRequested : 27|1@1+ (1,0) [0|1] "" Receiver + //HVP_fcCtrsOpenRequested : 28|1@1+ (1,0) [0|1] "" Receiver + //HVP_fcCtrsRequestStatus : 24|2@1+ (1,0) [0|2] "" Receiver + //HVP_fcCtrsResetRequestRequired : 26|1@1+ (1,0) [0|1] "" Receiver + //HVP_fcLinkAllowedToEnergize : 44|2@1+ (1,0) [0|2] "" Receiver break; - case 0x252: - //Limits - battery_regenerative_limit = - ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]) * 0.01; //Example 4715 * 0.01 = 47.15kW - battery_discharge_limit = - ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]) * 0.013; //Example 2009 * 0.013 = 26.117??? - battery_max_heat_park = - (((rx_frame.data.u8[5] & 0x03) << 8) | rx_frame.data.u8[4]) * 0.01; //Example 500 * 0.01 = 5kW - battery_hvac_max_power = - (((rx_frame.data.u8[7] << 6) | ((rx_frame.data.u8[6] & 0xFC) >> 2))) * 0.02; //Example 1000 * 0.02 = 20kW? + case 0x252: //Limit //BMS_powerAvailable252: + battery_regenerative_limit = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]) * + 0.01; //0|16@1+ (0.01,0) [0|655.35] "kW" //Example 4715 * 0.01 = 47.15kW + battery_discharge_limit = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]) * + 0.013; //16|16@1+ (0.013,0) [0|655.35] "kW" //Example 2009 * 0.013 = 26.117??? + battery_max_heat_park = (((rx_frame.data.u8[5] & 0x03) << 8) | rx_frame.data.u8[4]) * + 0.01; //32|10@1+ (0.01,0) [0|10.23] "kW" //Example 500 * 0.01 = 5kW + battery_hvac_max_power = (((rx_frame.data.u8[7] << 6) | ((rx_frame.data.u8[6] & 0xFC) >> 2))) * + 0.02; //50|10@1+ (0.02,0) [0|20.46] "kW" //Example 1000 * 0.02 = 20kW? + //BMS_notEnoughPowerForHeatPump : 42|1@1+ (1,0) [0|1] "" Receiver + //BMS_powerLimitsState : 48|1@1+ (1,0) [0|1] "" Receiver break; - case 0x132: - //battery amps/volts - battery_volts = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]) * 0.01; //Example 37030mv * 0.01 = 370V - battery_amps = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]); //Example 65492 (-4.3A) OR 225 (22.5A) - battery_raw_amps = ((rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4]) * -0.05; //Example 10425 * -0.05 = ? + case 0x132: //battery amps/volts //HVBattAmpVolt + battery_volts = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]) * + 0.01; //0|16@1+ (0.01,0) [0|655.35] "V" //Example 37030mv * 0.01 = 370V + battery_amps = + ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]) * + -0.1; //SmoothBattCurrent : 16|16@1- (-0.1,0) [-3276.7|3276.7] "A" //Example 65492 (-4.3A) OR 225 (22.5A) + battery_raw_amps = + ((rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4]) * -0.05 + + 822; //RawBattCurrent : 32|16@1- (-0.05,822) [-1138.35|2138.4] "A" //Example 10425 * -0.05 = ? battery_charge_time_remaining = - (((rx_frame.data.u8[7] & 0x0F) << 8) | rx_frame.data.u8[6]) * 0.1; //Example 228 * 0.1 = 22.8min + (((rx_frame.data.u8[7] & 0x0F) << 8) | + rx_frame.data.u8[6]); //ChargeHoursRemaining : 48|12@1+ (1,0) [0|4095] "Min" //Example 228 * 0.1 = 22.8min if (battery_charge_time_remaining == 4095) { battery_charge_time_remaining = 0; } - break; - case 0x3D2: - // total charge/discharge kwh + case 0x3D2: //TotalChargeDischarge: battery_total_discharge = ((rx_frame.data.u8[3] << 24) | (rx_frame.data.u8[2] << 16) | - (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]); + (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]) * + 0.001; //0|32@1+ (0.001,0) [0|4294970] "kWh" battery_total_charge = ((rx_frame.data.u8[7] << 24) | (rx_frame.data.u8[6] << 16) | (rx_frame.data.u8[5] << 8) | - rx_frame.data.u8[4]); + rx_frame.data.u8[4]) * + 0.001; //32|32@1+ (0.001,0) [0|4294970] "kWh" break; - case 0x332: - //min/max hist values - mux = (rx_frame.data.u8[0] & 0x03); + case 0x332: //min/max hist values //BattBrickMinMax: + mux = (rx_frame.data.u8[0] & 0x03); //BattBrickMultiplexer M : 0|2@1+ (1,0) [0|0] "" if (mux == 1) //Cell voltages { @@ -565,10 +582,24 @@ void receive_can_battery(CAN_frame rx_frame) { battery_min_temp = (rx_frame.data.u8[3] * 5) - 400; //Multiply by 5 and remove offset to get C+1 (0x61*5=485-400=8.5*C) } + //BattBrickMultiplexer M : 0|2@1+ (1,0) [0|0] "" Receiver + //BattBrickTempMaxNum m0 : 2|4@1+ (1,0) [0|0] "" Receiver + //BattBrickTempMinNum m0 : 8|4@1+ (1,0) [0|0] "" Receiver + //BattBrickTempMax m0 : 16|8@1+ (0.5,-40) [0|0] "C" Receiver + //BattBrickTempMin m0 : 24|8@1+ (0.5,-40) [0|0] "C" Receiver + //BattBrickModelTMax m0 : 32|8@1+ (0.5,-40) [0|0] "C" Receiver + //BattBrickModelTMin m0 : 40|8@1+ (0.5,-40) [0|0] "C" Receiver + //BattBrickVoltageMax m1 : 2|12@1+ (0.002,0) [0|0] "V" Receiver + //BattBrickVoltageMin m1 : 16|12@1+ (0.002,0) [0|0] "V" Receiver + //BattBrickVoltageMaxNum m1 : 32|7@1+ (1,1) [0|0] "" Receiver + //BattBrickVoltageMinNum m1 : 40|7@1+ (1,1) [0|0] "" Receiver break; - case 0x401: // Cell stats - mux = (rx_frame.data.u8[0]); - + case 0x401: // Cell stats //BrickVoltages + mux = (rx_frame.data.u8[0]); //MultiplexSelector M : 0|8@1+ (1,0) [0|0] "" + //StatusFlags : 8|8@1+ (1,0) [0|0] "" + //Brick0 m0 : 16|16@1+ (0.0001,0) [0|0] "V" + //Brick1 m0 : 32|16@1+ (0.0001,0) [0|0] "V" + //Brick2 m0 : 48|16@1+ (0.0001,0) [0|0] "V" static uint16_t volts; static uint8_t mux_zero_counter = 0u; static uint8_t mux_max = 0u; @@ -597,41 +628,50 @@ void receive_can_battery(CAN_frame rx_frame) { } } break; - case 0x2d2: - //Min / max limits //BMSVAlimits: //move factoring to datalayer? - battery_bms_min_voltage = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]); //Example 24148mv * 0.01 = 241.48 V - battery_bms_max_voltage = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]); //Example 40282mv * 0.01 = 402.82 V - battery_max_charge_current = - (((rx_frame.data.u8[5] & 0x3F) << 8) | rx_frame.data.u8[4]); //Example 1301? * 0.1 = 130.1? - battery_max_discharge_current = - (((rx_frame.data.u8[7] & 0x3F) << 8) | rx_frame.data.u8[6]); //Example 430? * 0.128 = 55.4? + case 0x2d2: //BMSVAlimits: + battery_bms_min_voltage = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]) * + 0.1; //0|16@1+ (0.01,0) [0|430] "V" //Example 24148mv * 0.01 = 241.48 V + battery_bms_max_voltage = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]) * + 0.1; //16|16@1+ (0.01,0) [0|430] "V" //Example 40282mv * 0.01 = 402.82 V + battery_max_charge_current = (((rx_frame.data.u8[5] & 0x3F) << 8) | rx_frame.data.u8[4]) * + 0.1; //32|14@1+ (0.1,0) [0|1638.2] "A" //Example 1301? * 0.1 = 130.1? + battery_max_discharge_current = (((rx_frame.data.u8[7] & 0x3F) << 8) | rx_frame.data.u8[6]) * + 0.128; //48|14@1+ (0.128,0) [0|2096.9] "A" //Example 430? * 0.128 = 55.4? break; - case 0x2b4: //PCS_dcdcRailStatus: - battery_dcdcLvBusVolt = (((rx_frame.data.u8[1] & 0x03) << 8) | rx_frame.data.u8[0]); //update name move factoring - battery_dcdcHvBusVolt = - ((((rx_frame.data.u8[2] & 0x3F) << 6) | ((rx_frame.data.u8[1] & 0xFC) >> 2))); //update name move factoring + case 0x2b4: //PCS_dcdcRailStatus: + battery_dcdcLvBusVolt = (((rx_frame.data.u8[1] & 0x03) << 8) | rx_frame.data.u8[0]) * + 0.0390625; //0|10@1+ (0.0390625,0) [0|39.9609] "V" + battery_dcdcHvBusVolt = ((((rx_frame.data.u8[2] & 0x3F) << 6) | ((rx_frame.data.u8[1] & 0xFC) >> 2))) * + 0.146484; //10|12@1+ (0.146484,0) [0|599.854] "V" battery_dcdcLvOutputCurrent = - (((rx_frame.data.u8[4] & 0x0F) << 8) | rx_frame.data.u8[3]); //update name move factoring + (((rx_frame.data.u8[4] & 0x0F) << 8) | rx_frame.data.u8[3]) * 0.1; //24|12@1+ (0.1,0) [0|400] "A" break; case 0x292: //BMS_socStatus datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; //We are getting CAN messages from the BMS - battery_beginning_of_life = (((rx_frame.data.u8[6] & 0x03) << 8) | rx_frame.data.u8[5]); - battery_soc_min = (((rx_frame.data.u8[1] & 0x03) << 8) | rx_frame.data.u8[0]); - battery_soc_ui = (((rx_frame.data.u8[2] & 0x0F) << 6) | ((rx_frame.data.u8[1] & 0xFC) >> 2)); - battery_soc_max = (((rx_frame.data.u8[3] & 0x3F) << 4) | ((rx_frame.data.u8[2] & 0xF0) >> 4)); - battery_soc_ave = ((rx_frame.data.u8[4] << 2) | ((rx_frame.data.u8[3] & 0xC0) >> 6)); - battery_battTempPct = ((rx_frame.data.u8[7] & 0x03) << 6) | (rx_frame.data.u8[6] & (0x3F) >> 2); + battery_beginning_of_life = + (((rx_frame.data.u8[6] & 0x03) << 8) | rx_frame.data.u8[5]) * 0.1; //40|10@1+ (0.1,0) [0|102.3] "kWh" + battery_soc_min = + (((rx_frame.data.u8[1] & 0x03) << 8) | rx_frame.data.u8[0]) * 0.1; //0|10@1+ (0.1,0) [0|102.3] "%" + battery_soc_ui = (((rx_frame.data.u8[2] & 0x0F) << 6) | ((rx_frame.data.u8[1] & 0xFC) >> 2)) * + 0.1; //10|10@1+ (0.1,0) [0|102.3] "%" + battery_soc_max = (((rx_frame.data.u8[3] & 0x3F) << 4) | ((rx_frame.data.u8[2] & 0xF0) >> 4)) * + 0.1; //20|10@1+ (0.1,0) [0|102.3] "%" + battery_soc_ave = + ((rx_frame.data.u8[4] << 2) | ((rx_frame.data.u8[3] & 0xC0) >> 6)) * 0.1; //30|10@1+ (0.1,0) [0|102.3] "%" + battery_battTempPct = ((rx_frame.data.u8[7] & 0x03) << 6) | + (rx_frame.data.u8[6] & (0x3F) >> 2) * 0.4; //50|8@1+ (0.4,0) [0|100] "%" break; case 0x392: //BMS_packConfig mux = (rx_frame.data.u8[0] & (0xFF)); if (mux == 1) { - battery_packConfigMultiplexer = (rx_frame.data.u8[0] & (0xff)); - battery_moduleType = (rx_frame.data.u8[1] & (0x07)); - battery_packMass = (rx_frame.data.u8[2]); - battery_platformMaxBusVoltage = ((rx_frame.data.u8[4] & 0x03) << 8) | (rx_frame.data.u8[3]); + battery_packConfigMultiplexer = (rx_frame.data.u8[0] & (0xff)); //0|8@1+ (1,0) [0|1] "" + battery_moduleType = (rx_frame.data.u8[1] & (0x07)); //8|3@1+ (1,0) [0|4] "" + battery_packMass = (rx_frame.data.u8[2]) + 300; //16|8@1+ (1,300) [342|469] "kg" + battery_platformMaxBusVoltage = + ((rx_frame.data.u8[4] & 0x03) << 8) | (rx_frame.data.u8[3]) * 0.1 + 375; //24|10@1+ (0.1,375) [0|0] "V" } if (mux == 0) { - battery_reservedConfig = (rx_frame.data.u8[1] & (0x1F)); + battery_reservedConfig = (rx_frame.data.u8[1] & (0x1F)); //8|5@1+ (1,0) [0|31] "" } break; case 0x3aa: //HVP_alertMatrix1 diff --git a/Software/src/devboard/webserver/advanced_battery_html.cpp b/Software/src/devboard/webserver/advanced_battery_html.cpp index 2cd7917c..1efa3530 100644 --- a/Software/src/devboard/webserver/advanced_battery_html.cpp +++ b/Software/src/devboard/webserver/advanced_battery_html.cpp @@ -285,58 +285,54 @@ String advanced_battery_processor(const String& var) { #endif //BYD_ATTO_3_BATTERY #ifdef TESLA_BATTERY - float beginning_of_life = static_cast(datalayer_extended.tesla.battery_beginning_of_life) * 0.1; + float beginning_of_life = static_cast(datalayer_extended.tesla.battery_beginning_of_life); content += "

Battery Beginning of Life: " + String(beginning_of_life) + " kWh

"; - float battTempPct = static_cast(datalayer_extended.tesla.battery_battTempPct) * 0.4; + float battTempPct = static_cast(datalayer_extended.tesla.battery_battTempPct); content += "

BattTempPct: " + String(battTempPct) + "

"; - float dcdcLvBusVolt = static_cast(datalayer_extended.tesla.battery_dcdcLvBusVolt) * 0.0390625; + float dcdcLvBusVolt = static_cast(datalayer_extended.tesla.battery_dcdcLvBusVolt); content += "

PCS Lv Bus: " + String(dcdcLvBusVolt) + " V

"; - float dcdcHvBusVolt = static_cast(datalayer_extended.tesla.battery_dcdcHvBusVolt) * 0.146484; + float dcdcHvBusVolt = static_cast(datalayer_extended.tesla.battery_dcdcHvBusVolt); content += "

PCS Hv Bus: " + String(dcdcHvBusVolt) + " V

"; - float dcdcLvOutputCurrent = static_cast(datalayer_extended.tesla.battery_dcdcLvOutputCurrent) * 0.1; + float dcdcLvOutputCurrent = static_cast(datalayer_extended.tesla.battery_dcdcLvOutputCurrent); content += "

PCS Lv Output: " + String(dcdcLvOutputCurrent) + " A

"; - float nominal_full_pack_energy = - static_cast(datalayer_extended.tesla.battery_nominal_full_pack_energy) * 0.1; + float nominal_full_pack_energy = static_cast(datalayer_extended.tesla.battery_nominal_full_pack_energy); content += "

Nominal Full Pack Energy: " + String(nominal_full_pack_energy) + " kWh

"; - float nominal_energy_remaining = - static_cast(datalayer_extended.tesla.battery_nominal_energy_remaining) * 0.1; + float nominal_energy_remaining = static_cast(datalayer_extended.tesla.battery_nominal_energy_remaining); content += "

Nominal Energy Remaining: " + String(nominal_energy_remaining) + " kWh

"; - float ideal_energy_remaining = static_cast(datalayer_extended.tesla.battery_ideal_energy_remaining) * 0.1; + float ideal_energy_remaining = static_cast(datalayer_extended.tesla.battery_ideal_energy_remaining); content += "

Ideal Energy Remaining: " + String(ideal_energy_remaining) + " kWh

"; - float energy_to_charge_complete = - static_cast(datalayer_extended.tesla.battery_energy_to_charge_complete) * 0.1; + float energy_to_charge_complete = static_cast(datalayer_extended.tesla.battery_energy_to_charge_complete); content += "

Energy to Charge Complete: " + String(energy_to_charge_complete) + " kWh

"; - float energy_buffer = static_cast(datalayer_extended.tesla.battery_energy_buffer) * 0.1; + float energy_buffer = static_cast(datalayer_extended.tesla.battery_energy_buffer); content += "

Energy Buffer: " + String(energy_buffer) + " kWh

"; content += "

packConfigMultiplexer: " + String(datalayer_extended.tesla.battery_packConfigMultiplexer) + "

"; content += "

moduleType: " + String(datalayer_extended.tesla.battery_moduleType) + "

"; content += "

reserveConfig: " + String(datalayer_extended.tesla.battery_reservedConfig) + "

"; content += "

Full Charge Complete: " + String(datalayer_extended.tesla.battery_full_charge_complete) + "

"; // no float needed - float total_discharge = static_cast(datalayer_extended.tesla.battery_total_discharge) * 0.001; + float total_discharge = static_cast(datalayer_extended.tesla.battery_total_discharge); content += "

Total Discharge: " + String(total_discharge) + " kWh

"; - float total_charge = static_cast(datalayer_extended.tesla.battery_total_charge) * 0.001; + float total_charge = static_cast(datalayer_extended.tesla.battery_total_charge); content += "

Total Charge: " + String(total_charge) + " kWh

"; - float packMass = static_cast(datalayer_extended.tesla.battery_packMass) + 300; + float packMass = static_cast(datalayer_extended.tesla.battery_packMass); content += "

Battery Pack Mass: " + String(packMass) + " KG

"; - float platformMaxBusVoltage = - static_cast(datalayer_extended.tesla.battery_platformMaxBusVoltage) * 0.1 + 375; + float platformMaxBusVoltage = static_cast(datalayer_extended.tesla.battery_platformMaxBusVoltage); content += "

Platform Max Bus Voltage: " + String(platformMaxBusVoltage) + " V

"; - float bms_min_voltage = static_cast(datalayer_extended.tesla.battery_bms_min_voltage) * 0.01 * 2; + float bms_min_voltage = static_cast(datalayer_extended.tesla.battery_bms_min_voltage); content += "

BMS Min Voltage: " + String(bms_min_voltage) + " V

"; - float bms_max_voltage = static_cast(datalayer_extended.tesla.battery_bms_max_voltage) * 0.01 * 2; + float bms_max_voltage = static_cast(datalayer_extended.tesla.battery_bms_max_voltage); content += "

BMS Max Voltage: " + String(bms_max_voltage) + " V

"; - float max_charge_current = static_cast(datalayer_extended.tesla.battery_max_charge_current) * 0.1; + float max_charge_current = static_cast(datalayer_extended.tesla.battery_max_charge_current); content += "

Max Charge Current: " + String(max_charge_current) + " A

"; - float max_discharge_current = static_cast(datalayer_extended.tesla.battery_max_discharge_current) * 0.128; + float max_discharge_current = static_cast(datalayer_extended.tesla.battery_max_discharge_current); content += "

Max Discharge Current: " + String(max_discharge_current) + " A

"; - float soc_ave = static_cast(datalayer_extended.tesla.battery_soc_ave) * 0.1; + float soc_ave = static_cast(datalayer_extended.tesla.battery_soc_ave); content += "

Battery SOC Ave: " + String(soc_ave) + "

"; - float soc_max = static_cast(datalayer_extended.tesla.battery_soc_max) * 0.1; + float soc_max = static_cast(datalayer_extended.tesla.battery_soc_max); content += "

Battery SOC Max: " + String(soc_max) + "

"; - float soc_min = static_cast(datalayer_extended.tesla.battery_soc_min) * 0.1; + float soc_min = static_cast(datalayer_extended.tesla.battery_soc_min); content += "

Battery SOC Min: " + String(soc_min) + "

"; - float soc_ui = static_cast(datalayer_extended.tesla.battery_soc_ui) * 0.1; + float soc_ui = static_cast(datalayer_extended.tesla.battery_soc_ui); content += "

Battery SOC UI: " + String(soc_ui) + "

"; static const char* contactorText[] = {"UNKNOWN(0)", "OPEN", "CLOSING", "BLOCKED", "OPENING", "CLOSED", "UNKNOWN(6)", "WELDED", "POS_CL", "NEG_CL", From 6412a9e64dcbba6e38c47163cdd509e8420ab72e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Tue, 19 Nov 2024 17:50:43 +0200 Subject: [PATCH 181/210] Fix compilation error Fixed compilation error when using SOC sent from battery --- Software/src/battery/BYD-ATTO-3-BATTERY.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Software/src/battery/BYD-ATTO-3-BATTERY.cpp b/Software/src/battery/BYD-ATTO-3-BATTERY.cpp index 9b63cdd7..5ad4220f 100644 --- a/Software/src/battery/BYD-ATTO-3-BATTERY.cpp +++ b/Software/src/battery/BYD-ATTO-3-BATTERY.cpp @@ -118,7 +118,7 @@ void update_values_battery() { //This function maps all the values fetched via datalayer.battery.status.real_soc = estimateSOC(datalayer.battery.status.voltage_dV); SOC_method = ESTIMATED; #else // Pack is not crashed, we can use periodically transmitted SOC - datalayer.battery.status.real_soc = highprecision_SOC * 100; + datalayer.battery.status.real_soc = battery_highprecision_SOC * 100; SOC_method = MEASURED; #endif From 7249d6d646eeca97058d282b1f3ed1f6a1088998 Mon Sep 17 00:00:00 2001 From: josiahhiggs <79869367+josiahhiggs@users.noreply.github.com> Date: Wed, 20 Nov 2024 08:35:24 +1300 Subject: [PATCH 182/210] Update TESLA-BATTERY.cpp Fix compile issues, missed a few () --- Software/src/battery/TESLA-BATTERY.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Software/src/battery/TESLA-BATTERY.cpp b/Software/src/battery/TESLA-BATTERY.cpp index 16e79559..7b1a269f 100644 --- a/Software/src/battery/TESLA-BATTERY.cpp +++ b/Software/src/battery/TESLA-BATTERY.cpp @@ -641,7 +641,7 @@ void receive_can_battery(CAN_frame rx_frame) { case 0x2b4: //PCS_dcdcRailStatus: battery_dcdcLvBusVolt = (((rx_frame.data.u8[1] & 0x03) << 8) | rx_frame.data.u8[0]) * 0.0390625; //0|10@1+ (0.0390625,0) [0|39.9609] "V" - battery_dcdcHvBusVolt = ((((rx_frame.data.u8[2] & 0x3F) << 6) | ((rx_frame.data.u8[1] & 0xFC) >> 2))) * + battery_dcdcHvBusVolt = (((rx_frame.data.u8[2] & 0x3F) << 6) | ((rx_frame.data.u8[1] & 0xFC) >> 2)) * 0.146484; //10|12@1+ (0.146484,0) [0|599.854] "V" battery_dcdcLvOutputCurrent = (((rx_frame.data.u8[4] & 0x0F) << 8) | rx_frame.data.u8[3]) * 0.1; //24|12@1+ (0.1,0) [0|400] "A" @@ -658,8 +658,8 @@ void receive_can_battery(CAN_frame rx_frame) { 0.1; //20|10@1+ (0.1,0) [0|102.3] "%" battery_soc_ave = ((rx_frame.data.u8[4] << 2) | ((rx_frame.data.u8[3] & 0xC0) >> 6)) * 0.1; //30|10@1+ (0.1,0) [0|102.3] "%" - battery_battTempPct = ((rx_frame.data.u8[7] & 0x03) << 6) | - (rx_frame.data.u8[6] & (0x3F) >> 2) * 0.4; //50|8@1+ (0.4,0) [0|100] "%" + battery_battTempPct = (((rx_frame.data.u8[7] & 0x03) << 6) | (rx_frame.data.u8[6] & 0x3F) >> 2) * + 0.4; //50|8@1+ (0.4,0) [0|100] "%" break; case 0x392: //BMS_packConfig mux = (rx_frame.data.u8[0] & (0xFF)); @@ -668,7 +668,7 @@ void receive_can_battery(CAN_frame rx_frame) { battery_moduleType = (rx_frame.data.u8[1] & (0x07)); //8|3@1+ (1,0) [0|4] "" battery_packMass = (rx_frame.data.u8[2]) + 300; //16|8@1+ (1,300) [342|469] "kg" battery_platformMaxBusVoltage = - ((rx_frame.data.u8[4] & 0x03) << 8) | (rx_frame.data.u8[3]) * 0.1 + 375; //24|10@1+ (0.1,375) [0|0] "V" + (((rx_frame.data.u8[4] & 0x03) << 8) | (rx_frame.data.u8[3])) * 0.1 + 375; //24|10@1+ (0.1,375) [0|0] "V" } if (mux == 0) { battery_reservedConfig = (rx_frame.data.u8[1] & (0x1F)); //8|5@1+ (1,0) [0|31] "" From 7fa3a160e118b90e28a14aac8739560848765bd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Wed, 20 Nov 2024 21:49:18 +0200 Subject: [PATCH 183/210] Squash bugs related to estop and datalayer --- Software/Software.ino | 26 ++++++++------------ Software/src/battery/NISSAN-LEAF-BATTERY.cpp | 14 ++++------- Software/src/datalayer/datalayer.h | 2 +- 3 files changed, 16 insertions(+), 26 deletions(-) diff --git a/Software/Software.ino b/Software/Software.ino index e97a6779..3d800713 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -57,7 +57,7 @@ const char* version_number = "7.7.dev"; // Interval settings uint16_t intervalUpdateValues = INTERVAL_1_S; // Interval at which to update inverter values / Modbus registers -unsigned long previousMillis10ms = 50; +unsigned long previousMillis10ms = 0; unsigned long previousMillisUpdateVal = 0; // CAN parameters @@ -302,9 +302,6 @@ void core_loop(void* task_time_us) { previousMillis10ms = millis(); led_exe(); handle_contactors(); // Take care of startup precharge/contactor closing -#ifdef DOUBLE_BATTERY - check_interconnect_available(); -#endif } END_TIME_MEASUREMENT_MAX(time_10ms, datalayer.system.status.time_10ms_us); @@ -314,6 +311,7 @@ void core_loop(void* task_time_us) { update_values_battery(); // Fetch battery values #ifdef DOUBLE_BATTERY update_values_battery2(); + check_interconnect_available(); #endif update_calculated_values(); #ifndef SERIAL_LINK_RECEIVER @@ -536,6 +534,7 @@ void init_contactors() { set(PRECHARGE_PIN, OFF); #endif //CONTACTOR_CONTROL #ifdef CONTACTOR_CONTROL_DOUBLE_BATTERY + pinMode(SECOND_POSITIVE_CONTACTOR_PIN, OUTPUT); set(SECOND_POSITIVE_CONTACTOR_PIN, OFF); pinMode(SECOND_NEGATIVE_CONTACTOR_PIN, OUTPUT); @@ -775,15 +774,11 @@ void handle_contactors() { timeSpentInFaultedMode = 0; } - //handle contactor control SHUTDOWN_REQUESTED vs DISCONNECTED - if (timeSpentInFaultedMode > MAX_ALLOWED_FAULT_TICKS || - (datalayer.system.settings.equipment_stop_active && contactorStatus != SHUTDOWN_REQUESTED)) { + //handle contactor control SHUTDOWN_REQUESTED + if (timeSpentInFaultedMode > MAX_ALLOWED_FAULT_TICKS) { contactorStatus = SHUTDOWN_REQUESTED; - datalayer.system.settings.equipment_stop_active = true; - } - if (contactorStatus == SHUTDOWN_REQUESTED && !datalayer.system.settings.equipment_stop_active) { - contactorStatus = DISCONNECTED; } + if (contactorStatus == SHUTDOWN_REQUESTED) { set(PRECHARGE_PIN, OFF); set(NEGATIVE_CONTACTOR_PIN, OFF, PWM_OFF_DUTY); @@ -800,15 +795,15 @@ void handle_contactors() { set(POSITIVE_CONTACTOR_PIN, OFF, PWM_OFF_DUTY); if (datalayer.system.status.battery_allows_contactor_closing && - datalayer.system.status.inverter_allows_contactor_closing) { + datalayer.system.status.inverter_allows_contactor_closing && !datalayer.system.settings.equipment_stop_active) { contactorStatus = PRECHARGE; } } // In case the inverter requests contactors to open, set the state accordingly if (contactorStatus == COMPLETED) { - //Incase inverter requests contactors to open, make state machine jump to Disconnected state - if (!datalayer.system.status.inverter_allows_contactor_closing) { + //Incase inverter (or estop) requests contactors to open, make state machine jump to Disconnected state (recoverable) + if (!datalayer.system.status.inverter_allows_contactor_closing || datalayer.system.settings.equipment_stop_active) { contactorStatus = DISCONNECTED; } // Skip running the state machine below if it has already completed @@ -856,8 +851,7 @@ void handle_contactors() { #ifdef CONTACTOR_CONTROL_DOUBLE_BATTERY void handle_contactors_battery2() { - if ((contactorStatus == COMPLETED) && datalayer.system.status.battery2_allows_contactor_closing && - datalayer.system.status.inverter_allows_contactor_closing) { + if ((contactorStatus == COMPLETED) && datalayer.system.status.battery2_allows_contactor_closing) { set(SECOND_NEGATIVE_CONTACTOR_PIN, ON); set(SECOND_POSITIVE_CONTACTOR_PIN, ON); datalayer.system.status.contactors_battery2_engaged = true; diff --git a/Software/src/battery/NISSAN-LEAF-BATTERY.cpp b/Software/src/battery/NISSAN-LEAF-BATTERY.cpp index f5165a72..90fb6365 100644 --- a/Software/src/battery/NISSAN-LEAF-BATTERY.cpp +++ b/Software/src/battery/NISSAN-LEAF-BATTERY.cpp @@ -136,10 +136,10 @@ static uint16_t battery2_TEMP = 0; //Temporary value used in s static uint16_t battery2_Wh_Remaining = 0; //Amount of energy in battery, in Wh static uint16_t battery2_GIDS = 273; //Startup in 24kWh mode static uint16_t battery2_MAX = 0; -static uint16_t battery2_Max_GIDS = 273; //Startup in 24kWh mode -static uint16_t battery2_StateOfHealth = 99; //State of health % -static uint16_t battery2_Total_Voltage2 = 690; //Battery voltage (0-450V) [0.5V/bit, so actual range 0-800] -static int16_t battery2_Current2 = 0; //Battery current (-400-200A) [0.5A/bit, so actual range -800-400] +static uint16_t battery2_Max_GIDS = 273; //Startup in 24kWh mode +static uint16_t battery2_StateOfHealth = 99; //State of health % +static uint16_t battery2_Total_Voltage2 = 0; //Battery voltage (0-450V) [0.5V/bit, so actual range 0-800] +static int16_t battery2_Current2 = 0; //Battery current (-400-200A) [0.5A/bit, so actual range -800-400] static int16_t battery2_HistData_Temperature_MAX = 6; //-40 to 86*C static int16_t battery2_HistData_Temperature_MIN = 5; //-40 to 86*C static int16_t battery2_AverageTemperature = 6; //Only available on ZE0, in celcius, -40 to +55 @@ -507,11 +507,7 @@ void receive_can_battery2(CAN_frame rx_frame) { battery2_Relay_Cut_Request = ((rx_frame.data.u8[1] & 0x18) >> 3); battery2_Failsafe_Status = (rx_frame.data.u8[1] & 0x07); battery2_MainRelayOn_flag = (bool)((rx_frame.data.u8[3] & 0x20) >> 5); - if (battery2_MainRelayOn_flag) { - datalayer.system.status.battery2_allows_contactor_closing = true; - } else { - datalayer.system.status.battery2_allows_contactor_closing = false; - } + //battery2_allows_contactor_closing written by check_interconnect_available(); battery2_Full_CHARGE_flag = (bool)((rx_frame.data.u8[3] & 0x10) >> 4); battery2_Interlock = (bool)((rx_frame.data.u8[3] & 0x08) >> 3); break; diff --git a/Software/src/datalayer/datalayer.h b/Software/src/datalayer/datalayer.h index 150a4567..528179a6 100644 --- a/Software/src/datalayer/datalayer.h +++ b/Software/src/datalayer/datalayer.h @@ -190,7 +190,7 @@ typedef struct { #ifdef CONTACTOR_CONTROL /** True if the contactor controlled by battery-emulator is closed */ bool contactors_engaged = false; - /** True if the contactor controlled by battery-emulator is closed */ + /** True if the contactor controlled by battery-emulator is closed. Determined by check_interconnect_available(); if voltage is OK */ bool contactors_battery2_engaged = false; #endif } DATALAYER_SYSTEM_STATUS_TYPE; From 7e15571a4108dcc3da5594e774039d093ea71041 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Wed, 20 Nov 2024 22:20:35 +0200 Subject: [PATCH 184/210] Add currents to battery 2 in webserver --- Software/src/devboard/webserver/webserver.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/Software/src/devboard/webserver/webserver.cpp b/Software/src/devboard/webserver/webserver.cpp index ce4ec7d4..2792a045 100644 --- a/Software/src/devboard/webserver/webserver.cpp +++ b/Software/src/devboard/webserver/webserver.cpp @@ -714,8 +714,19 @@ String processor(const String& var) { content += formatPowerValue("Real Remaining capacity", datalayer.battery2.status.remaining_capacity_Wh, "h", 1); content += formatPowerValue("Scaled Remaining capacity", datalayer.battery2.status.reported_remaining_capacity_Wh, "h", 1); - content += formatPowerValue("Max discharge power", datalayer.battery2.status.max_discharge_power_W, "", 1); - content += formatPowerValue("Max charge power", datalayer.battery2.status.max_charge_power_W, "", 1); + + if (emulator_pause_status == NORMAL) { + content += formatPowerValue("Max discharge power", datalayer.battery2.status.max_discharge_power_W, "", 1); + content += formatPowerValue("Max charge power", datalayer.battery2.status.max_charge_power_W, "", 1); + content += "

Max discharge current: " + String(maxCurrentDischargeFloat, 1) + " A

"; + content += "

Max charge current: " + String(maxCurrentChargeFloat, 1) + " A

"; + } else { + content += formatPowerValue("Max discharge power", datalayer.battery2.status.max_discharge_power_W, "", 1, "red"); + content += formatPowerValue("Max charge power", datalayer.battery2.status.max_charge_power_W, "", 1, "red"); + content += "

Max discharge current: " + String(maxCurrentDischargeFloat, 1) + " A

"; + content += "

Max charge current: " + String(maxCurrentChargeFloat, 1) + " A

"; + } + content += "

Cell max: " + String(datalayer.battery2.status.cell_max_voltage_mV) + " mV

"; content += "

Cell min: " + String(datalayer.battery2.status.cell_min_voltage_mV) + " mV

"; if (cell_delta_mv > datalayer.battery2.info.max_cell_voltage_deviation_mV) { From f915184edd1568d37079777b69ede0a2a459c5ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Wed, 20 Nov 2024 22:24:38 +0200 Subject: [PATCH 185/210] Make paused text stay red incase user reboots --- Software/src/devboard/webserver/webserver.cpp | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Software/src/devboard/webserver/webserver.cpp b/Software/src/devboard/webserver/webserver.cpp index 2792a045..640429dc 100644 --- a/Software/src/devboard/webserver/webserver.cpp +++ b/Software/src/devboard/webserver/webserver.cpp @@ -585,16 +585,16 @@ String processor(const String& var) { content += formatPowerValue("Scaled Remaining capacity", datalayer.battery.status.reported_remaining_capacity_Wh, "h", 1); - if (emulator_pause_status == NORMAL) { - 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 += "

Max discharge current: " + String(maxCurrentDischargeFloat, 1) + " A

"; - content += "

Max charge current: " + String(maxCurrentChargeFloat, 1) + " A

"; - } else { + if (datalayer.system.settings.equipment_stop_active) { 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 += "

Max discharge current: " + String(maxCurrentDischargeFloat, 1) + " A

"; content += "

Max charge current: " + String(maxCurrentChargeFloat, 1) + " A

"; + } else { + 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 += "

Max discharge current: " + String(maxCurrentDischargeFloat, 1) + " A

"; + content += "

Max charge current: " + String(maxCurrentChargeFloat, 1) + " A

"; } content += "

Cell max: " + String(datalayer.battery.status.cell_max_voltage_mV) + " mV

"; @@ -715,16 +715,16 @@ String processor(const String& var) { content += formatPowerValue("Scaled Remaining capacity", datalayer.battery2.status.reported_remaining_capacity_Wh, "h", 1); - if (emulator_pause_status == NORMAL) { - content += formatPowerValue("Max discharge power", datalayer.battery2.status.max_discharge_power_W, "", 1); - content += formatPowerValue("Max charge power", datalayer.battery2.status.max_charge_power_W, "", 1); - content += "

Max discharge current: " + String(maxCurrentDischargeFloat, 1) + " A

"; - content += "

Max charge current: " + String(maxCurrentChargeFloat, 1) + " A

"; - } else { + if (datalayer.system.settings.equipment_stop_active) { content += formatPowerValue("Max discharge power", datalayer.battery2.status.max_discharge_power_W, "", 1, "red"); content += formatPowerValue("Max charge power", datalayer.battery2.status.max_charge_power_W, "", 1, "red"); content += "

Max discharge current: " + String(maxCurrentDischargeFloat, 1) + " A

"; content += "

Max charge current: " + String(maxCurrentChargeFloat, 1) + " A

"; + } else { + content += formatPowerValue("Max discharge power", datalayer.battery2.status.max_discharge_power_W, "", 1); + content += formatPowerValue("Max charge power", datalayer.battery2.status.max_charge_power_W, "", 1); + content += "

Max discharge current: " + String(maxCurrentDischargeFloat, 1) + " A

"; + content += "

Max charge current: " + String(maxCurrentChargeFloat, 1) + " A

"; } content += "

Cell max: " + String(datalayer.battery2.status.cell_max_voltage_mV) + " mV

"; From eb850e616c3e4715ad6f3a97ce2d73432954caa1 Mon Sep 17 00:00:00 2001 From: NJbubo Date: Thu, 21 Nov 2024 01:22:11 +0100 Subject: [PATCH 186/210] =?UTF-8?q?I=20use=20pre-commit.=20Removed=20=20in?= =?UTF-8?q?tervalUpdateValues=20=E2=80=8B=E2=80=8B=3D=20800=20row.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Software/src/battery/CHADEMO-BATTERY.cpp | 7 +- Software/src/battery/CHADEMO-SHUNTS.cpp | 88 ++++++++++++------------ 2 files changed, 48 insertions(+), 47 deletions(-) diff --git a/Software/src/battery/CHADEMO-BATTERY.cpp b/Software/src/battery/CHADEMO-BATTERY.cpp index 5207806c..14720065 100644 --- a/Software/src/battery/CHADEMO-BATTERY.cpp +++ b/Software/src/battery/CHADEMO-BATTERY.cpp @@ -1032,7 +1032,6 @@ void handle_chademo_sequence() { void setup_battery(void) { // Performs one time setup at startup -// intervalUpdateValues = 800; // This mode requires the values to be updated faster pinMode(CHADEMO_PIN_2, OUTPUT); digitalWrite(CHADEMO_PIN_2, LOW); pinMode(CHADEMO_PIN_10, OUTPUT); @@ -1085,9 +1084,9 @@ void setup_battery(void) { // Performs one time setup at startup x109_evse_state.s.status.ChgDischStopControl = 1; handle_chademo_sequence(); -// ISA_deFAULT(); // ISA Setup - it is sufficient to set it once, because it is saved in SUNT -// ISA_initialize(); // ISA Setup - it is sufficient to set it once, because it is saved in SUNT -// ISA_RESTART(); + // ISA_deFAULT(); // ISA Setup - it is sufficient to set it once, because it is saved in SUNT + // ISA_initialize(); // ISA Setup - it is sufficient to set it once, because it is saved in SUNT + // ISA_RESTART(); setupMillis = millis(); } diff --git a/Software/src/battery/CHADEMO-SHUNTS.cpp b/Software/src/battery/CHADEMO-SHUNTS.cpp index 34dfb734..6ca55ca7 100644 --- a/Software/src/battery/CHADEMO-SHUNTS.cpp +++ b/Software/src/battery/CHADEMO-SHUNTS.cpp @@ -241,21 +241,21 @@ inline void ISA_handle528(CAN_frame* frame) { } void ISA_initialize() { - firstframe=false; + firstframe = false; ISA_STOP(); delay(500); - for(int i=0;i<8;i++) { + for (int i = 0; i < 8; i++) { Serial.print("ISA Initialization "); Serial.println(i); - outframe.data.u8[0]=(0x20+i); - outframe.data.u8[1]=0x02; - outframe.data.u8[2]=0x02; - outframe.data.u8[3]=(0x60+(i*18)); - outframe.data.u8[4]=0x00; - outframe.data.u8[5]=0x00; - outframe.data.u8[6]=0x00; - outframe.data.u8[7]=0x00; + outframe.data.u8[0] = (0x20 + i); + outframe.data.u8[1] = 0x02; + outframe.data.u8[2] = 0x02; + outframe.data.u8[3] = (0x60 + (i * 18)); + outframe.data.u8[4] = 0x00; + outframe.data.u8[5] = 0x00; + outframe.data.u8[6] = 0x00; + outframe.data.u8[7] = 0x00; transmit_can(&outframe, can_config.battery); delay(500); @@ -266,8 +266,8 @@ void ISA_initialize() { ISA_START(); delay(500); - lastAs=As; - lastWh=wh; + lastAs = As; + lastWh = wh; } void ISA_STOP() { @@ -316,7 +316,7 @@ void ISA_START() { } void ISA_RESTART() { - //Has the effect of zeroing AH and KWH + //Has the effect of zeroing AH and KWH Serial.println("ISA RESTART"); outframe.data.u8[0] = 0x3F; @@ -332,20 +332,20 @@ void ISA_RESTART() { } void ISA_deFAULT() { - //Returns module to original defaults + //Returns module to original defaults ISA_STOP(); delay(500); Serial.println("ISA RESTART to default"); - outframe.data.u8[0]=0x3D; - outframe.data.u8[1]=0x00; - outframe.data.u8[2]=0x00; - outframe.data.u8[3]=0x00; - outframe.data.u8[4]=0x00; - outframe.data.u8[5]=0x00; - outframe.data.u8[6]=0x00; - outframe.data.u8[7]=0x00; + outframe.data.u8[0] = 0x3D; + outframe.data.u8[1] = 0x00; + outframe.data.u8[2] = 0x00; + outframe.data.u8[3] = 0x00; + outframe.data.u8[4] = 0x00; + outframe.data.u8[5] = 0x00; + outframe.data.u8[6] = 0x00; + outframe.data.u8[7] = 0x00; transmit_can(&outframe, can_config.battery); delay(500); @@ -357,17 +357,17 @@ void ISA_deFAULT() { void ISA_initCurrent() { ISA_STOP(); delay(500); - + Serial.println("ISA Initialization Current"); - outframe.data.u8[0]=0x21; - outframe.data.u8[1]=0x02; - outframe.data.u8[2]=0x01; - outframe.data.u8[3]=0x61; - outframe.data.u8[4]=0x00; - outframe.data.u8[5]=0x00; - outframe.data.u8[6]=0x00; - outframe.data.u8[7]=0x00; + outframe.data.u8[0] = 0x21; + outframe.data.u8[1] = 0x02; + outframe.data.u8[2] = 0x01; + outframe.data.u8[3] = 0x61; + outframe.data.u8[4] = 0x00; + outframe.data.u8[5] = 0x00; + outframe.data.u8[6] = 0x00; + outframe.data.u8[7] = 0x00; transmit_can(&outframe, can_config.battery); delay(500); @@ -377,15 +377,15 @@ void ISA_initCurrent() { ISA_START(); delay(500); - lastAs=As; - lastWh=wh; + lastAs = As; + lastWh = wh; } void ISA_getCONFIG(uint8_t i) { Serial.print("ISA Get Config "); Serial.println(i); - - outframe.data.u8[0] = (0x60+i); + + outframe.data.u8[0] = (0x60 + i); outframe.data.u8[1] = 0x00; outframe.data.u8[2] = 0x00; outframe.data.u8[3] = 0x00; @@ -400,10 +400,12 @@ void ISA_getCONFIG(uint8_t i) { void ISA_getCAN_ID(uint8_t i) { Serial.print("ISA Get CAN ID "); Serial.println(i); - - outframe.data.u8[0] = (0x50+i); - if (i == 8) outframe.data.u8[0] = 0x5D; - if (i == 9) outframe.data.u8[0] = 0x5F; + + outframe.data.u8[0] = (0x50 + i); + if (i == 8) + outframe.data.u8[0] = 0x5D; + if (i == 9) + outframe.data.u8[0] = 0x5F; outframe.data.u8[1] = 0x00; outframe.data.u8[2] = 0x00; outframe.data.u8[3] = 0x00; @@ -417,9 +419,9 @@ void ISA_getCAN_ID(uint8_t i) { void ISA_getINFO(uint8_t i) { Serial.print("ISA Get INFO "); - Serial.println(i,HEX); - - outframe.data.u8[0] = (0x70+i); + Serial.println(i, HEX); + + outframe.data.u8[0] = (0x70 + i); outframe.data.u8[1] = 0x00; outframe.data.u8[2] = 0x00; outframe.data.u8[3] = 0x00; @@ -430,4 +432,4 @@ void ISA_getINFO(uint8_t i) { transmit_can(&outframe, can_config.battery); } -#endif \ No newline at end of file +#endif From f52efba66eb0d159525501f65caca366699b8fa1 Mon Sep 17 00:00:00 2001 From: rha Date: Fri, 22 Nov 2024 18:33:32 +0200 Subject: [PATCH 187/210] Cyclic data Byte offset fix --- Software/src/inverter/KOSTAL-RS485.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Software/src/inverter/KOSTAL-RS485.cpp b/Software/src/inverter/KOSTAL-RS485.cpp index bd5cb8ef..797ef39b 100644 --- a/Software/src/inverter/KOSTAL-RS485.cpp +++ b/Software/src/inverter/KOSTAL-RS485.cpp @@ -199,26 +199,26 @@ void update_RS485_registers_inverter() { nominal_voltage_dV = (((datalayer.battery.info.max_design_voltage_dV - datalayer.battery.info.min_design_voltage_dV) / 2) + datalayer.battery.info.min_design_voltage_dV); - float2frame(BATTERY_INFO, (float)nominal_voltage_dV / 10, 8); + float2frame(BATTERY_INFO, (float)nominal_voltage_dV / 10, 6); - float2frame(CyclicData, (float)datalayer.battery.info.max_design_voltage_dV / 10, 12); + float2frame(CyclicData, (float)datalayer.battery.info.max_design_voltage_dV / 10, 10); - float2frame(CyclicData, (float)average_temperature_dC / 10, 16); + float2frame(CyclicData, (float)average_temperature_dC / 10, 14); // Some current values causes communication error, must be resolved, why. - float2frame(CyclicData, (float)datalayer.battery.status.current_dA / 10, 20); // Peak discharge? current (2 byte float) - float2frame(CyclicData, (float)datalayer.battery.status.current_dA / 10, 24); + float2frame(CyclicData, (float)datalayer.battery.status.current_dA / 10, 18); // Peak discharge? current (2 byte float) + float2frame(CyclicData, (float)datalayer.battery.status.current_dA / 10, 22); - float2frame(CyclicData, (float)discharge_current_dA / 10, 28); // BAttery capacity Ah + float2frame(CyclicData, (float)discharge_current_dA / 10, 26); // BAttery capacity Ah - float2frame(CyclicData, (float)discharge_current_dA / 10, 32); + float2frame(CyclicData, (float)discharge_current_dA / 10, 30); // When SOC = 100%, drop down allowed charge current down. if ((datalayer.battery.status.reported_soc / 100) < 100) { - float2frame(CyclicData, (float)charge_current_dA / 10, 36); + float2frame(CyclicData, (float)charge_current_dA / 10, 34); } else { - float2frame(CyclicData, 0.0, 36); + float2frame(CyclicData, 0.0, 34); } float2frame(CyclicData, (float)datalayer.battery.status.temperature_max_dC / 10, 38); From a005533ef2e5cebcc24af65dbfc393bb153dbe31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Fri, 22 Nov 2024 19:08:33 +0200 Subject: [PATCH 188/210] Refactor according to 7.8.dev --- Software/Software.ino | 2 +- Software/src/inverter/INVERTERS.h | 1 + Software/src/inverter/KOSTAL-RS485.cpp | 140 ++++++++++--------------- Software/src/inverter/KOSTAL-RS485.h | 2 +- 4 files changed, 61 insertions(+), 84 deletions(-) diff --git a/Software/Software.ino b/Software/Software.ino index 3820398c..4fccaea9 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -170,7 +170,7 @@ void setup() { init_rs485(); init_serialDataLink(); -#if defined(CAN_INVERTER_SELECTED) || defined(MODBUS_INVERTER_SELECTED) +#if defined(CAN_INVERTER_SELECTED) || defined(MODBUS_INVERTER_SELECTED) || defined(RS485_INVERTER_SELECTED) setup_inverter(); #endif setup_battery(); diff --git a/Software/src/inverter/INVERTERS.h b/Software/src/inverter/INVERTERS.h index bf933c8c..f1944ecf 100644 --- a/Software/src/inverter/INVERTERS.h +++ b/Software/src/inverter/INVERTERS.h @@ -72,6 +72,7 @@ void update_modbus_registers_inverter(); #ifdef RS485_INVERTER_SELECTED void receive_RS485(); void update_RS485_registers_inverter(); +void setup_inverter(); #endif #endif diff --git a/Software/src/inverter/KOSTAL-RS485.cpp b/Software/src/inverter/KOSTAL-RS485.cpp index 797ef39b..02d8c4a4 100644 --- a/Software/src/inverter/KOSTAL-RS485.cpp +++ b/Software/src/inverter/KOSTAL-RS485.cpp @@ -10,8 +10,6 @@ static const uint8_t KOSTAL_FRAMEHEADER[5] = {0x62, 0xFF, 0x02, 0xFF, 0x29}; static const uint8_t KOSTAL_FRAMEHEADER2[5] = {0x63, 0xFF, 0x02, 0xFF, 0x29}; static uint16_t nominal_voltage_dV = 0; -static uint16_t discharge_current_dA = 0; -static uint16_t charge_current_dA = 0; static int16_t average_temperature_dC = 0; static uint8_t incoming_message_counter = RS485_HEALTHY; static int8_t f2_startup_count = 0; @@ -29,31 +27,33 @@ union f32b { byte b[4]; }; -uint8_t BATTERY_INFO[40] = {0x00, 0xE2, 0xFF, 0x02, 0xFF, 0x29, // Frame header - 0x00, 0x00, 0x80, 0x43, // 256.063 Nominal voltage / 5*51.2=256 first byte 0x01 or 0x04 - 0xE4, 0x70, 0x8A, 0x5C, // Manufacture date (Epoch time) (BYD: GetBatteryInfo this[0x10ac]) - 0xB5, 0x00, 0xD3, 0x00, // Battery Serial number? Modbus register 527 - 0x10b0 - 0x00, 0x00, 0xC8, 0x41, // 0x10b4 - 0xC2, 0x18, // Battery Firmware, modbus register 586 (0x10b8) - 0x00, // Static (BYD: GetBatteryInfo this[0x10ba]) - 0x00, // ? - 0x59, 0x42, // Static (BYD: GetBatteryInfo this[0x10bc]) - 0x00, 0x00, // Static (BYD: GetBatteryInfo this[0x10be]) - 0x00, 0x00, - 0x05, 0x00, 0xA0, 0x00, 0x00, 0x00, - 0x4D, // CRC - 0x00}; // +uint8_t BATTERY_INFO[40] = { + 0x00, 0xE2, 0xFF, 0x02, 0xFF, 0x29, // Frame header + 0x00, 0x00, 0x80, 0x43, // 256.063 Nominal voltage / 5*51.2=256 first byte 0x01 or 0x04 + 0xE4, 0x70, 0x8A, 0x5C, // Manufacture date (Epoch time) (BYD: GetBatteryInfo this[0x10ac]) + 0xB5, 0x00, 0xD3, 0x00, // Battery Serial number? Modbus register 527 - 0x10b0 + 0x00, 0x00, 0xC8, 0x41, // 0x10b4 + 0xC2, 0x18, // Battery Firmware, modbus register 586 (0x10b8) + 0x00, // Static (BYD: GetBatteryInfo this[0x10ba]) + 0x00, // ? + 0x59, 0x42, // Static (BYD: GetBatteryInfo this[0x10bc]) + 0x00, 0x00, // Static (BYD: GetBatteryInfo this[0x10be]) + 0x00, 0x00, 0x05, 0x00, 0xA0, 0x00, 0x00, 0x00, + 0x4D, // CRC + 0x00}; // // values in CyclicData will be overwritten at update_modbus_registers_inverter() uint8_t CyclicData[64] = { 0x00, // First zero byte pointer 0xE2, 0xFF, 0x02, 0xFF, 0x29, // frame Header - 0x1D, 0x5A, 0x85, 0x43, // Current Voltage (float) Modbus register 216, Bytes 6-9 - 0x00, 0x00, 0x8D, 0x43, // Max Voltage (2 byte float), Bytes 12-13 - 0x00, 0x00, 0xAC, 0x41, // BAttery Temperature (2 byte float) Modbus register 214, Bytes 16-17 - 0x00, 0x00, 0x00, 0x00, // Peak Current (1s period?), Bytes 18-21 - Communication fault seen with some values (>10A?) - 0x00, 0x00, 0x00, 0x00, // Avg current (1s period?), Bytes 22-25 - Communication fault seen with some values (>10A?) + 0x1D, 0x5A, 0x85, 0x43, // Current Voltage (float) Modbus register 216, Bytes 6-9 + 0x00, 0x00, 0x8D, 0x43, // Max Voltage (2 byte float), Bytes 12-13 + 0x00, 0x00, 0xAC, 0x41, // BAttery Temperature (2 byte float) Modbus register 214, Bytes 16-17 + 0x00, 0x00, 0x00, + 0x00, // Peak Current (1s period?), Bytes 18-21 - Communication fault seen with some values (>10A?) + 0x00, 0x00, 0x00, + 0x00, // Avg current (1s period?), Bytes 22-25 - Communication fault seen with some values (>10A?) 0x00, 0x00, 0x48, 0x42, // Max discharge current (2 byte float), Bit 26-29, // Sunspec: ADisChaMax 0x00, 0x00, 0xC8, 0x41, // Battery gross capacity, Ah (2 byte float) , Bytes 30-33, Modbus 512 @@ -64,14 +64,14 @@ uint8_t CyclicData[64] = { 0xA4, 0x70, 0x55, 0x40, // MaxCellVolt (float), Bit 46-49 0x7D, 0x3F, 0x55, 0x40, // MinCellVolt (float), Bit 50-53 - 0xFE, 0x04, // Cycle count, - 0x00, // Byte 56 - 0x00, // When SOC=100 Byte57=0x40, at startup 0x03 (about 7 times), otherwise 0x02 - 0x64, // SOC , Bit 58 - 0x00, // Unknown, - 0x00, // Unknown, - 0x00, // Unknown, - 0x00, // CRC (inverted sum of bytes 1-62 + 0xC0), Bit 62 + 0xFE, 0x04, // Cycle count, + 0x00, // Byte 56 + 0x00, // When SOC=100 Byte57=0x40, at startup 0x03 (about 7 times), otherwise 0x02 + 0x64, // SOC , Bit 58 + 0x00, // Unknown, + 0x00, // Unknown, + 0x00, // Unknown, + 0x00, // CRC (inverted sum of bytes 1-62 + 0xC0), Bit 62 0x00}; // FE 04 01 40 xx 01 01 02 yy (fully charged) @@ -84,7 +84,7 @@ uint8_t frame3[9] = { 0x00 //endbyte }; -uint8_t frame4[8] = {0x07, 0xE3, 0xFF, 0x02, 0xFF, 0x29, 0xF4, 0x00}; +uint8_t frame4[8] = {0x07, 0xE3, 0xFF, 0x02, 0xFF, 0x29, 0xF4, 0x00}; uint8_t frameB1[10] = {0x07, 0x63, 0xFF, 0x02, 0xFF, 0x29, 0x5E, 0x02, 0x16, 0x00}; uint8_t RS485_RXFRAME[10]; @@ -115,25 +115,25 @@ void send_kostal(byte* arr, int alen) { Serial2.write(arr, alen); } -void scramble_null_bytes(byte *lfc, int len) { +void scramble_null_bytes(byte* lfc, int len) { int last_null_byte = 0; for (int i = 0; i < len; i++) { if (lfc[i] == '\0') { - lfc[last_null_byte] = (byte) (i - last_null_byte); + lfc[last_null_byte] = (byte)(i - last_null_byte); last_null_byte = i; } } } -byte calculate_kostal_crc(byte *lfc, int len) { +byte calculate_kostal_crc(byte* lfc, int len) { unsigned int sum = 0; if (lfc[0] != 0) { - printf("WARNING: first byte should be 0, but is 0x%02x\n", lfc[0]); + printf("WARNING: first byte should be 0, but is 0x%02x\n", lfc[0]); } for (int i = 1; i < len; i++) { - sum += lfc[i]; + sum += lfc[i]; } - return (byte) (-sum & 0xff); + return (byte)(-sum & 0xff); } byte calculate_frame1_crc(byte* lfc, int lastbyte) { @@ -158,39 +158,14 @@ bool check_kostal_frame_crc() { void update_RS485_registers_inverter() { - if (datalayer.battery.status.voltage_dV > 10) { // Only update value when we have voltage available to avoid div0 - charge_current_dA = - ((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_dA = (charge_current_dA * 10); //Value needs a decimal before getting sent to inverter (81.0A) - - discharge_current_dA = - ((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_dA = (discharge_current_dA * 10); //Value needs a decimal before getting sent to inverter (81.0A) - } - - if (charge_current_dA > datalayer.battery.info.max_charge_amp_dA) { - charge_current_dA = - datalayer.battery.info - .max_charge_amp_dA; //Cap the value to the max allowed Amp. Some inverters cannot handle large values. - } - - if (discharge_current_dA > datalayer.battery.info.max_discharge_amp_dA) { - discharge_current_dA = - datalayer.battery.info - .max_discharge_amp_dA; //Cap the value to the max allowed Amp. Some inverters cannot handle large values. - } - average_temperature_dC = ((datalayer.battery.status.temperature_max_dC + datalayer.battery.status.temperature_min_dC) / 2); if (datalayer.battery.status.temperature_min_dC < 0) { average_temperature_dC = 0; } - if (datalayer.system.status.battery_allows_contactor_closing & datalayer.system.status.inverter_allows_contactor_closing ) { + if (datalayer.system.status.battery_allows_contactor_closing & + datalayer.system.status.inverter_allows_contactor_closing) { float2frame(CyclicData, (float)datalayer.battery.status.voltage_dV / 10, 6); // Confirmed OK mapping } else { float2frame(CyclicData, 0.0, 6); @@ -206,17 +181,18 @@ void update_RS485_registers_inverter() { float2frame(CyclicData, (float)average_temperature_dC / 10, 14); // Some current values causes communication error, must be resolved, why. - float2frame(CyclicData, (float)datalayer.battery.status.current_dA / 10, 18); // Peak discharge? current (2 byte float) + float2frame(CyclicData, (float)datalayer.battery.status.current_dA / 10, + 18); // Peak discharge? current (2 byte float) float2frame(CyclicData, (float)datalayer.battery.status.current_dA / 10, 22); - float2frame(CyclicData, (float)discharge_current_dA / 10, 26); // BAttery capacity Ah + float2frame(CyclicData, (float)datalayer.battery.status.max_discharge_current_dA / 10, 26); // BAttery capacity Ah - float2frame(CyclicData, (float)discharge_current_dA / 10, 30); + float2frame(CyclicData, (float)datalayer.battery.status.max_discharge_current_dA / 10, 30); // When SOC = 100%, drop down allowed charge current down. if ((datalayer.battery.status.reported_soc / 100) < 100) { - float2frame(CyclicData, (float)charge_current_dA / 10, 34); + float2frame(CyclicData, (float)datalayer.battery.status.max_charge_current_dA / 10, 34); } else { float2frame(CyclicData, 0.0, 34); } @@ -231,7 +207,6 @@ void update_RS485_registers_inverter() { register_content_ok = true; - if (incoming_message_counter > 0) { incoming_message_counter--; } @@ -258,7 +233,7 @@ void receive_RS485() // Runs as fast as possible to handle the serial stream if (startupMillis) { if (((currentMillis - startupMillis) >= INTERVAL_2_S & currentMillis - startupMillis <= 7000) & - datalayer.system.status.inverter_allows_contactor_closing) { + datalayer.system.status.inverter_allows_contactor_closing) { // Disconnect allowed only, when curren zero if (datalayer.battery.status.current_dA == 0) { datalayer.system.status.inverter_allows_contactor_closing = false; @@ -294,29 +269,25 @@ void receive_RS485() // Runs as fast as possible to handle the serial stream if (RS485_RXFRAME[1] == 'c') { if (RS485_RXFRAME[6] == 0x47) { - // Set time function - Do nothing. - send_kostal(frame4, 8); // ACK + // Set time function - Do nothing. + send_kostal(frame4, 8); // ACK } if (RS485_RXFRAME[6] == 0x5E) { // Set State function if (RS485_RXFRAME[7] == 0x01) { // State X - } - else if (RS485_RXFRAME[7] == 0x04) { + } else if (RS485_RXFRAME[7] == 0x04) { // INVALID - } - else { + } else { // State Y } - send_kostal(frame4, 8); // ACK + send_kostal(frame4, 8); // ACK } - } - else if (RS485_RXFRAME[1] == 'b') { + } else if (RS485_RXFRAME[1] == 'b') { if (RS485_RXFRAME[6] == 0x50) { //Reverse polarity, do nothing - } - else { - int code=RS485_RXFRAME[6] + RS485_RXFRAME[7]*0x100; + } else { + int code = RS485_RXFRAME[6] + RS485_RXFRAME[7] * 0x100; if (code == 0x44a) { //Send cyclic data update_values_battery(); @@ -327,7 +298,7 @@ void receive_RS485() // Runs as fast as possible to handle the serial stream byte tmpframe[64]; //copy values to prevent data manipulation during rewrite/crc calculation memcpy(tmpframe, CyclicData, 64); tmpframe[62] = calculate_kostal_crc(tmpframe, 62); - scramble_null_bytes(tmpframe,64); + scramble_null_bytes(tmpframe, 64); send_kostal(tmpframe, 64); } if (code == 0x84a) { @@ -358,4 +329,9 @@ void receive_RS485() // Runs as fast as possible to handle the serial stream } } +void setup_inverter(void) { // Performs one time setup at startup + strncpy(datalayer.system.info.inverter_protocol, "BYD battery via Kostal RS485", 63); + datalayer.system.info.inverter_protocol[63] = '\0'; +} + #endif diff --git a/Software/src/inverter/KOSTAL-RS485.h b/Software/src/inverter/KOSTAL-RS485.h index 22598b29..3de0697d 100644 --- a/Software/src/inverter/KOSTAL-RS485.h +++ b/Software/src/inverter/KOSTAL-RS485.h @@ -4,6 +4,6 @@ #include "../include.h" #define RS485_INVERTER_SELECTED -#define DEBUG_KOSTAL_RS485_DATA // Enable this line to get TX / RX printed out via serial +//#define DEBUG_KOSTAL_RS485_DATA // Enable this line to get TX / RX printed out via serial #endif From 66a713ef426c1b9048f0381c6ebb1b65d588d259 Mon Sep 17 00:00:00 2001 From: Bernhard Urban-Forster Date: Fri, 22 Nov 2024 18:24:27 +0100 Subject: [PATCH 189/210] github: add BYD_KOSTAL_RS485 to the build matrix --- .github/workflows/compile-all-combinations.yml | 1 + .github/workflows/compile-all-inverters.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/compile-all-combinations.yml b/.github/workflows/compile-all-combinations.yml index 281c0f5c..c194e97a 100644 --- a/.github/workflows/compile-all-combinations.yml +++ b/.github/workflows/compile-all-combinations.yml @@ -54,6 +54,7 @@ jobs: # These are the emulated inverter communication protocols for which the code will be compiled. inverter: - BYD_CAN + - BYD_KOSTAL_RS485 - BYD_SMA - BYD_MODBUS - FOXESS_CAN diff --git a/.github/workflows/compile-all-inverters.yml b/.github/workflows/compile-all-inverters.yml index 2bf79370..af2ce379 100644 --- a/.github/workflows/compile-all-inverters.yml +++ b/.github/workflows/compile-all-inverters.yml @@ -44,6 +44,7 @@ jobs: inverter: - AFORE_CAN - BYD_CAN + - BYD_KOSTAL_RS485 - BYD_SMA - BYD_MODBUS - FOXESS_CAN From 10a75d7d8da05ec45e0b223c4476344fb194a0e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Fri, 22 Nov 2024 23:25:33 +0200 Subject: [PATCH 190/210] Add SMA LV protocol --- Software/USER_SETTINGS.h | 1 + Software/src/inverter/INVERTERS.h | 4 + Software/src/inverter/SMA-48V-CAN.cpp | 184 ------------------ Software/src/inverter/SMA-LV-CAN.cpp | 166 ++++++++++++++++ .../inverter/{SMA-48V-CAN.h => SMA-LV-CAN.h} | 4 +- 5 files changed, 173 insertions(+), 186 deletions(-) delete mode 100644 Software/src/inverter/SMA-48V-CAN.cpp create mode 100644 Software/src/inverter/SMA-LV-CAN.cpp rename Software/src/inverter/{SMA-48V-CAN.h => SMA-LV-CAN.h} (82%) diff --git a/Software/USER_SETTINGS.h b/Software/USER_SETTINGS.h index cbd51f4c..f7d39463 100644 --- a/Software/USER_SETTINGS.h +++ b/Software/USER_SETTINGS.h @@ -45,6 +45,7 @@ //#define PYLON_CAN //Enable this line to emulate a "High Voltage Pylontech battery" over CAN bus //#define SCHNEIDER_CAN //Enable this line to emulate a "Schneider Version 2: SE BMS" over CAN bus //#define SMA_CAN //Enable this line to emulate a "BYD Battery-Box H 8.9kWh, 7 mod" over CAN bus +//#define SMA_LV_CAN //Enable this line to emulate a "SMA Sunny Island 48V battery" over CAN bus //#define SMA_TRIPOWER_CAN //Enable this line to emulate a "SMA Home Storage battery" over CAN bus //#define SOFAR_CAN //Enable this line to emulate a "Sofar Energy Storage Inverter High Voltage BMS General Protocol (Extended Frame)" over CAN bus //#define SOLAX_CAN //Enable this line to emulate a "SolaX Triple Power LFP" over CAN bus diff --git a/Software/src/inverter/INVERTERS.h b/Software/src/inverter/INVERTERS.h index 18089fe8..83d286b4 100644 --- a/Software/src/inverter/INVERTERS.h +++ b/Software/src/inverter/INVERTERS.h @@ -39,6 +39,10 @@ #include "SMA-CAN.h" #endif +#ifdef SMA_LV_CAN +#include "SMA-LV-CAN.h" +#endif + #ifdef SMA_TRIPOWER_CAN #include "SMA-TRIPOWER-CAN.h" #endif diff --git a/Software/src/inverter/SMA-48V-CAN.cpp b/Software/src/inverter/SMA-48V-CAN.cpp deleted file mode 100644 index 4c9a37ce..00000000 --- a/Software/src/inverter/SMA-48V-CAN.cpp +++ /dev/null @@ -1,184 +0,0 @@ -#include "../include.h" -#ifdef SMA_CAN -#include "../datalayer/datalayer.h" -#include "SMA-CAN.h" - -/* SMA 48V CAN protocol: -CAN 2.0A -500kBit/sec -11-Bit Identifiers */ - -/* Do not change code below unless you are sure what you are doing */ -static unsigned long previousMillis100ms = 0; - -//Actual content messages -CAN_frame SMA_558 = {.FD = false, - .ext_ID = false, - .DLC = 8, - .ID = 0x558, - .data = {0x03, 0x12, 0x00, 0x04, 0x00, 0x59, 0x07, 0x07}}; //7x BYD modules, Vendor ID 7 BYD -CAN_frame SMA_598 = {.FD = false, - .ext_ID = false, - .DLC = 8, - .ID = 0x598, - .data = {0x00, 0x00, 0x12, 0x34, 0x5A, 0xDE, 0x07, 0x4F}}; //B0-4 Serial, rest unknown -CAN_frame SMA_5D8 = {.FD = false, - .ext_ID = false, - .DLC = 8, - .ID = 0x5D8, - .data = {0x00, 0x42, 0x59, 0x44, 0x00, 0x00, 0x00, 0x00}}; //B Y D -CAN_frame SMA_618_1 = {.FD = false, - .ext_ID = false, - .DLC = 8, - .ID = 0x618, - .data = {0x00, 0x42, 0x61, 0x74, 0x74, 0x65, 0x72, 0x79}}; //0 B A T T E R Y -CAN_frame SMA_618_2 = {.FD = false, - .ext_ID = false, - .DLC = 8, - .ID = 0x618, - .data = {0x01, 0x2D, 0x42, 0x6F, 0x78, 0x20, 0x48, 0x39}}; //1 - B O X H -CAN_frame SMA_618_3 = {.FD = false, - .ext_ID = false, - .DLC = 8, - .ID = 0x618, - .data = {0x02, 0x2E, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00}}; //2 - 0 -CAN_frame SMA_358 = {.FD = false, - .ext_ID = false, - .DLC = 8, - .ID = 0x358, - .data = {0x0F, 0x6C, 0x06, 0x20, 0x00, 0x00, 0x00, 0x00}}; -CAN_frame SMA_3D8 = {.FD = false, - .ext_ID = false, - .DLC = 8, - .ID = 0x3D8, - .data = {0x04, 0x10, 0x27, 0x10, 0x00, 0x18, 0xF9, 0x00}}; -CAN_frame SMA_458 = {.FD = false, - .ext_ID = false, - .DLC = 8, - .ID = 0x458, - .data = {0x00, 0x00, 0x06, 0x75, 0x00, 0x00, 0x05, 0xD6}}; -CAN_frame SMA_518 = {.FD = false, - .ext_ID = false, - .DLC = 8, - .ID = 0x518, - .data = {0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF}}; -CAN_frame SMA_4D8 = {.FD = false, - .ext_ID = false, - .DLC = 8, - .ID = 0x4D8, - .data = {0x09, 0xFD, 0x00, 0x00, 0x00, 0xA8, 0x02, 0x08}}; -CAN_frame SMA_158 = {.FD = false, - .ext_ID = false, - .DLC = 8, - .ID = 0x158, - .data = {0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x6A, 0xAA, 0xAA}}; - -static int16_t temperature_average = 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 - //Calculate values - - temperature_average = - ((datalayer.battery.status.temperature_max_dC + datalayer.battery.status.temperature_min_dC) / 2); - - if (datalayer.battery.status.voltage_dV > 10) { // Only update value when we have voltage available to avoid div0 - ampere_hours_remaining = - ((datalayer.battery.status.reported_remaining_capacity_Wh / datalayer.battery.status.voltage_dV) * - 100); //(WH[10000] * V+1[3600])*100 = 270 (27.0Ah) - } - - //Map values to CAN messages - //Battery charge voltage (eg 400.0V = 4000 , 16bits long) - SMA_351.data.u8[0] = ((datalayer.battery.info.max_design_voltage_dV - 20) >> 8); - SMA_351.data.u8[1] = ((datalayer.battery.info.max_design_voltage_dV - 20) & 0x00FF); - //Discharge limited current, 500 = 50A, (0.1, A) - SMA_351.data.u8[2] = (datalayer.battery.status.max_discharge_current_dA >> 8); - SMA_351.data.u8[3] = (datalayer.battery.status.max_discharge_current_dA & 0x00FF); - //Charge limited current, 125 =12.5A (0.1, A) - SMA_351.data.u8[4] = (datalayer.battery.status.max_charge_current_dA >> 8); - SMA_351.data.u8[5] = (datalayer.battery.status.max_charge_current_dA & 0x00FF); - //Discharge voltage (eg 300.0V = 3000 , 16bits long) - SMA_351.data.u8[6] = ((datalayer.battery.info.min_design_voltage_dV + 20) >> 8); - SMA_351.data.u8[7] = ((datalayer.battery.info.min_design_voltage_dV + 20) & 0x00FF); - - //SOC (100.00%) - SMA_3D8.data.u8[0] = (datalayer.battery.status.reported_soc >> 8); - SMA_3D8.data.u8[1] = (datalayer.battery.status.reported_soc & 0x00FF); - //StateOfHealth (100.00%) - SMA_3D8.data.u8[2] = (datalayer.battery.status.soh_pptt >> 8); - SMA_3D8.data.u8[3] = (datalayer.battery.status.soh_pptt & 0x00FF); - //State of charge (AH, 0.1) - SMA_3D8.data.u8[4] = (ampere_hours_remaining >> 8); - SMA_3D8.data.u8[5] = (ampere_hours_remaining & 0x00FF); - - //Voltage (370.0) - SMA_4D8.data.u8[0] = (datalayer.battery.status.voltage_dV >> 8); - SMA_4D8.data.u8[1] = (datalayer.battery.status.voltage_dV & 0x00FF); - //Current (TODO: signed OK?) - SMA_4D8.data.u8[2] = (datalayer.battery.status.current_dA >> 8); - SMA_4D8.data.u8[3] = (datalayer.battery.status.current_dA & 0x00FF); - //Temperature average - SMA_4D8.data.u8[4] = (temperature_average >> 8); - SMA_4D8.data.u8[5] = (temperature_average & 0x00FF); - //Battery ready - if (datalayer.battery.status.bms_status == ACTIVE) { - SMA_4D8.data.u8[6] = READY_STATE; - } else { - SMA_4D8.data.u8[6] = STOP_STATE; - } -} - -void receive_can_inverter(CAN_frame rx_frame) { - switch (rx_frame.ID) { - case 0x305: - datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE; - //Frame0-1 Battery Voltage - //Frame2-3 Battery Current - //Frame4-5 Battery Temperature - //Frame6-7 SOC Battery - break; - case 0x306: - datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE; - //Frame0-1 SOH Battery - //Frame2 Charging procedure - //Frame3 Operating state - //Frame4-5 Active error message - //Frame6-7 Battery charge voltage setpoint - break; - default: - break; - } -} - -void send_can_inverter() { - unsigned long currentMillis = millis(); - - // Send CAN Message every 100ms if Enable line is HIGH - if (datalayer.system.status.inverter_allows_contactor_closing) { - if (currentMillis - previousMillis100ms >= 100) { - previousMillis100ms = currentMillis; - - transmit_can(&SMA_351, can_config.inverter); - transmit_can(&SMA_355, can_config.inverter); - transmit_can(&SMA_356, can_config.inverter); - transmit_can(&SMA_35A, can_config.inverter); - transmit_can(&SMA_35B, can_config.inverter); - transmit_can(&SMA_35E, can_config.inverter); - transmit_can(&SMA_35F, can_config.inverter); - - //Remote quick stop (optional) - if (datalayer.battery.status.bms_status == FAULT) { - transmit_can(&SMA_00F, can_config.inverter); - //After receiving this message, Sunny Island will immediately go into standby. - //Please send start command, to start again. Manual start is also possible. - } - } - } -} - -void setup_inverter(void) { // Performs one time setup at startup over CAN bus - strncpy(datalayer.system.info.inverter_protocol, "SMA 48V CAN", 63); - datalayer.system.info.inverter_protocol[63] = '\0'; -} -#endif diff --git a/Software/src/inverter/SMA-LV-CAN.cpp b/Software/src/inverter/SMA-LV-CAN.cpp new file mode 100644 index 00000000..77d3c429 --- /dev/null +++ b/Software/src/inverter/SMA-LV-CAN.cpp @@ -0,0 +1,166 @@ +#include "../include.h" +#ifdef SMA_LV_CAN +#include "../datalayer/datalayer.h" +#include "SMA-LV-CAN.h" + +/* SMA Sunny Island Low Voltage (48V) CAN protocol: +CAN 2.0A +500kBit/sec +11-Bit Identifiers */ + +/* Do not change code below unless you are sure what you are doing */ +static unsigned long previousMillis100ms = 0; + +#define VOLTAGE_OFFSET_DV 40 //Offset in deciVolt from max charge voltage and min discharge voltage +#define MAX_VOLTAGE_DV 630 +#define MIN_VOLTAGE_DV 41 + +//Actual content messages +CAN_frame SMA_351 = {.FD = false, // Battery charge voltage, charge/discharge limit, min discharge voltage + .ext_ID = false, + .DLC = 8, + .ID = 0x351, + .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame SMA_355 = {.FD = false, // SOC, SOH, HiResSOC + .ext_ID = false, + .DLC = 8, + .ID = 0x355, + .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame SMA_356 = {.FD = false, // Battery voltage, current, temperature + .ext_ID = false, + .DLC = 8, + .ID = 0x356, + .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame SMA_35A = {.FD = false, // Alarms & Warnings + .ext_ID = false, + .DLC = 8, + .ID = 0x35A, + .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame SMA_35B = {.FD = false, // Events + .ext_ID = false, + .DLC = 8, + .ID = 0x35B, + .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame SMA_35E = {.FD = false, // Manufacturer ASCII + .ext_ID = false, + .DLC = 8, + .ID = 0x35E, + .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame SMA_35F = {.FD = false, // Battery Type, version, capacity, ID + .ext_ID = false, + .DLC = 8, + .ID = 0x35F, + .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame SMA_00F = {.FD = false, // Emergency stop message + .ext_ID = false, + .DLC = 8, //Documentation unclear, should message even have any content? + .ID = 0x00F, + .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; + +static int16_t temperature_average = 0; + +void update_values_can_inverter() { //This function maps all the values fetched from battery CAN to the correct CAN messages + //Calculate values + + temperature_average = + ((datalayer.battery.status.temperature_max_dC + datalayer.battery.status.temperature_min_dC) / 2); + + //Map values to CAN messages + //Battery charge voltage (eg 400.0V = 4000 , 16bits long) (MIN 41V, MAX 63V, default 54V) + SMA_351.data.u8[0] = ((datalayer.battery.info.max_design_voltage_dV - VOLTAGE_OFFSET_DV) >> 8); + SMA_351.data.u8[1] = ((datalayer.battery.info.max_design_voltage_dV - VOLTAGE_OFFSET_DV) & 0x00FF); + if (datalayer.battery.info.max_design_voltage_dV > MAX_VOLTAGE_DV) { + //If the battery is designed for more than 63.0V, cap the value + SMA_351.data.u8[0] = (MAX_VOLTAGE_DV >> 8); + SMA_351.data.u8[1] = (MAX_VOLTAGE_DV & 0x00FF); + //TODO; raise event? + } + //Discharge limited current, 500 = 50A, (0.1, A) (MIN 0, MAX 1200) + SMA_351.data.u8[2] = (datalayer.battery.status.max_discharge_current_dA >> 8); + SMA_351.data.u8[3] = (datalayer.battery.status.max_discharge_current_dA & 0x00FF); + //Charge limited current, 125 =12.5A (0.1, A) (MIN 0, MAX 1200) + SMA_351.data.u8[4] = (datalayer.battery.status.max_charge_current_dA >> 8); + SMA_351.data.u8[5] = (datalayer.battery.status.max_charge_current_dA & 0x00FF); + //Discharge voltage (eg 300.0V = 3000 , 16bits long) (MIN 41V, MAX 48V, default 41V) + SMA_351.data.u8[6] = ((datalayer.battery.info.min_design_voltage_dV + VOLTAGE_OFFSET_DV) >> 8); + SMA_351.data.u8[7] = ((datalayer.battery.info.min_design_voltage_dV + VOLTAGE_OFFSET_DV) & 0x00FF); + if (datalayer.battery.info.min_design_voltage_dV < MIN_VOLTAGE_DV) { + //If the battery is designed for discharge voltage below 41.0V, cap the value + SMA_351.data.u8[6] = (MIN_VOLTAGE_DV >> 8); + SMA_351.data.u8[7] = (MIN_VOLTAGE_DV & 0x00FF); + //TODO; raise event? + } + + //SOC (100%) + SMA_355.data.u8[0] = ((datalayer.battery.status.reported_soc / 100) >> 8); + SMA_355.data.u8[1] = ((datalayer.battery.status.reported_soc / 100) & 0x00FF); + //StateOfHealth (100%) + SMA_355.data.u8[2] = ((datalayer.battery.status.soh_pptt / 100) >> 8); + SMA_355.data.u8[3] = ((datalayer.battery.status.soh_pptt / 100) & 0x00FF); + //State of charge High Precision (100.00%) + SMA_355.data.u8[4] = (datalayer.battery.status.reported_soc >> 8); + SMA_355.data.u8[5] = (datalayer.battery.status.reported_soc & 0x00FF); + + //Voltage (370.0) + SMA_356.data.u8[0] = ((datalayer.battery.status.voltage_dV * 10) >> 8); + SMA_356.data.u8[1] = ((datalayer.battery.status.voltage_dV * 10) & 0x00FF); + //Current (S16 dA) + SMA_356.data.u8[2] = (datalayer.battery.status.current_dA >> 8); + SMA_356.data.u8[3] = (datalayer.battery.status.current_dA & 0x00FF); + //Temperature (s16 degC) + SMA_356.data.u8[4] = (temperature_average >> 8); + SMA_356.data.u8[5] = (temperature_average & 0x00FF); + + //TODO: Map error/warnings in 0x35A +} + +void receive_can_inverter(CAN_frame rx_frame) { + switch (rx_frame.ID) { + case 0x305: + datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE; + //Frame0-1 Battery Voltage + //Frame2-3 Battery Current + //Frame4-5 Battery Temperature + //Frame6-7 SOC Battery + break; + case 0x306: + datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE; + //Frame0-1 SOH Battery + //Frame2 Charging procedure + //Frame3 Operating state + //Frame4-5 Active error message + //Frame6-7 Battery charge voltage setpoint + break; + default: + break; + } +} + +void send_can_inverter() { + unsigned long currentMillis = millis(); + + if (currentMillis - previousMillis100ms >= INTERVAL_100_MS) { + previousMillis100ms = currentMillis; + + transmit_can(&SMA_351, can_config.inverter); + transmit_can(&SMA_355, can_config.inverter); + transmit_can(&SMA_356, can_config.inverter); + transmit_can(&SMA_35A, can_config.inverter); + transmit_can(&SMA_35B, can_config.inverter); + transmit_can(&SMA_35E, can_config.inverter); + transmit_can(&SMA_35F, can_config.inverter); + + //Remote quick stop (optional) + if (datalayer.battery.status.bms_status == FAULT) { + transmit_can(&SMA_00F, can_config.inverter); + //After receiving this message, Sunny Island will immediately go into standby. + //Please send start command, to start again. Manual start is also possible. + } + } +} + +void setup_inverter(void) { // Performs one time setup at startup over CAN bus + strncpy(datalayer.system.info.inverter_protocol, "SMA Low Voltage (48V) protocol via CAN", 63); + datalayer.system.info.inverter_protocol[63] = '\0'; +} +#endif diff --git a/Software/src/inverter/SMA-48V-CAN.h b/Software/src/inverter/SMA-LV-CAN.h similarity index 82% rename from Software/src/inverter/SMA-48V-CAN.h rename to Software/src/inverter/SMA-LV-CAN.h index 111044a4..a1c6e7e9 100644 --- a/Software/src/inverter/SMA-48V-CAN.h +++ b/Software/src/inverter/SMA-LV-CAN.h @@ -1,5 +1,5 @@ -#ifndef SMA_CAN_H -#define SMA_CAN_H +#ifndef SMA_LV_CAN_H +#define SMA_LV_CAN_H #include "../include.h" #define CAN_INVERTER_SELECTED From 2338d8c87cad864d58d1cc978b2e544203af9b77 Mon Sep 17 00:00:00 2001 From: josiahhiggs <79869367+josiahhiggs@users.noreply.github.com> Date: Sat, 23 Nov 2024 17:02:47 +1300 Subject: [PATCH 191/210] added mux for 0x352 and webserver updated --- Software/src/battery/TESLA-BATTERY.cpp | 242 ++++++++++-------- Software/src/datalayer/datalayer_extended.h | 5 + .../webserver/advanced_battery_html.cpp | 81 ++++-- 3 files changed, 194 insertions(+), 134 deletions(-) diff --git a/Software/src/battery/TESLA-BATTERY.cpp b/Software/src/battery/TESLA-BATTERY.cpp index 7b1a269f..41914455 100644 --- a/Software/src/battery/TESLA-BATTERY.cpp +++ b/Software/src/battery/TESLA-BATTERY.cpp @@ -25,21 +25,27 @@ CAN_frame TESLA_221_2 = { static uint16_t sendContactorClosingMessagesStill = 300; static uint32_t battery_total_discharge = 0; static uint32_t battery_total_charge = 0; -static uint16_t battery_volts = 0; // V -static int16_t battery_amps = 0; // A -static uint16_t battery_raw_amps = 0; // A -static int16_t battery_max_temp = 0; // C* -static int16_t battery_min_temp = 0; // C* -static uint16_t battery_energy_buffer = 0; // kWh -static uint16_t battery_energy_to_charge_complete = 0; // kWh -static uint16_t battery_expected_energy_remaining = 0; // kWh -static uint8_t battery_full_charge_complete = 0; // kWh -static uint8_t battery_fully_charged = 0; // kWh -static uint16_t battery_ideal_energy_remaining = 0; // kWh -static uint16_t battery_nominal_energy_remaining = 0; // kWh -static uint16_t battery_nominal_full_pack_energy = 600; // Kwh -static uint16_t battery_beginning_of_life = 600; // kWh -static uint16_t battery_charge_time_remaining = 0; // Minutes +static uint16_t battery_volts = 0; // V +static int16_t battery_amps = 0; // A +static uint16_t battery_raw_amps = 0; // A +static int16_t battery_max_temp = 0; // C* +static int16_t battery_min_temp = 0; // C* +static uint16_t battery_energy_buffer = 0; // kWh +static uint16_t battery_energy_buffer_m1 = 0; // kWh +static uint16_t battery_energy_to_charge_complete = 0; // kWh +static uint16_t battery_energy_to_charge_complete_m1 = 0; // kWh +static uint16_t battery_expected_energy_remaining = 0; // kWh +static uint16_t battery_expected_energy_remaining_m1 = 0; // kWh +static uint8_t battery_full_charge_complete = 0; // kWh +static uint8_t battery_fully_charged = 0; // kWh +static uint16_t battery_ideal_energy_remaining = 0; // kWh +static uint16_t battery_ideal_energy_remaining_m0 = 0; // kWh +static uint16_t battery_nominal_energy_remaining = 0; // kWh +static uint16_t battery_nominal_energy_remaining_m0 = 0; // kWh +static uint16_t battery_nominal_full_pack_energy = 600; // Kwh +static uint16_t battery_nominal_full_pack_energy_m0 = 600; // Kwh +static uint16_t battery_beginning_of_life = 600; // kWh +static uint16_t battery_charge_time_remaining = 0; // Minutes static uint16_t battery_regenerative_limit = 0; static uint16_t battery_discharge_limit = 0; static uint16_t battery_max_heat_park = 0; @@ -143,13 +149,19 @@ static uint16_t battery2_raw_amps = 0; // A static int16_t battery2_max_temp = 0; // C* static int16_t battery2_min_temp = 0; // C* static uint16_t battery2_energy_buffer = 0; +static uint16_t battery2_energy_buffer_m1 = 0; // kWh static uint16_t battery2_energy_to_charge_complete = 0; +static uint16_t battery2_energy_to_charge_complete_m1 = 0; // kWh static uint16_t battery2_expected_energy_remaining = 0; +static uint16_t battery2_expected_energy_remaining_m1 = 0; // kWh static uint8_t battery2_full_charge_complete = 0; static uint8_t battery2_fully_charged = 0; static uint16_t battery2_ideal_energy_remaining = 0; +static uint16_t battery2_ideal_energy_remaining_m0 = 0; // kWh static uint16_t battery2_nominal_energy_remaining = 0; +static uint16_t battery2_nominal_energy_remaining_m0 = 0; // kWh static uint16_t battery2_nominal_full_pack_energy = 600; +static uint16_t battery2_nominal_full_pack_energy_m0 = 600; // Kwh static uint16_t battery2_beginning_of_life = 600; static uint16_t battery2_charge_time_remaining = 0; // Minutes static uint16_t battery2_regenerative_limit = 0; @@ -369,10 +381,15 @@ void update_values_battery() { //This function maps all the values fetched via datalayer_extended.tesla.battery_dcdcHvBusVolt = battery_dcdcHvBusVolt; datalayer_extended.tesla.battery_dcdcLvOutputCurrent = battery_dcdcLvOutputCurrent; datalayer_extended.tesla.battery_nominal_full_pack_energy = battery_nominal_full_pack_energy; + datalayer_extended.tesla.battery_nominal_full_pack_energy_m0 = battery_nominal_full_pack_energy_m0; datalayer_extended.tesla.battery_nominal_energy_remaining = battery_nominal_energy_remaining; + datalayer_extended.tesla.battery_nominal_energy_remaining_m0 = battery_nominal_energy_remaining_m0; datalayer_extended.tesla.battery_ideal_energy_remaining = battery_ideal_energy_remaining; + datalayer_extended.tesla.battery_ideal_energy_remaining_m0 = battery_ideal_energy_remaining_m0; datalayer_extended.tesla.battery_energy_to_charge_complete = battery_energy_to_charge_complete; + datalayer_extended.tesla.battery_energy_to_charge_complete_m1 = battery_energy_to_charge_complete_m1; datalayer_extended.tesla.battery_energy_buffer = battery_energy_buffer; + datalayer_extended.tesla.battery_energy_buffer_m1 = battery_energy_buffer_m1; datalayer_extended.tesla.battery_full_charge_complete = battery_full_charge_complete; datalayer_extended.tesla.battery_total_discharge = battery_total_discharge; datalayer_extended.tesla.battery_total_charge = battery_total_charge; @@ -414,7 +431,7 @@ void update_values_battery() { //This function maps all the values fetched via Serial.print("Real SOC: "); Serial.print(battery_soc_ui / 10.0, 1); print_int_with_units(", Battery voltage: ", battery_volts, "V"); - print_int_with_units(", Battery HV current: ", (battery_amps * 0.1), "A"); + print_int_with_units(", Battery HV current: ", (battery_amps), "A"); // (battery_amps * 0.1) Serial.print(", Fully charged?: "); if (battery_full_charge_complete) Serial.print("YES, "); @@ -461,45 +478,38 @@ void receive_can_battery(CAN_frame rx_frame) { static uint16_t temp = 0; switch (rx_frame.ID) { - case 0x352: + case 0x352: // BMS_energyStatus // newer BMS >2021 mux = (rx_frame.data.u8[0] & 0x02); //BMS_energyStatusIndex M : 0|2@1+ (1,0) [0|0] "" X + if (mux == 0) { + //battery_nominal_full_pack_energy_m0 = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]); //BMS_nominalFullPackEnergy m0 : 16|16@1+ (0.02,0) [0|0] "kWh" X + //battery_nominal_energy_remaining_m0 = ((rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4]); //BMS_nominalEnergyRemaining m0 : 32|16@1+ (0.02,0) [0|0] "kWh" X + //battery_ideal_energy_remaining_m0 = ((rx_frame.data.u8[7] << 8) | rx_frame.data.u8[6]); //BMS_idealEnergyRemaining m0 : 48|16@1+ (0.02,0) [0|0] "kWh" X + } + if (mux == 1) { + //battery_fully_charged = (rx_frame.data.u8[1] & 0x01); //BMS_fullyCharged m1 : 15|1@1+ (1,0) [0|1] "" X + //battery_energy_buffer_m1 = (rx_frame.data.u8[3] | rx_frame.data.u8[2]); //BMS_energyBuffer m1 : 16|16@1+ (0.01,0) [0|0] "kWh" X + //battery_expected_energy_remaining_m1 = (rx_frame.data.u8[5] | rx_frame.data.u8[4]); //BMS_expectedEnergyRemaining m1 : 32|16@1+ (0.02,0) [0|0] "kWh" X + //battery_energy_to_charge_complete_m1 = (rx_frame.data.u8[7] | rx_frame.data.u8[6]); //BMS_energyToChargeComplete m1 : 48|16@1+ (0.02,0) [0|0] "kWh" X + } + if (mux == 2) {} + // Additional information needed on this mux, example frame: 02 26 02 20 02 80 00 00 doesn't change + // older BMS <2021 without mux battery_nominal_full_pack_energy = //BMS_nominalFullPackEnergy : 0|11@1+ (0.1,0) [0|204.6] "KWh" //((_d[1] & (0x07U)) << 8) | (_d[0] & (0xFFU)); - (((rx_frame.data.u8[1] & 0x07) << 8) | (rx_frame.data.u8[0])) * 0.1; //Example 752 (75.2kWh) + (((rx_frame.data.u8[1] & 0x07) << 8) | (rx_frame.data.u8[0])); //Example 752 (75.2kWh) battery_nominal_energy_remaining = //BMS_nominalEnergyRemaining : 11|11@1+ (0.1,0) [0|204.6] "KWh" //((_d[2] & (0x3FU)) << 5) | ((_d[1] >> 3) & (0x1FU)); - (((rx_frame.data.u8[2] & 0x3F) << 5) | ((rx_frame.data.u8[1] & 0x1F) >> 3)) * - 0.1; //Example 1247 * 0.1 = 124.7kWh + (((rx_frame.data.u8[2] & 0x3F) << 5) | ((rx_frame.data.u8[1] & 0x1F) >> 3)); //Example 1247 * 0.1 = 124.7kWh battery_expected_energy_remaining = //BMS_expectedEnergyRemaining : 22|11@1+ (0.1,0) [0|204.6] "KWh"// ((_d[4] & (0x01U)) << 10) | ((_d[3] & (0xFFU)) << 2) | ((_d[2] >> 6) & (0x03U)); - (((rx_frame.data.u8[4] & 0x01) << 10) | (rx_frame.data.u8[3] << 2) | ((rx_frame.data.u8[2] & 0x03) >> 6)) * - 0.1; //Example 622 (62.2kWh) + (((rx_frame.data.u8[4] & 0x01) << 10) | (rx_frame.data.u8[3] << 2) | + ((rx_frame.data.u8[2] & 0x03) >> 6)); //Example 622 (62.2kWh) battery_ideal_energy_remaining = //BMS_idealEnergyRemaining : 33|11@1+ (0.1,0) [0|204.6] "KWh" //((_d[5] & (0x0FU)) << 7) | ((_d[4] >> 1) & (0x7FU)); - (((rx_frame.data.u8[5] & 0x0F) << 7) | ((rx_frame.data.u8[4] & 0x7F) >> 1)) * - 0.1; //Example 311 * 0.1 = 31.1kWh + (((rx_frame.data.u8[5] & 0x0F) << 7) | ((rx_frame.data.u8[4] & 0x7F) >> 1)); //Example 311 * 0.1 = 31.1kWh battery_energy_to_charge_complete = // BMS_energyToChargeComplete : 44|11@1+ (0.1,0) [0|204.6] "KWh"// ((_d[6] & (0x7FU)) << 4) | ((_d[5] >> 4) & (0x0FU)); - (((rx_frame.data.u8[6] & 0x7F) << 4) | ((rx_frame.data.u8[5] & 0x0F) << 4)) * - 0.1; //Example 147 * 0.1 = 14.7kWh + (((rx_frame.data.u8[6] & 0x7F) << 4) | ((rx_frame.data.u8[5] & 0x0F) << 4)); //Example 147 * 0.1 = 14.7kWh battery_energy_buffer = //BMS_energyBuffer : 55|8@1+ (0.1,0) [0|25.4] "KWh"// ((_d[7] & (0x7FU)) << 1) | ((_d[6] >> 7) & (0x01U)); - (((rx_frame.data.u8[7] & 0x7F) << 1) | ((rx_frame.data.u8[6] & 0x01) >> 7)) * 0.1; //Example 1 * 0.1 = 0 + (((rx_frame.data.u8[7] & 0x7F) << 1) | ((rx_frame.data.u8[6] & 0x01) >> 7)); //Example 1 * 0.1 = 0 battery_full_charge_complete = //BMS_fullChargeComplete : 63|1@1+ (1,0) [0|1] ""//((_d[7] >> 7) & (0x01U)); ((rx_frame.data.u8[7] & 0x01) >> 7); - - if (mux == 0) { - battery_nominal_full_pack_energy = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]) * - 0.02; //BMS_nominalFullPackEnergy m0 : 16|16@1+ (0.02,0) [0|0] "kWh" X - battery_nominal_energy_remaining = ((rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4]) * - 0.02; //BMS_nominalEnergyRemaining m0 : 32|16@1+ (0.02,0) [0|0] "kWh" X - battery_ideal_energy_remaining = ((rx_frame.data.u8[7] << 8) | rx_frame.data.u8[6]) * - 0.02; //BMS_idealEnergyRemaining m0 : 48|16@1+ (0.02,0) [0|0] "kWh" X - } - if (mux == 1) { - battery_fully_charged = (rx_frame.data.u8[1] & 0x01); //BMS_fullyCharged m1 : 15|1@1+ (1,0) [0|1] "" X - battery_energy_buffer = (rx_frame.data.u8[3] | rx_frame.data.u8[2]) * - 0.01; //BMS_energyBuffer m1 : 16|16@1+ (0.01,0) [0|0] "kWh" X - battery_expected_energy_remaining = (rx_frame.data.u8[5] | rx_frame.data.u8[4]) * - 0.02; //BMS_expectedEnergyRemaining m1 : 32|16@1+ (0.02,0) [0|0] "kWh" X - battery_energy_to_charge_complete = (rx_frame.data.u8[7] | rx_frame.data.u8[6]) * - 0.02; //BMS_energyToChargeComplete m1 : 48|16@1+ (0.02,0) [0|0] "kWh" X - } break; case 0x20A: //Contactor state //HVP_contactorState: battery_packContNegativeState = (rx_frame.data.u8[0] & 0x07); //0|3@1+ (1,0) [0|7] "" @@ -629,37 +639,38 @@ void receive_can_battery(CAN_frame rx_frame) { } break; case 0x2d2: //BMSVAlimits: - battery_bms_min_voltage = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]) * - 0.1; //0|16@1+ (0.01,0) [0|430] "V" //Example 24148mv * 0.01 = 241.48 V - battery_bms_max_voltage = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]) * - 0.1; //16|16@1+ (0.01,0) [0|430] "V" //Example 40282mv * 0.01 = 402.82 V + battery_bms_min_voltage = + ((rx_frame.data.u8[1] << 8) | + rx_frame.data.u8[0]); //0|16@1+ (0.01,0) [0|430] "V" //Example 24148mv * 0.01 = 241.48 V + battery_bms_max_voltage = + ((rx_frame.data.u8[3] << 8) | + rx_frame.data.u8[2]); //16|16@1+ (0.01,0) [0|430] "V" //Example 40282mv * 0.01 = 402.82 V battery_max_charge_current = (((rx_frame.data.u8[5] & 0x3F) << 8) | rx_frame.data.u8[4]) * 0.1; //32|14@1+ (0.1,0) [0|1638.2] "A" //Example 1301? * 0.1 = 130.1? battery_max_discharge_current = (((rx_frame.data.u8[7] & 0x3F) << 8) | rx_frame.data.u8[6]) * 0.128; //48|14@1+ (0.128,0) [0|2096.9] "A" //Example 430? * 0.128 = 55.4? break; case 0x2b4: //PCS_dcdcRailStatus: - battery_dcdcLvBusVolt = (((rx_frame.data.u8[1] & 0x03) << 8) | rx_frame.data.u8[0]) * - 0.0390625; //0|10@1+ (0.0390625,0) [0|39.9609] "V" - battery_dcdcHvBusVolt = (((rx_frame.data.u8[2] & 0x3F) << 6) | ((rx_frame.data.u8[1] & 0xFC) >> 2)) * - 0.146484; //10|12@1+ (0.146484,0) [0|599.854] "V" + battery_dcdcLvBusVolt = + (((rx_frame.data.u8[1] & 0x03) << 8) | rx_frame.data.u8[0]); //0|10@1+ (0.0390625,0) [0|39.9609] "V" + battery_dcdcHvBusVolt = (((rx_frame.data.u8[2] & 0x3F) << 6) | + ((rx_frame.data.u8[1] & 0xFC) >> 2)); //10|12@1+ (0.146484,0) [0|599.854] "V" battery_dcdcLvOutputCurrent = - (((rx_frame.data.u8[4] & 0x0F) << 8) | rx_frame.data.u8[3]) * 0.1; //24|12@1+ (0.1,0) [0|400] "A" + (((rx_frame.data.u8[4] & 0x0F) << 8) | rx_frame.data.u8[3]); //24|12@1+ (0.1,0) [0|400] "A" break; case 0x292: //BMS_socStatus datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; //We are getting CAN messages from the BMS battery_beginning_of_life = - (((rx_frame.data.u8[6] & 0x03) << 8) | rx_frame.data.u8[5]) * 0.1; //40|10@1+ (0.1,0) [0|102.3] "kWh" - battery_soc_min = - (((rx_frame.data.u8[1] & 0x03) << 8) | rx_frame.data.u8[0]) * 0.1; //0|10@1+ (0.1,0) [0|102.3] "%" - battery_soc_ui = (((rx_frame.data.u8[2] & 0x0F) << 6) | ((rx_frame.data.u8[1] & 0xFC) >> 2)) * - 0.1; //10|10@1+ (0.1,0) [0|102.3] "%" - battery_soc_max = (((rx_frame.data.u8[3] & 0x3F) << 4) | ((rx_frame.data.u8[2] & 0xF0) >> 4)) * - 0.1; //20|10@1+ (0.1,0) [0|102.3] "%" + (((rx_frame.data.u8[6] & 0x03) << 8) | rx_frame.data.u8[5]) * 0.1; //40|10@1+ (0.1,0) [0|102.3] "kWh" + battery_soc_min = (((rx_frame.data.u8[1] & 0x03) << 8) | rx_frame.data.u8[0]); //0|10@1+ (0.1,0) [0|102.3] "%" + battery_soc_ui = + (((rx_frame.data.u8[2] & 0x0F) << 6) | ((rx_frame.data.u8[1] & 0xFC) >> 2)); //10|10@1+ (0.1,0) [0|102.3] "%" + battery_soc_max = + (((rx_frame.data.u8[3] & 0x3F) << 4) | ((rx_frame.data.u8[2] & 0xF0) >> 4)); //20|10@1+ (0.1,0) [0|102.3] "%" battery_soc_ave = - ((rx_frame.data.u8[4] << 2) | ((rx_frame.data.u8[3] & 0xC0) >> 6)) * 0.1; //30|10@1+ (0.1,0) [0|102.3] "%" - battery_battTempPct = (((rx_frame.data.u8[7] & 0x03) << 6) | (rx_frame.data.u8[6] & 0x3F) >> 2) * - 0.4; //50|8@1+ (0.4,0) [0|100] "%" + ((rx_frame.data.u8[4] << 2) | ((rx_frame.data.u8[3] & 0xC0) >> 6)); //30|10@1+ (0.1,0) [0|102.3] "%" + battery_battTempPct = + (((rx_frame.data.u8[7] & 0x03) << 6) | (rx_frame.data.u8[6] & 0x3F) >> 2); //50|8@1+ (0.4,0) [0|100] "%" break; case 0x392: //BMS_packConfig mux = (rx_frame.data.u8[0] & (0xFF)); @@ -668,7 +679,7 @@ void receive_can_battery(CAN_frame rx_frame) { battery_moduleType = (rx_frame.data.u8[1] & (0x07)); //8|3@1+ (1,0) [0|4] "" battery_packMass = (rx_frame.data.u8[2]) + 300; //16|8@1+ (1,300) [342|469] "kg" battery_platformMaxBusVoltage = - (((rx_frame.data.u8[4] & 0x03) << 8) | (rx_frame.data.u8[3])) * 0.1 + 375; //24|10@1+ (0.1,375) [0|0] "V" + (((rx_frame.data.u8[4] & 0x03) << 8) | (rx_frame.data.u8[3])); //24|10@1+ (0.1,375) [0|0] "V" } if (mux == 0) { battery_reservedConfig = (rx_frame.data.u8[1] & (0x1F)); //8|5@1+ (1,0) [0|31] "" @@ -739,48 +750,49 @@ void receive_can_battery2(CAN_frame rx_frame) { static uint16_t temp = 0; switch (rx_frame.ID) { - case 0x352: + case 0x352: // BMS_energyStatus // newer BMS >2021 mux = (rx_frame.data.u8[0] & 0x02); //BMS_energyStatusIndex M : 0|2@1+ (1,0) [0|0] "" X - if (mux == 3) { - battery2_nominal_full_pack_energy = //BMS_nominalFullPackEnergy : 0|11@1+ (0.1,0) [0|204.6] "KWh" //((_d[1] & (0x07U)) << 8) | (_d[0] & (0xFFU)); - (((rx_frame.data.u8[1] & 0x07) << 8) | (rx_frame.data.u8[0])); //Example 752 (75.2kWh) - battery2_nominal_energy_remaining = //BMS_nominalEnergyRemaining : 11|11@1+ (0.1,0) [0|204.6] "KWh" //((_d[2] & (0x3FU)) << 5) | ((_d[1] >> 3) & (0x1FU)); - (((rx_frame.data.u8[2] & 0x3F) << 5) | - ((rx_frame.data.u8[1] & 0x1F) >> 3)); //Example 1247 * 0.1 = 124.7kWh - battery2_expected_energy_remaining = //BMS_expectedEnergyRemaining : 22|11@1+ (0.1,0) [0|204.6] "KWh"// ((_d[4] & (0x01U)) << 10) | ((_d[3] & (0xFFU)) << 2) | ((_d[2] >> 6) & (0x03U)); - (((rx_frame.data.u8[4] & 0x01) << 10) | (rx_frame.data.u8[3] << 2) | - ((rx_frame.data.u8[2] & 0x03) >> 6)); //Example 622 (62.2kWh) - battery2_ideal_energy_remaining = //BMS_idealEnergyRemaining : 33|11@1+ (0.1,0) [0|204.6] "KWh" //((_d[5] & (0x0FU)) << 7) | ((_d[4] >> 1) & (0x7FU)); - (((rx_frame.data.u8[5] & 0x0F) << 7) | ((rx_frame.data.u8[4] & 0x7F) >> 1)); //Example 311 * 0.1 = 31.1kWh - battery2_energy_to_charge_complete = // BMS_energyToChargeComplete : 44|11@1+ (0.1,0) [0|204.6] "KWh"// ((_d[6] & (0x7FU)) << 4) | ((_d[5] >> 4) & (0x0FU)); - (((rx_frame.data.u8[6] & 0x7F) << 4) | ((rx_frame.data.u8[5] & 0x0F) << 4)); //Example 147 * 0.1 = 14.7kWh - battery2_energy_buffer = //BMS_energyBuffer : 55|8@1+ (0.1,0) [0|25.4] "KWh"// ((_d[7] & (0x7FU)) << 1) | ((_d[6] >> 7) & (0x01U)); - (((rx_frame.data.u8[7] & 0x7F) << 1) | ((rx_frame.data.u8[6] & 0x01) >> 7)); //Example 1 * 0.1 = 0 - battery2_full_charge_complete = //BMS_fullChargeComplete : 63|1@1+ (1,0) [0|1] ""//((_d[7] >> 7) & (0x01U)); - ((rx_frame.data.u8[7] & 0x01) >> 7); - } + if (mux == 0) { - battery2_nominal_full_pack_energy = - (rx_frame.data.u8[3] | - rx_frame.data.u8[2]); //SG_ BMS_nominalFullPackEnergy m0 : 16|16@1+ (0.02,0) [0|0] "kWh" X - battery2_nominal_energy_remaining = - (rx_frame.data.u8[5] | - rx_frame.data.u8[4]); //SG_ BMS_nominalEnergyRemaining m0 : 32|16@1+ (0.02,0) [0|0] "kWh" X - battery2_ideal_energy_remaining = - (rx_frame.data.u8[7] | - rx_frame.data.u8[6]); //SG_ BMS_idealEnergyRemaining m0 : 48|16@1+ (0.02,0) [0|0] "kWh" X + battery2_nominal_full_pack_energy_m0 = + ((rx_frame.data.u8[3] << 8) | + rx_frame.data.u8[2]); //BMS_nominalFullPackEnergy m0 : 16|16@1+ (0.02,0) [0|0] "kWh" X + battery2_nominal_energy_remaining_m0 = + ((rx_frame.data.u8[5] << 8) | + rx_frame.data.u8[4]); //BMS_nominalEnergyRemaining m0 : 32|16@1+ (0.02,0) [0|0] "kWh" X + battery2_ideal_energy_remaining_m0 = + ((rx_frame.data.u8[7] << 8) | + rx_frame.data.u8[6]); //BMS_idealEnergyRemaining m0 : 48|16@1+ (0.02,0) [0|0] "kWh" X } if (mux == 1) { - battery2_fully_charged = (rx_frame.data.u8[1] & 0x01); //SG_ BMS_fullyCharged m1 : 15|1@1+ (1,0) [0|1] "" X - battery2_energy_buffer = - (rx_frame.data.u8[3] | rx_frame.data.u8[2]); //SG_ BMS_energyBuffer m1 : 16|16@1+ (0.01,0) [0|0] "kWh" X - battery2_expected_energy_remaining = + battery2_fully_charged = (rx_frame.data.u8[1] & 0x01); //BMS_fullyCharged m1 : 15|1@1+ (1,0) [0|1] "" X + battery2_energy_buffer_m1 = + (rx_frame.data.u8[3] | rx_frame.data.u8[2]); //BMS_energyBuffer m1 : 16|16@1+ (0.01,0) [0|0] "kWh" X + battery2_expected_energy_remaining_m1 = (rx_frame.data.u8[5] | - rx_frame.data.u8[4]); //SG_ BMS_expectedEnergyRemaining m1 : 32|16@1+ (0.02,0) [0|0] "kWh" X - battery2_energy_to_charge_complete = + rx_frame.data.u8[4]); //BMS_expectedEnergyRemaining m1 : 32|16@1+ (0.02,0) [0|0] "kWh" X + battery2_energy_to_charge_complete_m1 = (rx_frame.data.u8[7] | - rx_frame.data.u8[6]); //SG_ BMS_energyToChargeComplete m1 : 48|16@1+ (0.02,0) [0|0] "kWh" X + rx_frame.data.u8[6]); //BMS_energyToChargeComplete m1 : 48|16@1+ (0.02,0) [0|0] "kWh" X } + if (mux == 2) {} + // Additional information needed on this mux, example frame: 02 26 02 20 02 80 00 00 doesn't change + // older BMS <2021 without mux + //battery2_nominal_full_pack_energy = //BMS_nominalFullPackEnergy : 0|11@1+ (0.1,0) [0|204.6] "KWh" //((_d[1] & (0x07U)) << 8) | (_d[0] & (0xFFU)); + (((rx_frame.data.u8[1] & 0x07) << 8) | (rx_frame.data.u8[0])); //Example 752 (75.2kWh) + //battery2_nominal_energy_remaining = //BMS_nominalEnergyRemaining : 11|11@1+ (0.1,0) [0|204.6] "KWh" //((_d[2] & (0x3FU)) << 5) | ((_d[1] >> 3) & (0x1FU)); + (((rx_frame.data.u8[2] & 0x3F) << 5) | ((rx_frame.data.u8[1] & 0x1F) >> 3)); //Example 1247 * 0.1 = 124.7kWh + //battery2_expected_energy_remaining = //BMS_expectedEnergyRemaining : 22|11@1+ (0.1,0) [0|204.6] "KWh"// ((_d[4] & (0x01U)) << 10) | ((_d[3] & (0xFFU)) << 2) | ((_d[2] >> 6) & (0x03U)); + (((rx_frame.data.u8[4] & 0x01) << 10) | (rx_frame.data.u8[3] << 2) | + ((rx_frame.data.u8[2] & 0x03) >> 6)); //Example 622 (62.2kWh) + //battery2_ideal_energy_remaining = //BMS_idealEnergyRemaining : 33|11@1+ (0.1,0) [0|204.6] "KWh" //((_d[5] & (0x0FU)) << 7) | ((_d[4] >> 1) & (0x7FU)); + (((rx_frame.data.u8[5] & 0x0F) << 7) | ((rx_frame.data.u8[4] & 0x7F) >> 1)); //Example 311 * 0.1 = 31.1kWh + //battery2_energy_to_charge_complete = // BMS_energyToChargeComplete : 44|11@1+ (0.1,0) [0|204.6] "KWh"// ((_d[6] & (0x7FU)) << 4) | ((_d[5] >> 4) & (0x0FU)); + (((rx_frame.data.u8[6] & 0x7F) << 4) | ((rx_frame.data.u8[5] & 0x0F) << 4)); //Example 147 * 0.1 = 14.7kWh + //battery2_energy_buffer = //BMS_energyBuffer : 55|8@1+ (0.1,0) [0|25.4] "KWh"// ((_d[7] & (0x7FU)) << 1) | ((_d[6] >> 7) & (0x01U)); + (((rx_frame.data.u8[7] & 0x7F) << 1) | ((rx_frame.data.u8[6] & 0x01) >> 7)); //Example 1 * 0.1 = 0 + //battery2_full_charge_complete = //BMS_fullChargeComplete : 63|1@1+ (1,0) [0|1] ""//((_d[7] >> 7) & (0x01U)); + ((rx_frame.data.u8[7] & 0x01) >> 7); break; case 0x20A: //Contactor state @@ -791,6 +803,22 @@ void receive_can_battery2(CAN_frame rx_frame) { battery2_packCtrsClosingAllowed = (rx_frame.data.u8[4] & 0x08) >> 3; battery2_pyroTestInProgress = (rx_frame.data.u8[4] & 0x20) >> 5; battery2_hvil_status = (rx_frame.data.u8[5] & 0x0F); + //HVP_packCtrsOpenNowRequested : 33|1@1+ (1,0) [0|1] "" + //HVP_packCtrsOpenRequested : 34|1@1+ (1,0) [0|1] "" + //HVP_packCtrsRequestStatus : 30|2@1+ (1,0) [0|2] "" + //HVP_packCtrsResetRequestRequired : 32|1@1+ (1,0) [0|1] "" + //HVP_dcLinkAllowedToEnergize : 36|1@1+ (1,0) [0|1] "" Receiver + //HVP_fcContNegativeAuxOpen : 7|1@1+ (1,0) [0|1] "" Receiver + //HVP_fcContNegativeState : 12|3@1+ (1,0) [0|7] "" Receiver + //HVP_fcContPositiveAuxOpen : 6|1@1+ (1,0) [0|1] "" Receiver + //HVP_fcContPositiveState : 16|3@1+ (1,0) [0|7] "" Receiver + //HVP_fcContactorSetState : 19|4@1+ (1,0) [0|9] "" Receiver + //HVP_fcCtrsClosingAllowed : 29|1@1+ (1,0) [0|1] "" Receiver + //HVP_fcCtrsOpenNowRequested : 27|1@1+ (1,0) [0|1] "" Receiver + //HVP_fcCtrsOpenRequested : 28|1@1+ (1,0) [0|1] "" Receiver + //HVP_fcCtrsRequestStatus : 24|2@1+ (1,0) [0|2] "" Receiver + //HVP_fcCtrsResetRequestRequired : 26|1@1+ (1,0) [0|1] "" Receiver + //HVP_fcLinkAllowedToEnergize : 44|2@1+ (1,0) [0|2] "" Receiver break; case 0x252: //Limits @@ -809,7 +837,7 @@ void receive_can_battery2(CAN_frame rx_frame) { battery2_amps = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]); //Example 65492 (-4.3A) OR 225 (22.5A) battery2_raw_amps = ((rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4]) * -0.05; //Example 10425 * -0.05 = ? battery2_charge_time_remaining = - (((rx_frame.data.u8[7] & 0x0F) << 8) | rx_frame.data.u8[6]) * 0.1; //Example 228 * 0.1 = 22.8min + (((rx_frame.data.u8[7] & 0x0F) << 8) | rx_frame.data.u8[6]); //Example 228 * 0.1 = 22.8min if (battery2_charge_time_remaining == 4095) { battery2_charge_time_remaining = 0; } @@ -818,9 +846,11 @@ void receive_can_battery2(CAN_frame rx_frame) { case 0x3D2: // total charge/discharge kwh battery2_total_discharge = ((rx_frame.data.u8[3] << 24) | (rx_frame.data.u8[2] << 16) | - (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]); + (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]) * + 0.001; battery2_total_charge = ((rx_frame.data.u8[7] << 24) | (rx_frame.data.u8[6] << 16) | (rx_frame.data.u8[5] << 8) | - rx_frame.data.u8[4]); + rx_frame.data.u8[4]) * + 0.001; break; case 0x332: //min/max hist values @@ -882,9 +912,9 @@ void receive_can_battery2(CAN_frame rx_frame) { battery2_bms_max_voltage = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]); //Example 40282mv * 0.01 = 402.82 V battery2_max_charge_current = - (((rx_frame.data.u8[5] & 0x3F) << 8) | rx_frame.data.u8[4]); //Example 1301? * 0.1 = 130.1? + (((rx_frame.data.u8[5] & 0x3F) << 8) | rx_frame.data.u8[4]) * 0.1; //Example 1301? * 0.1 = 130.1? battery2_max_discharge_current = - (((rx_frame.data.u8[7] & 0x3F) << 8) | rx_frame.data.u8[6]); //Example 430? * 0.128 = 55.4? + (((rx_frame.data.u8[7] & 0x3F) << 8) | rx_frame.data.u8[6]) * 0.128; //Example 430? * 0.128 = 55.4? break; case 0x2b4: //PCS_dcdcRailStatus: battery2_dcdcLvBusVolt = (((rx_frame.data.u8[1] & 0x03) << 8) | rx_frame.data.u8[0]); @@ -893,7 +923,7 @@ void receive_can_battery2(CAN_frame rx_frame) { break; case 0x292: //BMS_socStatus datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE; //We are getting CAN messages from the BMS - battery2_beginning_of_life = (((rx_frame.data.u8[6] & 0x03) << 8) | rx_frame.data.u8[5]); + battery2_beginning_of_life = (((rx_frame.data.u8[6] & 0x03) << 8) | rx_frame.data.u8[5]) * 0.1; battery2_soc_min = (((rx_frame.data.u8[1] & 0x03) << 8) | rx_frame.data.u8[0]); battery2_soc_ui = (((rx_frame.data.u8[2] & 0x0F) << 6) | ((rx_frame.data.u8[1] & 0xFC) >> 2)); battery2_soc_max = (((rx_frame.data.u8[3] & 0x3F) << 4) | ((rx_frame.data.u8[2] & 0xF0) >> 4)); diff --git a/Software/src/datalayer/datalayer_extended.h b/Software/src/datalayer/datalayer_extended.h index 46a69bda..cb673593 100644 --- a/Software/src/datalayer/datalayer_extended.h +++ b/Software/src/datalayer/datalayer_extended.h @@ -186,10 +186,15 @@ typedef struct { uint16_t battery_dcdcHvBusVolt = 0; uint16_t battery_dcdcLvOutputCurrent = 0; uint16_t battery_nominal_full_pack_energy = 0; + uint16_t battery_nominal_full_pack_energy_m0 = 0; uint16_t battery_nominal_energy_remaining = 0; + uint16_t battery_nominal_energy_remaining_m0 = 0; uint16_t battery_ideal_energy_remaining = 0; + uint16_t battery_ideal_energy_remaining_m0 = 0; uint16_t battery_energy_to_charge_complete = 0; + uint16_t battery_energy_to_charge_complete_m1 = 0; uint16_t battery_energy_buffer = 0; + uint16_t battery_energy_buffer_m1 = 0; uint16_t battery_full_charge_complete = 0; uint8_t battery_fully_charged = 0; uint16_t battery_total_discharge = 0; diff --git a/Software/src/devboard/webserver/advanced_battery_html.cpp b/Software/src/devboard/webserver/advanced_battery_html.cpp index 1efa3530..7645465d 100644 --- a/Software/src/devboard/webserver/advanced_battery_html.cpp +++ b/Software/src/devboard/webserver/advanced_battery_html.cpp @@ -286,54 +286,79 @@ String advanced_battery_processor(const String& var) { #ifdef TESLA_BATTERY float beginning_of_life = static_cast(datalayer_extended.tesla.battery_beginning_of_life); + float battTempPct = static_cast(datalayer_extended.tesla.battery_battTempPct) * 0.4; + float dcdcLvBusVolt = static_cast(datalayer_extended.tesla.battery_dcdcLvBusVolt) * 0.0390625; + float dcdcHvBusVolt = static_cast(datalayer_extended.tesla.battery_dcdcHvBusVolt) * 0.146484; + float dcdcLvOutputCurrent = static_cast(datalayer_extended.tesla.battery_dcdcLvOutputCurrent) * 0.1; + float nominal_full_pack_energy = + static_cast(datalayer_extended.tesla.battery_nominal_full_pack_energy) * 0.1; + float nominal_full_pack_energy_m0 = + static_cast(datalayer_extended.tesla.battery_nominal_full_pack_energy_m0) * 0.02; + float nominal_energy_remaining = + static_cast(datalayer_extended.tesla.battery_nominal_energy_remaining) * 0.1; + float nominal_energy_remaining_m0 = + static_cast(datalayer_extended.tesla.battery_nominal_energy_remaining_m0) * 0.02; + float ideal_energy_remaining = static_cast(datalayer_extended.tesla.battery_ideal_energy_remaining) * 0.1; + float ideal_energy_remaining_m0 = + static_cast(datalayer_extended.tesla.battery_ideal_energy_remaining_m0) * 0.02; + float energy_to_charge_complete = + static_cast(datalayer_extended.tesla.battery_energy_to_charge_complete) * 0.1; + float energy_to_charge_complete_m1 = + static_cast(datalayer_extended.tesla.battery_energy_to_charge_complete_m1) * 0.02; + float energy_buffer = static_cast(datalayer_extended.tesla.battery_energy_buffer) * 0.1; + float energy_buffer_m1 = static_cast(datalayer_extended.tesla.battery_energy_buffer_m1) * 0.01; + float total_discharge = static_cast(datalayer_extended.tesla.battery_total_discharge); + float total_charge = static_cast(datalayer_extended.tesla.battery_total_charge); + float packMass = static_cast(datalayer_extended.tesla.battery_packMass); + float platformMaxBusVoltage = + static_cast(datalayer_extended.tesla.battery_platformMaxBusVoltage) * 0.1 + 375; + float bms_min_voltage = static_cast(datalayer_extended.tesla.battery_bms_min_voltage) * 0.01 * 2; + float bms_max_voltage = static_cast(datalayer_extended.tesla.battery_bms_max_voltage) * 0.01 * 2; + float max_charge_current = static_cast(datalayer_extended.tesla.battery_max_charge_current); + float max_discharge_current = static_cast(datalayer_extended.tesla.battery_max_discharge_current); + float soc_ave = static_cast(datalayer_extended.tesla.battery_soc_ave) * 0.1; + float soc_max = static_cast(datalayer_extended.tesla.battery_soc_max) * 0.1; + float soc_min = static_cast(datalayer_extended.tesla.battery_soc_min) * 0.1; + float soc_ui = static_cast(datalayer_extended.tesla.battery_soc_ui) * 0.1; + + // Comment what data you would like to dislay, order can be changed. content += "

Battery Beginning of Life: " + String(beginning_of_life) + " kWh

"; - float battTempPct = static_cast(datalayer_extended.tesla.battery_battTempPct); content += "

BattTempPct: " + String(battTempPct) + "

"; - float dcdcLvBusVolt = static_cast(datalayer_extended.tesla.battery_dcdcLvBusVolt); content += "

PCS Lv Bus: " + String(dcdcLvBusVolt) + " V

"; - float dcdcHvBusVolt = static_cast(datalayer_extended.tesla.battery_dcdcHvBusVolt); content += "

PCS Hv Bus: " + String(dcdcHvBusVolt) + " V

"; - float dcdcLvOutputCurrent = static_cast(datalayer_extended.tesla.battery_dcdcLvOutputCurrent); content += "

PCS Lv Output: " + String(dcdcLvOutputCurrent) + " A

"; - float nominal_full_pack_energy = static_cast(datalayer_extended.tesla.battery_nominal_full_pack_energy); - content += "

Nominal Full Pack Energy: " + String(nominal_full_pack_energy) + " kWh

"; - float nominal_energy_remaining = static_cast(datalayer_extended.tesla.battery_nominal_energy_remaining); - content += "

Nominal Energy Remaining: " + String(nominal_energy_remaining) + " kWh

"; - float ideal_energy_remaining = static_cast(datalayer_extended.tesla.battery_ideal_energy_remaining); - content += "

Ideal Energy Remaining: " + String(ideal_energy_remaining) + " kWh

"; - float energy_to_charge_complete = static_cast(datalayer_extended.tesla.battery_energy_to_charge_complete); - content += "

Energy to Charge Complete: " + String(energy_to_charge_complete) + " kWh

"; - float energy_buffer = static_cast(datalayer_extended.tesla.battery_energy_buffer); - content += "

Energy Buffer: " + String(energy_buffer) + " kWh

"; + + //if using older BMS <2021 and comment 0x352 without MUX + //content += "

Nominal Full Pack Energy: " + String(nominal_full_pack_energy) + " kWh

"; + //content += "

Nominal Energy Remaining: " + String(nominal_energy_remaining) + " kWh

"; + //content += "

Ideal Energy Remaining: " + String(ideal_energy_remaining) + " kWh

"; + //content += "

Energy to Charge Complete: " + String(energy_to_charge_complete) + " kWh

"; + //content += "

Energy Buffer: " + String(energy_buffer) + " kWh

"; + + //if using newer BMS >2021 and comment 0x352 with MUX + content += "

Nominal Full Pack Energy m0: " + String(nominal_full_pack_energy_m0) + " kWh

"; + content += "

Nominal Energy Remaining m0: " + String(nominal_energy_remaining_m0) + " kWh

"; + content += "

Ideal Energy Remaining m0: " + String(ideal_energy_remaining_m0) + " kWh

"; + content += "

Energy to Charge Complete m1: " + String(energy_to_charge_complete_m1) + " kWh

"; + content += "

Energy Buffer m1: " + String(energy_buffer_m1) + " kWh

"; + content += "

packConfigMultiplexer: " + String(datalayer_extended.tesla.battery_packConfigMultiplexer) + "

"; content += "

moduleType: " + String(datalayer_extended.tesla.battery_moduleType) + "

"; content += "

reserveConfig: " + String(datalayer_extended.tesla.battery_reservedConfig) + "

"; - content += "

Full Charge Complete: " + String(datalayer_extended.tesla.battery_full_charge_complete) + - "

"; // no float needed - float total_discharge = static_cast(datalayer_extended.tesla.battery_total_discharge); + content += "

Full Charge Complete: " + String(datalayer_extended.tesla.battery_full_charge_complete) + "

"; content += "

Total Discharge: " + String(total_discharge) + " kWh

"; - float total_charge = static_cast(datalayer_extended.tesla.battery_total_charge); content += "

Total Charge: " + String(total_charge) + " kWh

"; - float packMass = static_cast(datalayer_extended.tesla.battery_packMass); content += "

Battery Pack Mass: " + String(packMass) + " KG

"; - float platformMaxBusVoltage = static_cast(datalayer_extended.tesla.battery_platformMaxBusVoltage); content += "

Platform Max Bus Voltage: " + String(platformMaxBusVoltage) + " V

"; - float bms_min_voltage = static_cast(datalayer_extended.tesla.battery_bms_min_voltage); content += "

BMS Min Voltage: " + String(bms_min_voltage) + " V

"; - float bms_max_voltage = static_cast(datalayer_extended.tesla.battery_bms_max_voltage); content += "

BMS Max Voltage: " + String(bms_max_voltage) + " V

"; - float max_charge_current = static_cast(datalayer_extended.tesla.battery_max_charge_current); content += "

Max Charge Current: " + String(max_charge_current) + " A

"; - float max_discharge_current = static_cast(datalayer_extended.tesla.battery_max_discharge_current); content += "

Max Discharge Current: " + String(max_discharge_current) + " A

"; - float soc_ave = static_cast(datalayer_extended.tesla.battery_soc_ave); content += "

Battery SOC Ave: " + String(soc_ave) + "

"; - float soc_max = static_cast(datalayer_extended.tesla.battery_soc_max); content += "

Battery SOC Max: " + String(soc_max) + "

"; - float soc_min = static_cast(datalayer_extended.tesla.battery_soc_min); content += "

Battery SOC Min: " + String(soc_min) + "

"; - float soc_ui = static_cast(datalayer_extended.tesla.battery_soc_ui); content += "

Battery SOC UI: " + String(soc_ui) + "

"; + static const char* contactorText[] = {"UNKNOWN(0)", "OPEN", "CLOSING", "BLOCKED", "OPENING", "CLOSED", "UNKNOWN(6)", "WELDED", "POS_CL", "NEG_CL", "UNKNOWN(10)", "UNKNOWN(11)", "UNKNOWN(12)"}; From 95981d8365a53eab3aa56be8bab6f65655127f54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Sat, 23 Nov 2024 23:59:18 +0200 Subject: [PATCH 192/210] Refactor check_interconnect --- Software/Software.ino | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/Software/Software.ino b/Software/Software.ino index 3d800713..c49b72f4 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -743,16 +743,18 @@ void check_interconnect_available() { return; // Both voltage values need to be available to start check } - if (abs(datalayer.battery.status.voltage_dV - datalayer.battery2.status.voltage_dV) < 30) { // If we are within 3.0V + uint16_t voltage_diff = abs(datalayer.battery.status.voltage_dV - datalayer.battery2.status.voltage_dV); + + if (voltage_diff <= 30) { // If we are within 3.0V between the batteries clear_event(EVENT_VOLTAGE_DIFFERENCE); - if (datalayer.battery.status.bms_status != FAULT) { // Only proceed if we are not in faulted state - datalayer.system.status.battery2_allows_contactor_closing = true; - } else { // If main battery is in fault state, disengage the second battery + if (datalayer.battery.status.bms_status == FAULT) { + // If main battery is in fault state, disengage the second battery datalayer.system.status.battery2_allows_contactor_closing = false; + } else { // If main battery is OK, allow second battery to join + datalayer.system.status.battery2_allows_contactor_closing = true; } - } else { //We are over 3.0V diff - set_event(EVENT_VOLTAGE_DIFFERENCE, - (uint8_t)(abs(datalayer.battery.status.voltage_dV - datalayer.battery2.status.voltage_dV) / 10)); + } else { //Voltage between the two packs is too large + set_event(EVENT_VOLTAGE_DIFFERENCE, (uint8_t)(voltage_diff / 10)); } } #endif //DOUBLE_BATTERY From 491a67c8495474c6f30fd8f72f77c124f4773633 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Sun, 24 Nov 2024 00:31:18 +0200 Subject: [PATCH 193/210] Restore to working version of Kostal RS485 --- Software/src/inverter/KOSTAL-RS485.cpp | 253 +++++++++++++------------ 1 file changed, 134 insertions(+), 119 deletions(-) diff --git a/Software/src/inverter/KOSTAL-RS485.cpp b/Software/src/inverter/KOSTAL-RS485.cpp index 02d8c4a4..7df23ff1 100644 --- a/Software/src/inverter/KOSTAL-RS485.cpp +++ b/Software/src/inverter/KOSTAL-RS485.cpp @@ -27,51 +27,59 @@ union f32b { byte b[4]; }; -uint8_t BATTERY_INFO[40] = { - 0x00, 0xE2, 0xFF, 0x02, 0xFF, 0x29, // Frame header - 0x00, 0x00, 0x80, 0x43, // 256.063 Nominal voltage / 5*51.2=256 first byte 0x01 or 0x04 - 0xE4, 0x70, 0x8A, 0x5C, // Manufacture date (Epoch time) (BYD: GetBatteryInfo this[0x10ac]) - 0xB5, 0x00, 0xD3, 0x00, // Battery Serial number? Modbus register 527 - 0x10b0 - 0x00, 0x00, 0xC8, 0x41, // 0x10b4 - 0xC2, 0x18, // Battery Firmware, modbus register 586 (0x10b8) - 0x00, // Static (BYD: GetBatteryInfo this[0x10ba]) - 0x00, // ? - 0x59, 0x42, // Static (BYD: GetBatteryInfo this[0x10bc]) - 0x00, 0x00, // Static (BYD: GetBatteryInfo this[0x10be]) - 0x00, 0x00, 0x05, 0x00, 0xA0, 0x00, 0x00, 0x00, - 0x4D, // CRC - 0x00}; // - -// values in CyclicData will be overwritten at update_modbus_registers_inverter() - -uint8_t CyclicData[64] = { - 0x00, // First zero byte pointer +uint8_t frame1[40] = {0x06, 0xE2, 0xFF, 0x02, 0xFF, 0x29, // Frame header + 0x01, 0x08, 0x80, 0x43, // 256.063 Nominal voltage / 5*51.2=256 first byte 0x01 or 0x04 + 0xE4, 0x70, 0x8A, 0x5C, // These might be Umin & Unax, Uint16 + 0xB5, 0x02, 0xD3, 0x01, // Battery Serial number? Modbus register 527 + 0x01, 0x05, 0xC8, 0x41, // 25.0024 ? + 0xC2, 0x18, // Battery Firmware, modbus register 586 + 0x01, 0x03, 0x59, 0x42, // 0x00005942 = 54.25 ?? + 0x01, 0x01, 0x01, 0x02, 0x05, 0x02, 0xA0, 0x01, 0x01, 0x02, + 0x4D, // CRC + 0x00}; // + +// values in frame2 will be overwritten at update_modbus_registers_inverter() + +uint8_t frame2[64] = { + 0x0A, // This may also been 0x06, seen at startup when live values not valid, but also occasionally single frames. 0xE2, 0xFF, 0x02, 0xFF, 0x29, // frame Header - 0x1D, 0x5A, 0x85, 0x43, // Current Voltage (float) Modbus register 216, Bytes 6-9 - 0x00, 0x00, 0x8D, 0x43, // Max Voltage (2 byte float), Bytes 12-13 - 0x00, 0x00, 0xAC, 0x41, // BAttery Temperature (2 byte float) Modbus register 214, Bytes 16-17 - 0x00, 0x00, 0x00, - 0x00, // Peak Current (1s period?), Bytes 18-21 - Communication fault seen with some values (>10A?) - 0x00, 0x00, 0x00, - 0x00, // Avg current (1s period?), Bytes 22-25 - Communication fault seen with some values (>10A?) - 0x00, 0x00, 0x48, 0x42, // Max discharge current (2 byte float), Bit 26-29, + + 0x1D, 0x5A, 0x85, 0x43, // Current Voltage (float) Modbus register 216, Bytes 6-9 + 0x01, 0x03, // Unknown, 0x03 seen also 0x0F, 0x07, might hava something to do with current + 0x8D, 0x43, // Max Voltage (2 byte float), Bytes 12-13 + 0x01, 0x03, 0xAC, 0x41, // BAttery Temperature (2 byte float) Modbus register 214, Bytes 16-17 + 0x01, 0x01, 0x01, + 0x01, // Peak Current (1s period?), Bytes 18-21 - Communication fault seen with some values (>10A?) + 0x01, 0x01, 0x01, + 0x01, // Avg current (1s period?), Bytes 22-25 - Communication fault seen with some values (>10A?) + + 0x01, 0x03, 0x48, 0x42, // Max discharge current (2 byte float), Bit 26-29, // Sunspec: ADisChaMax - 0x00, 0x00, 0xC8, 0x41, // Battery gross capacity, Ah (2 byte float) , Bytes 30-33, Modbus 512 - 0x00, 0x00, 0xA0, 0x41, // Max charge current (2 byte float) Bit 36-37, ZERO WHEN SOC=100 - // Sunspec: AChaMax + + 0x01, 0x03, // Unknown + 0xC8, 0x41, // Battery gross capacity, Ah (2 byte float) , Bytes 30-33, Modbus 512 + + 0x01, // Unknown + 0x16, // This seems to have something to do with cell temperatures + + 0xA0, 0x41, // Max charge current (2 byte float) Bit 36-37, ZERO WHEN SOC=100 + // Sunspec: AChaMax + 0xCD, 0xCC, 0xB4, 0x41, // MaxCellTemp (4 byte float) Bit 38-41 - 0x00, 0x00, 0xA4, 0x41, // MinCellTemp (4 byte float) Bit 42-45 + 0x01, 0x0C, 0xA4, 0x41, // MinCellTemp (4 byte float) Bit 42-45 + 0xA4, 0x70, 0x55, 0x40, // MaxCellVolt (float), Bit 46-49 0x7D, 0x3F, 0x55, 0x40, // MinCellVolt (float), Bit 50-53 - 0xFE, 0x04, // Cycle count, - 0x00, // Byte 56 - 0x00, // When SOC=100 Byte57=0x40, at startup 0x03 (about 7 times), otherwise 0x02 - 0x64, // SOC , Bit 58 - 0x00, // Unknown, - 0x00, // Unknown, - 0x00, // Unknown, - 0x00, // CRC (inverted sum of bytes 1-62 + 0xC0), Bit 62 + 0xFE, // Cylce count , Bit 54 + 0x04, // Cycle count? , Bit 55 + 0x01, // Byte 56 + 0x40, // When SOC=100 Byte57=0x40, at startup 0x03 (about 7 times), otherwise 0x02 + 0x64, // SOC , Bit 58 + 0x01, // Unknown, when byte 57 = 0x03, this 0x02, otherwise 0x01 + 0x01, // Unknown, Seen only 0x01 + 0x02, // Unknown, Mostly 0x02. seen also 0x01 + 0x00, // CRC (inverted sum of bytes 1-62 + 0xC0), Bit 62 0x00}; // FE 04 01 40 xx 01 01 02 yy (fully charged) @@ -85,7 +93,9 @@ uint8_t frame3[9] = { }; uint8_t frame4[8] = {0x07, 0xE3, 0xFF, 0x02, 0xFF, 0x29, 0xF4, 0x00}; + uint8_t frameB1[10] = {0x07, 0x63, 0xFF, 0x02, 0xFF, 0x29, 0x5E, 0x02, 0x16, 0x00}; +uint8_t frameB1b[8] = {0x07, 0xE3, 0xFF, 0x02, 0xFF, 0x29, 0xF4, 0x00}; uint8_t RS485_RXFRAME[10]; @@ -100,6 +110,13 @@ void float2frame(byte* arr, float value, byte framepointer) { arr[framepointer + 3] = g.b[3]; } +void float2frameMSB(byte* arr, float value, byte framepointer) { + f32b g; + g.f = value; + arr[framepointer + 0] = g.b[2]; + arr[framepointer + 1] = g.b[3]; +} + void send_kostal(byte* arr, int alen) { #ifdef DEBUG_KOSTAL_RS485_DATA Serial.print("TX: "); @@ -115,25 +132,12 @@ void send_kostal(byte* arr, int alen) { Serial2.write(arr, alen); } -void scramble_null_bytes(byte* lfc, int len) { - int last_null_byte = 0; - for (int i = 0; i < len; i++) { - if (lfc[i] == '\0') { - lfc[last_null_byte] = (byte)(i - last_null_byte); - last_null_byte = i; - } - } -} - -byte calculate_kostal_crc(byte* lfc, int len) { +byte calculate_longframe_crc(byte* lfc, int lastbyte) { unsigned int sum = 0; - if (lfc[0] != 0) { - printf("WARNING: first byte should be 0, but is 0x%02x\n", lfc[0]); - } - for (int i = 1; i < len; i++) { + for (int i = 0; i < lastbyte; ++i) { sum += lfc[i]; } - return (byte)(-sum & 0xff); + return ((byte) ~(sum + 0xc0) & 0xff); } byte calculate_frame1_crc(byte* lfc, int lastbyte) { @@ -166,47 +170,61 @@ void update_RS485_registers_inverter() { if (datalayer.system.status.battery_allows_contactor_closing & datalayer.system.status.inverter_allows_contactor_closing) { - float2frame(CyclicData, (float)datalayer.battery.status.voltage_dV / 10, 6); // Confirmed OK mapping + float2frame(frame2, (float)datalayer.battery.status.voltage_dV / 10, 6); // Confirmed OK mapping + frame2[0] = 0x0A; } else { - float2frame(CyclicData, 0.0, 6); + frame2[0] = 0x06; + float2frame(frame2, 0.0, 6); } // Set nominal voltage to value between min and max voltage set by battery (Example 400 and 300 results in 350V) nominal_voltage_dV = (((datalayer.battery.info.max_design_voltage_dV - datalayer.battery.info.min_design_voltage_dV) / 2) + datalayer.battery.info.min_design_voltage_dV); - float2frame(BATTERY_INFO, (float)nominal_voltage_dV / 10, 6); + float2frameMSB(frame1, (float)nominal_voltage_dV / 10, 8); - float2frame(CyclicData, (float)datalayer.battery.info.max_design_voltage_dV / 10, 10); + float2frameMSB(frame2, (float)datalayer.battery.info.max_design_voltage_dV / 10, 12); - float2frame(CyclicData, (float)average_temperature_dC / 10, 14); + float2frameMSB(frame2, (float)average_temperature_dC / 10, 16); // Some current values causes communication error, must be resolved, why. - float2frame(CyclicData, (float)datalayer.battery.status.current_dA / 10, - 18); // Peak discharge? current (2 byte float) - float2frame(CyclicData, (float)datalayer.battery.status.current_dA / 10, 22); + // float2frameMSB(frame2, (float)datalayer.battery.status.current_dA / 10, 20); // Peak discharge? current (2 byte float) + // float2frameMSB(frame2, (float)datalayer.battery.status.current_dA / 10, 24); - float2frame(CyclicData, (float)datalayer.battery.status.max_discharge_current_dA / 10, 26); // BAttery capacity Ah + float2frameMSB(frame2, (float)datalayer.battery.status.max_discharge_current_dA / 10, 28); // BAttery capacity Ah - float2frame(CyclicData, (float)datalayer.battery.status.max_discharge_current_dA / 10, 30); + float2frameMSB(frame2, (float)datalayer.battery.status.max_discharge_current_dA / 10, 32); // When SOC = 100%, drop down allowed charge current down. if ((datalayer.battery.status.reported_soc / 100) < 100) { - float2frame(CyclicData, (float)datalayer.battery.status.max_charge_current_dA / 10, 34); + float2frameMSB(frame2, (float)datalayer.battery.status.max_charge_current_dA / 10, 36); + frame2[57] = 0x02; + frame2[59] = 0x01; } else { - float2frame(CyclicData, 0.0, 34); + float2frameMSB(frame2, 0.0, 36); + //frame2[57]=0x40; + frame2[57] = 0x02; + frame2[59] = 0x01; + } + + // On startup, byte 57 seems to be always 0x03 couple of frames,. + if (f2_startup_count < 14) { + frame2[57] = 0x03; + frame2[59] = 0x02; } - float2frame(CyclicData, (float)datalayer.battery.status.temperature_max_dC / 10, 38); - float2frame(CyclicData, (float)datalayer.battery.status.temperature_min_dC / 10, 42); + float2frame(frame2, (float)datalayer.battery.status.temperature_max_dC / 10, 38); + float2frame(frame2, (float)datalayer.battery.status.temperature_min_dC / 10, 42); - float2frame(CyclicData, (float)datalayer.battery.status.cell_max_voltage_mV / 1000, 46); - float2frame(CyclicData, (float)datalayer.battery.status.cell_min_voltage_mV / 1000, 50); + float2frame(frame2, (float)datalayer.battery.status.cell_max_voltage_mV / 1000, 46); + float2frame(frame2, (float)datalayer.battery.status.cell_min_voltage_mV / 1000, 50); - CyclicData[58] = (byte)(datalayer.battery.status.reported_soc / 100); // Confirmed OK mapping + frame2[58] = (byte)(datalayer.battery.status.reported_soc / 100); // Confirmed OK mapping register_content_ok = true; + frame1[38] = calculate_frame1_crc(frame1, 38); + if (incoming_message_counter > 0) { incoming_message_counter--; } @@ -246,7 +264,7 @@ void receive_RS485() // Runs as fast as possible to handle the serial stream if (B1_delay) { if ((currentMillis - B1_last_millis) > INTERVAL_1_S) { - send_kostal(frame4, 8); + send_kostal(frameB1b, 8); B1_delay = false; } } else if (Serial2.available()) { @@ -266,57 +284,54 @@ void receive_RS485() // Runs as fast as possible to handle the serial stream rx_index = 0; if (check_kostal_frame_crc()) { incoming_message_counter = RS485_HEALTHY; + bool headerA = true; + bool headerB = true; + for (uint8_t i = 0; i < 5; i++) { + if (RS485_RXFRAME[i + 1] != KOSTAL_FRAMEHEADER[i]) { + headerA = false; + } + if (RS485_RXFRAME[i + 1] != KOSTAL_FRAMEHEADER2[i]) { + headerB = false; + } + } + + // "frame B1", maybe reset request, seen after battery power on/partial data + if (headerB && (RS485_RXFRAME[6] == 0x5E) && (RS485_RXFRAME[7] == 0xFF)) { + send_kostal(frameB1, 10); + B1_delay = true; + B1_last_millis = currentMillis; + } + + // "frame B1", maybe reset request, seen after battery power on/partial data + if (headerB && (RS485_RXFRAME[6] == 0x5E) && (RS485_RXFRAME[7] == 0x04)) { + send_kostal(frame4, 8); + // This needs more reverse engineering, disabled... + } - if (RS485_RXFRAME[1] == 'c') { - if (RS485_RXFRAME[6] == 0x47) { - // Set time function - Do nothing. - send_kostal(frame4, 8); // ACK + if (headerA && (RS485_RXFRAME[6] == 0x4A) && (RS485_RXFRAME[7] == 0x08)) { // "frame 1" + send_kostal(frame1, 40); + if (!startupMillis) { + startupMillis = currentMillis; } - if (RS485_RXFRAME[6] == 0x5E) { - // Set State function - if (RS485_RXFRAME[7] == 0x01) { - // State X - } else if (RS485_RXFRAME[7] == 0x04) { - // INVALID - } else { - // State Y - } - send_kostal(frame4, 8); // ACK + } + if (headerA && (RS485_RXFRAME[6] == 0x4A) && (RS485_RXFRAME[7] == 0x04)) { // "frame 2" + update_values_battery(); + update_RS485_registers_inverter(); + if (f2_startup_count < 15) { + f2_startup_count++; } - } else if (RS485_RXFRAME[1] == 'b') { - if (RS485_RXFRAME[6] == 0x50) { - //Reverse polarity, do nothing - } else { - int code = RS485_RXFRAME[6] + RS485_RXFRAME[7] * 0x100; - if (code == 0x44a) { - //Send cyclic data - update_values_battery(); - update_RS485_registers_inverter(); - if (f2_startup_count < 15) { - f2_startup_count++; - } - byte tmpframe[64]; //copy values to prevent data manipulation during rewrite/crc calculation - memcpy(tmpframe, CyclicData, 64); - tmpframe[62] = calculate_kostal_crc(tmpframe, 62); - scramble_null_bytes(tmpframe, 64); - send_kostal(tmpframe, 64); - } - if (code == 0x84a) { - //Send battery info - byte tmpframe[40]; //copy values to prevent data manipulation during rewrite/crc calculation - memcpy(tmpframe, BATTERY_INFO, 40); - tmpframe[38] = calculate_kostal_crc(tmpframe, 38); - scramble_null_bytes(tmpframe, 40); - send_kostal(tmpframe, 40); - if (!startupMillis) { - startupMillis = currentMillis; - } - } - if (code == 0x353) { - //Send battery error - send_kostal(frame3, 9); + byte tmpframe[64]; //copy values to prevent data manipulation during rewrite/crc calculation + memcpy(tmpframe, frame2, 64); + for (int i = 1; i < 63; i++) { + if (tmpframe[i] == 0x00) { + tmpframe[i] = 0x01; } } + tmpframe[62] = calculate_longframe_crc(tmpframe, 62); + send_kostal(tmpframe, 64); + } + if (headerA && (RS485_RXFRAME[6] == 0x53) && (RS485_RXFRAME[7] == 0x03)) { // "frame 3" + send_kostal(frame3, 9); } } } From fd4ea496f98016662edb968559bfb151a5d08244 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Sun, 24 Nov 2024 20:02:58 +0200 Subject: [PATCH 194/210] Swap raw and dash SOC --- Software/src/battery/BMW-I3-BATTERY.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Software/src/battery/BMW-I3-BATTERY.cpp b/Software/src/battery/BMW-I3-BATTERY.cpp index b15cb31e..e43eaa5a 100644 --- a/Software/src/battery/BMW-I3-BATTERY.cpp +++ b/Software/src/battery/BMW-I3-BATTERY.cpp @@ -487,8 +487,8 @@ void update_values_battery() { //This function maps all the values fetched via } // Update webserver datalayer - datalayer_extended.bmwi3.SOC_raw = (battery_display_SOC * 50); - datalayer_extended.bmwi3.SOC_dash = (battery_HVBatt_SOC * 10); + datalayer_extended.bmwi3.SOC_raw = (battery_HVBatt_SOC * 10); + datalayer_extended.bmwi3.SOC_dash = (battery_display_SOC * 50); datalayer_extended.bmwi3.SOC_OBD2 = battery_soc; datalayer_extended.bmwi3.ST_iso_ext = battery_status_error_isolation_external_Bordnetz; datalayer_extended.bmwi3.ST_iso_int = battery_status_error_isolation_internal_Bordnetz; From eb7c3545777d1c27d5b43940ed3d2ebe9118c962 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Mon, 25 Nov 2024 10:42:14 +0200 Subject: [PATCH 195/210] Add insulation reading + generation enum --- Software/src/battery/NISSAN-LEAF-BATTERY.cpp | 1 + Software/src/datalayer/datalayer_extended.h | 3 +++ Software/src/devboard/webserver/advanced_battery_html.cpp | 4 +++- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Software/src/battery/NISSAN-LEAF-BATTERY.cpp b/Software/src/battery/NISSAN-LEAF-BATTERY.cpp index 21267422..9a9f347e 100644 --- a/Software/src/battery/NISSAN-LEAF-BATTERY.cpp +++ b/Software/src/battery/NISSAN-LEAF-BATTERY.cpp @@ -331,6 +331,7 @@ void update_values_battery() { /* This function maps all the values fetched via datalayer_extended.nissanleaf.ChargePowerLimit = battery_Charge_Power_Limit; datalayer_extended.nissanleaf.MaxPowerForCharger = battery_MAX_POWER_FOR_CHARGER; datalayer_extended.nissanleaf.Interlock = battery_Interlock; + datalayer_extended.nissanleaf.Insulation = battery_insulation; datalayer_extended.nissanleaf.RelayCutRequest = battery_Relay_Cut_Request; datalayer_extended.nissanleaf.FailsafeStatus = battery_Failsafe_Status; datalayer_extended.nissanleaf.Full = battery_Full_CHARGE_flag; diff --git a/Software/src/datalayer/datalayer_extended.h b/Software/src/datalayer/datalayer_extended.h index d4fd2444..7d31fcaa 100644 --- a/Software/src/datalayer/datalayer_extended.h +++ b/Software/src/datalayer/datalayer_extended.h @@ -199,6 +199,9 @@ typedef struct { /** bool */ /** Interlock status */ bool Interlock = false; + /** int16_t */ + /** Insulation resistance, most likely kOhm */ + uint16_t Insulation = 0; /** uint8_t */ /** battery_FAIL status */ uint8_t RelayCutRequest = 0; diff --git a/Software/src/devboard/webserver/advanced_battery_html.cpp b/Software/src/devboard/webserver/advanced_battery_html.cpp index 659bcda8..054a1270 100644 --- a/Software/src/devboard/webserver/advanced_battery_html.cpp +++ b/Software/src/devboard/webserver/advanced_battery_html.cpp @@ -319,11 +319,13 @@ String advanced_battery_processor(const String& var) { #endif #ifdef NISSAN_LEAF_BATTERY - content += "

LEAF generation: " + String(datalayer_extended.nissanleaf.LEAF_gen) + "

"; + static const char* LEAFgen[] = {"ZE0", "AZE0", "ZE1"}; + content += "

LEAF generation: " + String(LEAFgen[datalayer_extended.nissanleaf.LEAF_gen]) + "

"; content += "

GIDS: " + String(datalayer_extended.nissanleaf.GIDS) + "

"; content += "

Regen kW: " + String(datalayer_extended.nissanleaf.ChargePowerLimit) + "

"; content += "

Charge kW: " + String(datalayer_extended.nissanleaf.MaxPowerForCharger) + "

"; content += "

Interlock: " + String(datalayer_extended.nissanleaf.Interlock) + "

"; + content += "

Insulation: " + String(datalayer_extended.nissanleaf.Insulation) + "

"; content += "

Relay cut request: " + String(datalayer_extended.nissanleaf.RelayCutRequest) + "

"; content += "

Failsafe status: " + String(datalayer_extended.nissanleaf.FailsafeStatus) + "

"; content += "

Fully charged: " + String(datalayer_extended.nissanleaf.Full) + "

"; From 2c54366b5776cc7f6c8e8206758a84f5ed835c57 Mon Sep 17 00:00:00 2001 From: amarofarinha <151563493+amarofarinha@users.noreply.github.com> Date: Mon, 25 Nov 2024 10:03:23 +0000 Subject: [PATCH 196/210] bugfix - Scaled Remaining Capacity underflows --- Software/Software.ino | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Software/Software.ino b/Software/Software.ino index 2074f191..dcf0d752 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -901,8 +901,13 @@ void update_calculated_values() { calc_max_capacity = (datalayer.battery.status.remaining_capacity_Wh * 10000 / datalayer.battery.status.real_soc); calc_reserved_capacity = calc_max_capacity * datalayer.battery.settings.min_percentage / 10000; // remove % capacity reserved in min_percentage to total_capacity_Wh - datalayer.battery.status.reported_remaining_capacity_Wh = - datalayer.battery.status.remaining_capacity_Wh - calc_reserved_capacity; + if (datalayer.battery.status.remaining_capacity_Wh > calc_reserved_capacity) { + datalayer.battery.status.reported_remaining_capacity_Wh = + datalayer.battery.status.remaining_capacity_Wh - calc_reserved_capacity; + } else { + datalayer.battery.status.reported_remaining_capacity_Wh = 0; + } + } else { datalayer.battery.status.reported_remaining_capacity_Wh = datalayer.battery.status.remaining_capacity_Wh; } From 198c5a63f40cce94765b8b710f9b0ceb9791b241 Mon Sep 17 00:00:00 2001 From: amarofarinha <151563493+amarofarinha@users.noreply.github.com> Date: Mon, 25 Nov 2024 10:13:05 +0000 Subject: [PATCH 197/210] include DOUBLE_BATTERY setup in the fix --- Software/Software.ino | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Software/Software.ino b/Software/Software.ino index dcf0d752..dd0a545a 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -923,8 +923,12 @@ void update_calculated_values() { (datalayer.battery2.status.remaining_capacity_Wh * 10000 / datalayer.battery2.status.real_soc); calc_reserved_capacity = calc_max_capacity * datalayer.battery2.settings.min_percentage / 10000; // remove % capacity reserved in min_percentage to total_capacity_Wh - datalayer.battery2.status.reported_remaining_capacity_Wh = - datalayer.battery2.status.remaining_capacity_Wh - calc_reserved_capacity; + if (datalayer.battery2.status.remaining_capacity_Wh > calc_reserved_capacity) { + datalayer.battery2.status.reported_remaining_capacity_Wh = + datalayer.battery2.status.remaining_capacity_Wh - calc_reserved_capacity; + } else { + datalayer.battery2.status.reported_remaining_capacity_Wh = 0; + } } else { datalayer.battery2.status.reported_remaining_capacity_Wh = datalayer.battery2.status.remaining_capacity_Wh; } From be257a1658227d8520de5077e19e7e12f5036c41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Mon, 25 Nov 2024 12:47:52 +0200 Subject: [PATCH 198/210] Update version number for release --- Software/Software.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Software/Software.ino b/Software/Software.ino index dd0a545a..118d181b 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -53,7 +53,7 @@ Preferences settings; // Store user settings // The current software version, shown on webserver -const char* version_number = "7.8.dev"; +const char* version_number = "7.8.0"; // Interval settings uint16_t intervalUpdateValues = INTERVAL_1_S; // Interval at which to update inverter values / Modbus registers From 69664b902b87b2df5ddf3d96c436b802c25dad34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Mon, 25 Nov 2024 13:01:24 +0200 Subject: [PATCH 199/210] Update version number to signal dev --- Software/Software.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Software/Software.ino b/Software/Software.ino index 118d181b..d88ef46b 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -53,7 +53,7 @@ Preferences settings; // Store user settings // The current software version, shown on webserver -const char* version_number = "7.8.0"; +const char* version_number = "7.9.dev"; // Interval settings uint16_t intervalUpdateValues = INTERVAL_1_S; // Interval at which to update inverter values / Modbus registers From b7bc1da2033364057c911b9b8d236edb9f7e9c27 Mon Sep 17 00:00:00 2001 From: josiahhiggs <79869367+josiahhiggs@users.noreply.github.com> Date: Tue, 26 Nov 2024 17:41:38 +1300 Subject: [PATCH 200/210] Update TESLA-BATTERY.cpp --- Software/src/battery/TESLA-BATTERY.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Software/src/battery/TESLA-BATTERY.cpp b/Software/src/battery/TESLA-BATTERY.cpp index 41914455..f6db10a8 100644 --- a/Software/src/battery/TESLA-BATTERY.cpp +++ b/Software/src/battery/TESLA-BATTERY.cpp @@ -345,7 +345,7 @@ void update_values_battery() { //This function maps all the values fetched via #ifdef TESLA_MODEL_3Y_BATTERY // Autodetect algoritm for chemistry on 3/Y packs. - // NCM/A batteries have 96s, LFP has 102-106s + // NCM/A batteries have 96s, LFP has 102-108s // Drawback with this check is that it takes 3-5minutes before all cells have been counted! if (datalayer.battery.info.number_of_cells > 101) { datalayer.battery.info.chemistry = battery_chemistry_enum::LFP; @@ -431,7 +431,7 @@ void update_values_battery() { //This function maps all the values fetched via Serial.print("Real SOC: "); Serial.print(battery_soc_ui / 10.0, 1); print_int_with_units(", Battery voltage: ", battery_volts, "V"); - print_int_with_units(", Battery HV current: ", (battery_amps), "A"); // (battery_amps * 0.1) + print_int_with_units(", Battery HV current: ", (battery_amps * 0.1), "A"); Serial.print(", Fully charged?: "); if (battery_full_charge_complete) Serial.print("YES, "); @@ -552,8 +552,9 @@ void receive_can_battery(CAN_frame rx_frame) { battery_volts = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]) * 0.01; //0|16@1+ (0.01,0) [0|655.35] "V" //Example 37030mv * 0.01 = 370V battery_amps = - ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]) * - -0.1; //SmoothBattCurrent : 16|16@1- (-0.1,0) [-3276.7|3276.7] "A" //Example 65492 (-4.3A) OR 225 (22.5A) + ((rx_frame.data.u8[3] << 8) | + rx_frame.data.u8 + [2]); //SmoothBattCurrent : 16|16@1- (-0.1,0) [-3276.7|3276.7] "A"//Example 65492 (-4.3A) OR 225 (22.5A) battery_raw_amps = ((rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4]) * -0.05 + 822; //RawBattCurrent : 32|16@1- (-0.05,822) [-1138.35|2138.4] "A" //Example 10425 * -0.05 = ? From 4232da8aec8e06d5ecf34d77fe0ffe0f8e3f697f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Wed, 27 Nov 2024 20:00:16 +0200 Subject: [PATCH 201/210] Feature: Add initial version of CAN logger (#639) * Add initial version of CAN logger --- Software/Software.ino | 49 ++++++++++++++-- Software/src/datalayer/datalayer.h | 5 ++ .../webserver/advanced_battery_html.cpp | 4 ++ .../devboard/webserver/can_logging_html.cpp | 58 +++++++++++++++++++ .../src/devboard/webserver/can_logging_html.h | 16 +++++ .../devboard/webserver/cellmonitor_html.cpp | 4 ++ .../src/devboard/webserver/events_html.cpp | 2 + .../src/devboard/webserver/settings_html.cpp | 4 ++ Software/src/devboard/webserver/webserver.cpp | 46 +++++++++++++++ Software/src/inverter/BYD-MODBUS.h | 2 +- 10 files changed, 184 insertions(+), 6 deletions(-) create mode 100644 Software/src/devboard/webserver/can_logging_html.cpp create mode 100644 Software/src/devboard/webserver/can_logging_html.h diff --git a/Software/Software.ino b/Software/Software.ino index d88ef46b..721eabde 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -78,7 +78,7 @@ ACAN2517FD canfd(MCP2517_CS, SPI, MCP2517_INT); // ModbusRTU parameters #ifdef MODBUS_INVERTER_SELECTED -#define MB_RTU_NUM_VALUES 30000 +#define MB_RTU_NUM_VALUES 13100 uint16_t mbPV[MB_RTU_NUM_VALUES]; // Process variable memory // Create a ModbusRTU server instance listening on Serial2 with 2000ms timeout ModbusServerRTU MBserver(Serial2, 2000); @@ -623,6 +623,7 @@ void init_equipment_stop_button() { enum frameDirection { MSG_RX, MSG_TX }; //RX = 0, TX = 1 void print_can_frame(CAN_frame frame, frameDirection msgDir); void print_can_frame(CAN_frame frame, frameDirection msgDir) { +#ifdef DEBUG_CAN_DATA // If enabled in user settings, print out the CAN messages via USB uint8_t i = 0; Serial.print(millis()); Serial.print(" "); @@ -637,6 +638,48 @@ void print_can_frame(CAN_frame frame, frameDirection msgDir) { Serial.print(" "); } Serial.println(" "); +#endif //#DEBUG_CAN_DATA + + if (datalayer.system.info.can_logging_active) { // If user clicked on CAN Logging page in webserver, start recording + + char message_string[128]; // Buffer to hold the message string + int offset = 0; // Keeps track of the current position in the buffer + + // Add timestamp + offset += snprintf(message_string + offset, sizeof(message_string) - offset, "%lu ", millis()); + + // Add direction + offset += + snprintf(message_string + offset, sizeof(message_string) - offset, "%s ", (msgDir == MSG_RX) ? "RX" : "TX"); + + // Add ID and DLC + offset += snprintf(message_string + offset, sizeof(message_string) - offset, "%X %u ", frame.ID, frame.DLC); + + // Add data bytes + for (uint8_t i = 0; i < frame.DLC; i++) { + offset += snprintf(message_string + offset, sizeof(message_string) - offset, "%s%X ", + frame.data.u8[i] < 16 ? "0" : "", frame.data.u8[i]); + } + // Add linebreak + offset += snprintf(message_string + offset, sizeof(message_string) - offset, "\n"); + + // Ensure the string is null-terminated + message_string[sizeof(message_string) - 1] = '\0'; + + // Append the message string to the system info structure + size_t current_len = + strnlen(datalayer.system.info.logged_can_messages, sizeof(datalayer.system.info.logged_can_messages)); + size_t available_space = + sizeof(datalayer.system.info.logged_can_messages) - current_len - 1; // Space left for new data + + if (available_space < strlen(message_string) + 1) { + // Not enough space, reset and start from the beginning + current_len = 0; + datalayer.system.info.logged_can_messages[0] = '\0'; + } + + strncat(datalayer.system.info.logged_can_messages, message_string, available_space); + } } #ifdef CAN_FD @@ -1103,9 +1146,7 @@ void transmit_can(CAN_frame* tx_frame, int interface) { if (!allowed_to_send_CAN) { return; } -#ifdef DEBUG_CAN_DATA print_can_frame(*tx_frame, frameDirection(MSG_TX)); -#endif //DEBUG_CAN_DATA switch (interface) { case CAN_NATIVE: @@ -1160,9 +1201,7 @@ void transmit_can(CAN_frame* tx_frame, int interface) { } void receive_can(CAN_frame* rx_frame, int interface) { -#ifdef DEBUG_CAN_DATA print_can_frame(*rx_frame, frameDirection(MSG_RX)); -#endif //DEBUG_CAN_DATA if (interface == can_config.battery) { receive_can_battery(*rx_frame); diff --git a/Software/src/datalayer/datalayer.h b/Software/src/datalayer/datalayer.h index dcf75b7b..81814336 100644 --- a/Software/src/datalayer/datalayer.h +++ b/Software/src/datalayer/datalayer.h @@ -131,6 +131,11 @@ typedef struct { char battery_protocol[64] = {0}; /** array with type of inverter used, for displaying on webserver */ char inverter_protocol[64] = {0}; + /** array with incoming CAN messages, for displaying on webserver */ + char logged_can_messages[15000] = {0}; + /** bool, determines if CAN messages should be logged for webserver */ + bool can_logging_active = false; + } DATALAYER_SYSTEM_INFO_TYPE; typedef struct { diff --git a/Software/src/devboard/webserver/advanced_battery_html.cpp b/Software/src/devboard/webserver/advanced_battery_html.cpp index 9404b36f..b3cba77f 100644 --- a/Software/src/devboard/webserver/advanced_battery_html.cpp +++ b/Software/src/devboard/webserver/advanced_battery_html.cpp @@ -9,6 +9,10 @@ String advanced_battery_processor(const String& var) { //Page format content += ""; content += ""; diff --git a/Software/src/devboard/webserver/can_logging_html.cpp b/Software/src/devboard/webserver/can_logging_html.cpp new file mode 100644 index 00000000..59f03823 --- /dev/null +++ b/Software/src/devboard/webserver/can_logging_html.cpp @@ -0,0 +1,58 @@ +#include "can_logging_html.h" +#include +#include "../../datalayer/datalayer.h" + +String can_logger_processor(const String& var) { + if (var == "X") { + datalayer.system.info.can_logging_active = + true; // Signal to main loop that we should log messages. Disabled by default for performance reasons + String content = ""; + // Page format + content += ""; + content += " "; + content += " "; + content += ""; + + // Start a new block for the CAN messages + content += "
"; + + // Check for messages + if (datalayer.system.info.logged_can_messages[0] == 0) { + content += "CAN logger started! Refresh page to display incoming(RX) and outgoing(TX) messages"; + } else { + // Split the messages using the newline character + String messages = String(datalayer.system.info.logged_can_messages); + int startIndex = 0; + int endIndex = messages.indexOf('\n'); + while (endIndex != -1) { + // Extract a single message and wrap it in a styled div + String singleMessage = messages.substring(startIndex, endIndex); + content += "
" + singleMessage + "
"; + startIndex = endIndex + 1; // Move past the newline character + endIndex = messages.indexOf('\n', startIndex); + } + } + + content += "
"; + + // Add JavaScript for navigation + content += ""; + return content; + } + return String(); +} diff --git a/Software/src/devboard/webserver/can_logging_html.h b/Software/src/devboard/webserver/can_logging_html.h new file mode 100644 index 00000000..f208c3ff --- /dev/null +++ b/Software/src/devboard/webserver/can_logging_html.h @@ -0,0 +1,16 @@ +#ifndef CANLOGGER_H +#define CANLOGGER_H + +#include +#include + +/** + * @brief Replaces placeholder with content section in web page + * + * @param[in] var + * + * @return String + */ +String can_logger_processor(const String& var); + +#endif diff --git a/Software/src/devboard/webserver/cellmonitor_html.cpp b/Software/src/devboard/webserver/cellmonitor_html.cpp index 21a5c1df..f37f5d9d 100644 --- a/Software/src/devboard/webserver/cellmonitor_html.cpp +++ b/Software/src/devboard/webserver/cellmonitor_html.cpp @@ -8,6 +8,10 @@ String cellmonitor_processor(const String& var) { // Page format content += " diff --git a/Software/src/devboard/webserver/settings_html.cpp b/Software/src/devboard/webserver/settings_html.cpp index caac64d4..484e5021 100644 --- a/Software/src/devboard/webserver/settings_html.cpp +++ b/Software/src/devboard/webserver/settings_html.cpp @@ -8,6 +8,10 @@ String settings_processor(const String& var) { //Page format content += ""; content += ""; diff --git a/Software/src/devboard/webserver/webserver.cpp b/Software/src/devboard/webserver/webserver.cpp index 360787d2..227dee8c 100644 --- a/Software/src/devboard/webserver/webserver.cpp +++ b/Software/src/devboard/webserver/webserver.cpp @@ -1,5 +1,6 @@ #include "webserver.h" #include +#include #include "../../datalayer/datalayer.h" #include "../../datalayer/datalayer_extended.h" #include "../../lib/bblanchon-ArduinoJson/ArduinoJson.h" @@ -14,6 +15,7 @@ AsyncWebServer server(80); unsigned long ota_progress_millis = 0; #include "advanced_battery_html.h" +#include "can_logging_html.h" #include "cellmonitor_html.h" #include "events_html.h" #include "index_html.cpp" @@ -56,6 +58,44 @@ void init_webserver() { request->send_P(200, "text/html", index_html, advanced_battery_processor); }); + // Route for going to CAN logging web page + server.on("/canlog", HTTP_GET, [](AsyncWebServerRequest* request) { + request->send_P(200, "text/html", index_html, can_logger_processor); + }); + + // Define the handler to stop logging + server.on("/stop_logging", HTTP_GET, [](AsyncWebServerRequest* request) { + datalayer.system.info.can_logging_active = false; + request->send_P(200, "text/plain", "Logging stopped"); + }); + + // Define the handler to export logs + server.on("/export_logs", HTTP_GET, [](AsyncWebServerRequest* request) { + String logs = String(datalayer.system.info.logged_can_messages); + if (logs.length() == 0) { + logs = "No logs available."; + } + + // Get the current time + time_t now = time(nullptr); + struct tm timeinfo; + localtime_r(&now, &timeinfo); + + // Ensure time retrieval was successful + char filename[32]; + if (strftime(filename, sizeof(filename), "canlog_%H-%M-%S.txt", &timeinfo)) { + // Valid filename created + } else { + // Fallback filename if automatic timestamping failed + strcpy(filename, "battery_emulator_can_log.txt"); + } + + // Use request->send with dynamic headers + AsyncWebServerResponse* response = request->beginResponse(200, "text/plain", logs); + response->addHeader("Content-Disposition", String("attachment; filename=\"") + String(filename) + "\""); + request->send(response); + }); + // Route for going to cellmonitor web page server.on("/cellmonitor", HTTP_GET, [](AsyncWebServerRequest* request) { if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) @@ -443,6 +483,10 @@ String processor(const String& var) { //Page format content += ""; // Start a new block with a specific background color @@ -874,6 +918,7 @@ String processor(const String& var) { content += " "; content += " "; content += " "; + content += " "; content += " "; content += " "; content += ""; @@ -898,6 +943,7 @@ String processor(const String& var) { content += "function Cellmon() { window.location.href = '/cellmonitor'; }"; content += "function Settings() { window.location.href = '/settings'; }"; content += "function Advanced() { window.location.href = '/advanced'; }"; + content += "function CANlog() { window.location.href = '/canlog'; }"; content += "function Events() { window.location.href = '/events'; }"; content += "function askReboot() { if (window.confirm('Are you sure you want to reboot the emulator? NOTE: If " diff --git a/Software/src/inverter/BYD-MODBUS.h b/Software/src/inverter/BYD-MODBUS.h index 487c9784..d1326609 100644 --- a/Software/src/inverter/BYD-MODBUS.h +++ b/Software/src/inverter/BYD-MODBUS.h @@ -4,7 +4,7 @@ #define MODBUS_INVERTER_SELECTED -#define MB_RTU_NUM_VALUES 30000 +#define MB_RTU_NUM_VALUES 13100 #define MAX_POWER 40960 //BYD Modbus specific value extern uint16_t mbPV[MB_RTU_NUM_VALUES]; From 4e66ffcd385b3e50c4b646c7798130b9a3518ef8 Mon Sep 17 00:00:00 2001 From: Ikko Eltociear Ashimine Date: Mon, 2 Dec 2024 01:28:37 +0900 Subject: [PATCH 202/210] docs: update README.md (#649) compability -> compatibility --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f5ad6aa5..e486e2a1 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ This software enables EV battery packs to be used for stationary storage. It ach ## Hardware requirements 📜 This code fits on the LilyGo ESP32 T-CAN485 devboard , see https://github.com/Xinyuan-LilyGO/T-CAN485 -You will also need a complete EV battery. [See the battery compability list on which are supported.](https://github.com/dalathegreat/BYD-Battery-Emulator-For-Gen24/wiki#supported-batteries-list) +You will also need a complete EV battery. [See the battery compatibility list on which are supported.](https://github.com/dalathegreat/BYD-Battery-Emulator-For-Gen24/wiki#supported-batteries-list) Finally, you will need a [compatible hybrid solar inverter](https://github.com/dalathegreat/BYD-Battery-Emulator-For-Gen24/wiki#supported-inverters-list), for example the "Fronius Gen24" or "GoodWe ET" From 0750c6ab5b4bd30f4db4a1e72cc17940275cc6d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Mon, 2 Dec 2024 18:39:57 +0200 Subject: [PATCH 203/210] Feature: Volkswagen MEB platform support (#405) * Add MEB Support Co-authored-by: mvgalen --- .github/workflows/compile-all-batteries.yml | 1 + Software/Software.ino | 27 +- Software/USER_SETTINGS.h | 1 + Software/src/battery/BATTERIES.h | 4 + Software/src/battery/MEB-BATTERY.cpp | 2107 +++++++++++++++++ Software/src/battery/MEB-BATTERY.h | 138 ++ Software/src/datalayer/datalayer_extended.h | 73 + Software/src/devboard/safety/safety.cpp | 4 +- Software/src/devboard/utils/events.cpp | 3 + Software/src/devboard/utils/events.h | 1 + Software/src/devboard/utils/types.h | 2 + .../webserver/advanced_battery_html.cpp | 204 +- .../src/devboard/webserver/events_html.cpp | 2 +- 13 files changed, 2551 insertions(+), 16 deletions(-) create mode 100644 Software/src/battery/MEB-BATTERY.cpp create mode 100644 Software/src/battery/MEB-BATTERY.h diff --git a/.github/workflows/compile-all-batteries.yml b/.github/workflows/compile-all-batteries.yml index 287b75f2..7f95c73d 100644 --- a/.github/workflows/compile-all-batteries.yml +++ b/.github/workflows/compile-all-batteries.yml @@ -43,6 +43,7 @@ jobs: - KIA_HYUNDAI_64_BATTERY - KIA_E_GMP_BATTERY - KIA_HYUNDAI_HYBRID_BATTERY + - MEB_BATTERY - MG_5_BATTERY - NISSAN_LEAF_BATTERY - PYLON_BATTERY diff --git a/Software/Software.ino b/Software/Software.ino index 721eabde..1994978f 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -625,19 +625,20 @@ void print_can_frame(CAN_frame frame, frameDirection msgDir); void print_can_frame(CAN_frame frame, frameDirection msgDir) { #ifdef DEBUG_CAN_DATA // If enabled in user settings, print out the CAN messages via USB uint8_t i = 0; - Serial.print(millis()); - Serial.print(" "); - (msgDir == 0) ? Serial.print("RX ") : Serial.print("TX "); + Serial.print("("); + Serial.print(millis() / 1000.0); + (msgDir == MSG_RX) ? Serial.print(") RX0 ") : Serial.print(") TX1 "); Serial.print(frame.ID, HEX); - Serial.print(" "); + Serial.print(" ["); Serial.print(frame.DLC); - Serial.print(" "); + Serial.print("] "); for (i = 0; i < frame.DLC; i++) { Serial.print(frame.data.u8[i] < 16 ? "0" : ""); Serial.print(frame.data.u8[i], HEX); - Serial.print(" "); + if (i < frame.DLC - 1) + Serial.print(" "); } - Serial.println(" "); + Serial.println(""); #endif //#DEBUG_CAN_DATA if (datalayer.system.info.can_logging_active) { // If user clicked on CAN Logging page in webserver, start recording @@ -686,16 +687,15 @@ void print_can_frame(CAN_frame frame, frameDirection msgDir) { // Functions void receive_canfd() { // This section checks if we have a complete CAN-FD message incoming CANFDMessage frame; - if (canfd.available()) { + int count = 0; + while (canfd.available() && count++ < 16) { canfd.receive(frame); CAN_frame rx_frame; rx_frame.ID = frame.id; rx_frame.ext_ID = frame.ext; rx_frame.DLC = frame.len; - for (uint8_t i = 0; i < rx_frame.DLC && i < 64; i++) { - rx_frame.data.u8[i] = frame.data[i]; - } + memcpy(rx_frame.data.u8, frame.data, MIN(rx_frame.DLC, 64)); //message incoming, pass it on to the handler receive_can(&rx_frame, CAN_ADDON_FD_MCP2518); receive_can(&rx_frame, CANFD_NATIVE); @@ -1180,6 +1180,11 @@ void transmit_can(CAN_frame* tx_frame, int interface) { case CAN_ADDON_FD_MCP2518: { #ifdef CAN_FD CANFDMessage MCP2518Frame; + if (tx_frame->FD) { + MCP2518Frame.type = CANFDMessage::CANFD_WITH_BIT_RATE_SWITCH; + } else { //Classic CAN message + MCP2518Frame.type = CANFDMessage::CAN_DATA; + } MCP2518Frame.id = tx_frame->ID; MCP2518Frame.ext = tx_frame->ext_ID ? CAN_frame_ext : CAN_frame_std; MCP2518Frame.len = tx_frame->DLC; diff --git a/Software/USER_SETTINGS.h b/Software/USER_SETTINGS.h index e9687df5..c846aa09 100644 --- a/Software/USER_SETTINGS.h +++ b/Software/USER_SETTINGS.h @@ -19,6 +19,7 @@ //#define KIA_HYUNDAI_64_BATTERY //#define KIA_E_GMP_BATTERY //#define KIA_HYUNDAI_HYBRID_BATTERY +//#define MEB_BATTERY //#define MG_5_BATTERY //#define NISSAN_LEAF_BATTERY //#define PYLON_BATTERY diff --git a/Software/src/battery/BATTERIES.h b/Software/src/battery/BATTERIES.h index e17d1c06..c2c0bdac 100644 --- a/Software/src/battery/BATTERIES.h +++ b/Software/src/battery/BATTERIES.h @@ -43,6 +43,10 @@ #include "KIA-HYUNDAI-HYBRID-BATTERY.h" #endif +#ifdef MEB_BATTERY +#include "MEB-BATTERY.h" +#endif + #ifdef MG_5_BATTERY #include "MG-5-BATTERY.h" #endif diff --git a/Software/src/battery/MEB-BATTERY.cpp b/Software/src/battery/MEB-BATTERY.cpp new file mode 100644 index 00000000..c6dc918c --- /dev/null +++ b/Software/src/battery/MEB-BATTERY.cpp @@ -0,0 +1,2107 @@ +#include "../include.h" +#ifdef MEB_BATTERY +#include // For std::min and std::max +#include "../datalayer/datalayer.h" +#include "../datalayer/datalayer_extended.h" //For "More battery info" webpage +#include "../devboard/utils/events.h" +#include "MEB-BATTERY.h" + +/* +TODO list +- Get contactors closing +- What CAN messages needs to be sent towards the battery to keep it alive +- Check value mappings on the PID polls +- Check value mappings on the constantly broadcasted messages +- Check all TODO:s in the code +- 0x1B000044 & 1B00008F seems to be missing from logs? (Classic CAN) +*/ + +/* Do not change code below unless you are sure what you are doing */ +static unsigned long previousMillis10ms = 0; // will store last time a 10ms CAN Message was send +static unsigned long previousMillis20ms = 0; // will store last time a 20ms CAN Message was send +static unsigned long previousMillis40ms = 0; // will store last time a 40ms CAN Message was send +static unsigned long previousMillis50ms = 0; // will store last time a 50ms CAN Message was send +static unsigned long previousMillis100ms = 0; // will store last time a 100ms CAN Message was send +static unsigned long previousMillis200ms = 0; // will store last time a 200ms CAN Message was send +static unsigned long previousMillis500ms = 0; // will store last time a 200ms CAN Message was send +static unsigned long previousMillis1s = 0; // will store last time a 1s CAN Message was send + +static bool toggle = false; +static uint8_t counter_1000ms = 0; +static uint8_t counter_200ms = 0; +static uint8_t counter_100ms = 0; +static uint8_t counter_50ms = 0; +static uint8_t counter_40ms = 0; +static uint8_t counter_20ms = 0; +static uint8_t counter_10ms = 0; +static uint8_t counter_040 = 0; +static uint8_t counter_0F7 = 0; +static uint8_t counter_3b5 = 0; + +static uint32_t poll_pid = 0; +static uint32_t pid_reply = 0; +static uint16_t battery_soc_polled = 0; +static uint16_t battery_voltage_polled = 1480; +static int16_t battery_current_polled = 0; +static int16_t battery_max_temp = 600; +static int16_t battery_min_temp = 600; +static uint16_t battery_max_charge_voltage = 0; +static uint16_t battery_min_discharge_voltage = 0; +static uint16_t battery_allowed_charge_power = 0; +static uint16_t battery_allowed_discharge_power = 0; +static uint16_t cellvoltages_polled[108]; +static uint16_t tempval = 0; +static uint8_t BMS_5A2_CRC = 0; +static uint8_t BMS_5CA_CRC = 0; +static uint8_t BMS_0CF_CRC = 0; +static uint8_t BMS_578_CRC = 0; +static uint8_t BMS_0C0_CRC = 0; +static uint8_t BMS_16A954A6_CRC = 0; +static uint8_t BMS_5A2_counter = 0; +static uint8_t BMS_5CA_counter = 0; +static uint8_t BMS_0CF_counter = 0; +static uint8_t BMS_0C0_counter = 0; +static uint8_t BMS_578_counter = 0; +static uint8_t BMS_16A954A6_counter = 0; +static bool BMS_ext_limits_active = + false; //The current current limits of the HV battery are expanded to start the combustion engine / confirmation of the request +static uint8_t BMS_mode = + 0x07; //0: standby; Gates open; Communication active 1: Main contactor closed / HV network activated / normal driving operation +//2: assigned depending on the project (e.g. balancing, extended DC fast charging) //3: external charging +static uint8_t BMS_HVIL_status = 0; //0 init, 1 seated, 2 open, 3 fault +static bool BMS_fault_performance = false; //Error: Battery performance is limited (e.g. due to sensor or fan failure) +static uint16_t BMS_current = 16300; +static bool BMS_fault_emergency_shutdown_crash = + false; //Error: Safety-critical error (crash detection) Battery contactors are already opened / will be opened immediately Signal is read directly by the EMS and initiates an AKS of the PWR and an active discharge of the DC link +static uint32_t BMS_voltage_intermediate = 0; +static uint32_t BMS_voltage = 0; +static uint8_t BMS_status_voltage_free = + 0; //0=Init, 1=BMS intermediate circuit voltage-free (U_Zwkr < 20V), 2=BMS intermediate circuit not voltage-free (U_Zwkr >/= 25V, hysteresis), 3=Error +static bool BMS_OBD_MIL = false; +static uint8_t BMS_error_status = + 0x7; //0 Component_IO, 1 Restricted_CompFkt_Isoerror_I, 2 Restricted_CompFkt_Isoerror_II, 3 Restricted_CompFkt_Interlock, 4 Restricted_CompFkt_SD, 5 Restricted_CompFkt_Performance red, 6 = No component function, 7 = Init +static uint16_t BMS_capacity_ah = 0; +static bool BMS_error_lamp_req = false; +static bool BMS_warning_lamp_req = false; +static uint8_t BMS_Kl30c_Status = 0; // 0 init, 1 closed, 2 open, 3 fault +static bool service_disconnect_switch_missing = false; +static bool pilotline_open = false; +static bool balancing_request = false; +static uint8_t battery_diagnostic = 0; +static uint16_t battery_Wh_left = 0; +static uint16_t battery_Wh_max = 1000; +static uint8_t battery_potential_status = 0; +static uint8_t battery_temperature_warning = 0; +static uint16_t max_discharge_power_watt = 0; +static uint16_t max_discharge_current_amp = 0; +static uint16_t max_charge_power_watt = 0; +static uint16_t max_charge_current_amp = 0; +static uint16_t battery_SOC = 0; +static uint16_t usable_energy_amount_Wh = 0; +static uint8_t status_HV_line = 0; //0 init, 1 No open HV line, 2 open HV line detected, 3 fault +static uint8_t warning_support = 0; +static bool battery_heating_active = false; +static uint16_t power_discharge_percentage = 0; +static uint16_t power_charge_percentage = 0; +static uint16_t actual_battery_voltage = 0; +static uint16_t regen_battery = 0; +static uint16_t energy_extracted_from_battery = 0; +static uint16_t max_fastcharging_current_amp = 0; //maximum DC charging current allowed +static uint8_t BMS_Status_DCLS = + 0; //Status of the voltage monitoring on the DC charging interface. 0 inactive, 1 I_O , 2 N_I_O , 3 active +static uint16_t DC_voltage_DCLS = + 0; //Factor 1, DC voltage of the charging station. Measurement between the DC HV lines. +static uint16_t DC_voltage_chargeport = + 0; //Factor 0.5, Current voltage at the HV battery DC charging terminals; Outlet to the DC charging plug. +static uint8_t BMS_welded_contactors_status = + 0; //0: Init no diagnostic result, 1: no contactor welded, 2: at least 1 contactor welded, 3: Protection status detection error +static bool BMS_error_shutdown_request = + false; // Fault: Fault condition, requires battery contactors to be opened internal battery error; Advance notification of an impending opening of the battery contactors by the BMS +static bool BMS_error_shutdown = + false; // Fault: Fault condition, requires battery contactors to be opened Internal battery error, battery contactors opened without notice by the BMS +static uint16_t power_battery_heating_watt = 0; +static uint16_t power_battery_heating_req_watt = 0; +static uint8_t cooling_request = + 0; //0 = No cooling, 1 = Light cooling, cabin prio, 2= higher cooling, 3 = immediate cooling, 4 = emergency cooling +static uint8_t heating_request = 0; //0 = init, 1= maintain temp, 2=higher demand, 3 = immediate heat demand +static uint8_t balancing_active = false; //0 = init, 1 active, 2 not active +static bool charging_active = false; +static uint16_t max_energy_Wh = 0; +static uint16_t max_charge_percent = 0; +static uint16_t min_charge_percent = 0; +static uint16_t isolation_resistance_kOhm = 0; +static bool battery_heating_installed = false; +static bool error_NT_circuit = false; +static uint8_t pump_1_control = 0; //0x0D not installed, 0x0E init, 0x0F fault +static uint8_t pump_2_control = 0; //0x0D not installed, 0x0E init, 0x0F fault +static uint8_t target_flow_temperature_C = 0; //*0,5 -40 +static uint8_t return_temperature_C = 0; //*0,5 -40 +static uint8_t status_valve_1 = 0; //0 not active, 1 active, 5 not installed, 6 init, 7 fault +static uint8_t status_valve_2 = 0; //0 not active, 1 active, 5 not installed, 6 init, 7 fault +static uint8_t battery_temperature = 0; +static uint8_t temperature_request = + 0; //0 high cooling, 1 medium cooling, 2 low cooling, 3 no temp requirement init, 4 low heating , 5 medium heating, 6 high heating, 7 circulation +static uint16_t performance_index_discharge_peak_temperature_percentage = 0; +static uint16_t performance_index_charge_peak_temperature_percentage = 0; +static uint8_t temperature_status_discharge = + 0; //0 init, 1 temp under optimal, 2 temp optimal, 3 temp over optimal, 7 fault +static uint8_t temperature_status_charge = + 0; //0 init, 1 temp under optimal, 2 temp optimal, 3 temp over optimal, 7 fault +static uint8_t isolation_fault = + 0; //0 init, 1 no iso fault, 2 iso fault threshold1, 3 iso fault threshold2, 4 IWU defective +static uint8_t isolation_status = + 0; // 0 init, 1 = larger threshold1, 2 = smaller threshold1 total, 3 = smaller threshold1 intern, 4 = smaller threshold2 total, 5 = smaller threshold2 intern, 6 = no measurement, 7 = measurement active +static uint8_t actual_temperature_highest_C = 0; //*0,5 -40 +static uint8_t actual_temperature_lowest_C = 0; //*0,5 -40 +static uint16_t actual_cellvoltage_highest_mV = 0; //bias 1000 +static uint16_t actual_cellvoltage_lowest_mV = 0; //bias 1000 +static uint16_t predicted_power_dyn_standard_watt = 0; +static uint8_t predicted_time_dyn_standard_minutes = 0; +static uint8_t mux = 0; +static int8_t celltemperature[56] = {0}; //Temperatures 1-56. Value is 0xFD if sensor not present +static uint16_t cellvoltages[160] = {0}; +static uint16_t duration_discharge_power_watt = 0; +static uint16_t duration_charge_power_watt = 0; +static uint16_t maximum_voltage = 0; +static uint16_t minimum_voltage = 0; +static uint8_t battery_serialnumber[26]; +static uint8_t realtime_overcurrent_monitor = 0; +static uint8_t realtime_CAN_communication_fault = 0; +static uint8_t realtime_overcharge_warning = 0; +static uint8_t realtime_SOC_too_high = 0; +static uint8_t realtime_SOC_too_low = 0; +static uint8_t realtime_SOC_jumping_warning = 0; +static uint8_t realtime_temperature_difference_warning = 0; +static uint8_t realtime_cell_overtemperature_warning = 0; +static uint8_t realtime_cell_undertemperature_warning = 0; +static uint8_t realtime_battery_overvoltage_warning = 0; +static uint8_t realtime_battery_undervoltage_warning = 0; +static uint8_t realtime_cell_overvoltage_warning = 0; +static uint8_t realtime_cell_undervoltage_warning = 0; +static uint8_t realtime_cell_imbalance_warning = 0; +static uint8_t realtime_warning_battery_unathorized = 0; +static bool component_protection_active = false; +static bool shutdown_active = false; +static bool transportation_mode_active = false; +static uint8_t KL15_mode = 0; +static uint8_t bus_knockout_timer = 0; +static uint8_t hybrid_wakeup_reason = 0; +static uint8_t wakeup_type = 0; +static bool instrumentation_cluster_request = false; +static uint8_t seconds = 0; +static uint32_t first_can_msg = 0; +static uint32_t last_can_msg_timestamp = 0; + +#define TIME_YEAR 2024 +#define TIME_MONTH 8 +#define TIME_DAY 20 +#define TIME_HOUR 10 +#define TIME_MINUTE 5 +#define TIME_SECOND 0 + +#define BMS_TARGET_HV_OFF 0 +#define BMS_TARGET_HV_ON 1 +#define BMS_TARGET_AC_CHARGING_EXT 3 //(HS + AC_2 contactors closed) +#define BMS_TARGET_AC_CHARGING 4 //(HS + AC contactors closed) +#define BMS_TARGET_DC_CHARGING 6 //(HS + DC contactors closed) +#define BMS_TARGET_INIT 7 + +#define DC_FASTCHARGE_NO_START_REQUEST 0x00 +#define DC_FASTCHARGE_VEHICLE 0x40 +#define DC_FASTCHARGE_LS1 0x80 +#define DC_FASTCHARGE_LS2 0xC0 + +CAN_frame MEB_POLLING_FRAME = {.FD = true, + .ext_ID = true, + .DLC = 8, + .ID = 0x1C40007B, // SOC 02 8C + .data = {0x03, 0x22, 0x02, 0x8C, 0x55, 0x55, 0x55, 0x55}}; +CAN_frame MEB_ACK_FRAME = {.FD = true, + .ext_ID = true, + .DLC = 8, + .ID = 0x1C40007B, // Ack + .data = {0x30, 0x00, 0x00, 0x55, 0x55, 0x55, 0x55, 0x55}}; +//Messages needed for contactor closing +CAN_frame MEB_040 = {.FD = true, // Airbag + .ext_ID = false, + .DLC = 8, + .ID = 0x040, //Frame5 has HV deactivate request. Needs to be 0x00 + .data = {0x7E, 0x83, 0x00, 0x01, 0x00, 0x00, 0x15, 0x00}}; +CAN_frame MEB_0C0 = { + .FD = true, // EM1 message + .ext_ID = false, + .DLC = 32, + .ID = 0x0C0, //Frame 5-6 and maybe 7-8 important (external voltage at inverter) + .data = {0x77, 0x0A, 0xFE, 0xE7, 0x7F, 0x10, 0x27, 0x00, 0xE0, 0x7F, 0xFF, 0xF3, 0x3F, 0xFF, 0xF3, 0x3F, + 0xFC, 0x0F, 0x00, 0x00, 0xC0, 0xFF, 0xFE, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame MEB_0FC = { + .FD = true, // + .ext_ID = false, + .DLC = 48, + .ID = 0x0FC, //This message contains emergency regen request?(byte19), battery needs to see this message + .data = {0x07, 0x08, 0x00, 0x00, 0x7E, 0x00, 0x40, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFE, 0xFE, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xF4, 0x01, 0x40, 0xFF, 0xEB, 0x7F, 0x0A, 0x88, 0xE3, 0x81, 0xAF, 0x42}}; +CAN_frame MEB_6B2 = {.FD = true, // Diagnostics + .ext_ID = false, + .DLC = 8, + .ID = 0x6B2, + .data = {0x6A, 0xA7, 0x37, 0x80, 0xC9, 0xBD, 0xF6, 0xC2}}; +CAN_frame MEB_17FC007B_poll = {.FD = true, // Non period request + .ext_ID = true, + .DLC = 8, + .ID = 0x17FC007B, + .data = {0x03, 0x22, 0x1E, 0x3D, 0x55, 0x55, 0x55, 0x55}}; +CAN_frame MEB_1A5555A6 = {.FD = true, + .ext_ID = true, + .DLC = 8, + .ID = 0x1A5555A6, + .data = {0x00, 0x00, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame MEB_585 = { + .FD = true, + .ext_ID = false, + .DLC = 8, + .ID = 0x585, + .data = {0xCF, 0x38, 0xAF, 0x5B, 0x25, 0x00, 0x00, 0x00}}; // CF 38 AF 5B 25 00 00 00 in start4.log +// .data = {0xCF, 0x38, 0x20, 0x02, 0x25, 0xF7, 0x30, 0x00}}; // CF 38 AF 5B 25 00 00 00 in start4.log +CAN_frame MEB_5F5 = {.FD = true, + .ext_ID = false, + .DLC = 8, + .ID = 0x5F5, + .data = {0x23, 0x02, 0x39, 0xC0, 0x1B, 0x8B, 0xC8, 0x1B}}; +CAN_frame MEB_641 = {.FD = true, + .ext_ID = false, + .DLC = 8, + .ID = 0x641, + .data = {0x37, 0x18, 0x00, 0x00, 0xF0, 0x00, 0xAA, 0x70}}; +CAN_frame MEB_3C0 = {.FD = true, + .ext_ID = false, + .DLC = 4, + .ID = 0x3C0, + .data = {0x66, 0x00, 0x00, 0x00}}; // Klemmen_status_01 +CAN_frame MEB_0FD = {.FD = true, + .ext_ID = false, + .DLC = 8, + .ID = 0x0FD, //CRC and counter, otherwise static + .data = {0x5F, 0xD0, 0x1F, 0x81, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame MEB_16A954FB = {.FD = true, + .ext_ID = true, + .DLC = 8, + .ID = 0x16A954FB, + .data = {0x00, 0xC0, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame MEB_1A555548 = {.FD = true, + .ext_ID = true, + .DLC = 8, + .ID = 0x1A555548, + .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame MEB_1A55552B = {.FD = true, + .ext_ID = true, + .DLC = 8, + .ID = 0x1A55552B, + .data = {0x00, 0x00, 0x00, 0xA0, 0x02, 0x04, 0x00, 0x30}}; +CAN_frame MEB_569 = {.FD = true, + .ext_ID = false, + .DLC = 8, + .ID = 0x569, //HVEM + .data = {0x00, 0x00, 0x01, 0x3A, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame MEB_16A954B4 = {.FD = true, + .ext_ID = true, + .DLC = 8, + .ID = 0x16A954B4, //eTM + .data = {0xFE, 0xB6, 0x0D, 0x00, 0x00, 0xD5, 0x48, 0xFD}}; +CAN_frame MEB_1B000046 = {.FD = false, // Not FD + .ext_ID = true, + .DLC = 8, + .ID = 0x1B000046, // Klima + .data = {0x00, 0x40, 0x08, 0x01, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame MEB_1B000010 = {.FD = false, // Not FD + .ext_ID = true, + .DLC = 8, + .ID = 0x1B000010, // Gateway + .data = {0x00, 0x50, 0x08, 0x50, 0x01, 0xFF, 0x30, 0x00}}; +CAN_frame MEB_1B0000B9 = {.FD = false, // Not FD + .ext_ID = true, + .DLC = 8, + .ID = 0x1B0000B9, //DC/DC converter + .data = {0x00, 0x40, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame MEB_153 = {.FD = true, + .ext_ID = false, + .DLC = 8, + .ID = 0x153, // Static content + .data = {0x00, 0x00, 0x00, 0xFF, 0xEF, 0xFE, 0xFF, 0xFF}}; +CAN_frame MEB_5E1 = {.FD = true, + .ext_ID = false, + .DLC = 8, + .ID = 0x5E1, // Static content + .data = {0x7F, 0x2A, 0x00, 0x60, 0xFE, 0x00, 0x00, 0x00}}; +CAN_frame MEB_3BE = {.FD = true, + .ext_ID = false, + .DLC = 8, + .ID = 0x3BE, // CRC, otherwise Static content + .data = {0x57, 0x0D, 0x00, 0x00, 0x00, 0x02, 0x04, 0x40}}; +CAN_frame MEB_272 = {.FD = true, //HVLM_14 + .ext_ID = false, + .DLC = 8, + .ID = 0x272, // Static content + .data = {0x00, 0x00, 0x00, 0x00, 0x48, 0x08, 0x00, 0x94}}; +CAN_frame MEB_503 = {.FD = true, //HVK_01 + .ext_ID = false, + .DLC = 8, + .ID = 0x503, // Content varies. Frame1 & 3 has HV req + .data = {0x5D, 0x61, 0x00, 0xFF, 0x7F, 0x80, 0xE3, 0x03}}; +CAN_frame MEB_14C = { + .FD = true, //Motor message + .ext_ID = false, + .DLC = 32, + .ID = 0x14C, //CRC needed, static content otherwise + .data = {0x38, 0x0A, 0xFF, 0x01, 0x01, 0xFF, 0x01, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, + 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0x25, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE}}; +/** Calculate the CRC checksum for VAG CAN Messages + * + * The method used is described in Chapter "7.2.1.2 8-bit 0x2F polynomial CRC Calculation". + * CRC Parameters: + * 0x2F - Polynomial + * 0xFF - Initial Value + * 0xFF - XOR Output + * + * @see https://github.com/crasbe/VW-OnBoard-Charger + * @see https://github.com/colinoflynn/crcbeagle for CRC hacking :) + * @see https://github.com/commaai/opendbc/blob/master/can/common.cc#L110 + * @see https://www.autosar.org/fileadmin/user_upload/standards/classic/4-3/AUTOSAR_SWS_CRCLibrary.pdf + * @see https://web.archive.org/web/20221105210302/https://www.autosar.org/fileadmin/user_upload/standards/classic/4-3/AUTOSAR_SWS_CRCLibrary.pdf + */ +uint8_t vw_crc_calc(uint8_t* inputBytes, uint8_t length, uint16_t address) { + + const uint8_t poly = 0x2F; + const uint8_t xor_output = 0xFF; + // VAG Magic Bytes + const uint8_t MB0040[16] = {0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, + 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40}; + const uint8_t MB00C0[16] = {0x2f, 0x44, 0x72, 0xd3, 0x07, 0xf2, 0x39, 0x09, + 0x8d, 0x6f, 0x57, 0x20, 0x37, 0xf9, 0x9b, 0xfa}; + const uint8_t MB00FC[16] = {0x77, 0x5c, 0xa0, 0x89, 0x4b, 0x7c, 0xbb, 0xd6, + 0x1f, 0x6c, 0x4f, 0xf6, 0x20, 0x2b, 0x43, 0xdd}; + const uint8_t MB00FD[16] = {0xb4, 0xef, 0xf8, 0x49, 0x1e, 0xe5, 0xc2, 0xc0, + 0x97, 0x19, 0x3c, 0xc9, 0xf1, 0x98, 0xd6, 0x61}; + const uint8_t MB0097[16] = {0x3C, 0x54, 0xCF, 0xA3, 0x81, 0x93, 0x0B, 0xC7, + 0x3E, 0xDF, 0x1C, 0xB0, 0xA7, 0x25, 0xD3, 0xD8}; + const uint8_t MB00F7[16] = {0x5F, 0xA0, 0x44, 0xD0, 0x63, 0x59, 0x5B, 0xA2, + 0x68, 0x04, 0x90, 0x87, 0x52, 0x12, 0xB4, 0x9E}; + const uint8_t MB0124[16] = {0x12, 0x7E, 0x34, 0x16, 0x25, 0x8F, 0x8E, 0x35, + 0xBA, 0x7F, 0xEA, 0x59, 0x4C, 0xF0, 0x88, 0x15}; + const uint8_t MB0153[16] = {0x03, 0x13, 0x23, 0x7a, 0x40, 0x51, 0x68, 0xba, + 0xa8, 0xbe, 0x55, 0x02, 0x11, 0x31, 0x76, 0xec}; + const uint8_t MB014C[16] = {0x16, 0x35, 0x59, 0x15, 0x9a, 0x2a, 0x97, 0xb8, + 0x0e, 0x4e, 0x30, 0xcc, 0xb3, 0x07, 0x01, 0xad}; + const uint8_t MB0187[16] = {0x7F, 0xED, 0x17, 0xC2, 0x7C, 0xEB, 0x44, 0x21, + 0x01, 0xFA, 0xDB, 0x15, 0x4A, 0x6B, 0x23, 0x05}; + const uint8_t MB03A6[16] = {0xB6, 0x1C, 0xC1, 0x23, 0x6D, 0x8B, 0x0C, 0x51, + 0x38, 0x32, 0x24, 0xA8, 0x3F, 0x3A, 0xA4, 0x02}; + const uint8_t MB03AF[16] = {0x94, 0x6A, 0xB5, 0x38, 0x8A, 0xB4, 0xAB, 0x27, + 0xCB, 0x22, 0x88, 0xEF, 0xA3, 0xE1, 0xD0, 0xBB}; + const uint8_t MB03BE[16] = {0x1f, 0x28, 0xc6, 0x85, 0xe6, 0xf8, 0xb0, 0x19, + 0x5b, 0x64, 0x35, 0x21, 0xe4, 0xf7, 0x9c, 0x24}; + const uint8_t MB03C0[16] = {0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, + 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3}; + const uint8_t MB0503[16] = {0xed, 0xd6, 0x96, 0x63, 0xa5, 0x12, 0xd5, 0x9a, + 0x1e, 0x0d, 0x24, 0xcd, 0x8c, 0xa6, 0x2f, 0x41}; + const uint8_t MB0578[16] = {0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, + 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48}; + const uint8_t MB05CA[16] = {0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, + 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43}; + const uint8_t MB0641[16] = {0x47, 0x47, 0x47, 0x47, 0x47, 0x47, 0x47, 0x47, + 0x47, 0x47, 0x47, 0x47, 0x47, 0x47, 0x47, 0x47}; + const uint8_t MB06A3[16] = {0xC1, 0x8B, 0x38, 0xA8, 0xA4, 0x27, 0xEB, 0xC8, + 0xEF, 0x05, 0x9A, 0xBB, 0x39, 0xF7, 0x80, 0xA7}; + const uint8_t MB06A4[16] = {0xC7, 0xD8, 0xF1, 0xC4, 0xE3, 0x5E, 0x9A, 0xE2, + 0xA1, 0xCB, 0x02, 0x4F, 0x57, 0x4E, 0x8E, 0xE4}; + const uint8_t MB16A954A6[16] = {0x79, 0xB9, 0x67, 0xAD, 0xD5, 0xF7, 0x70, 0xAA, + 0x44, 0x61, 0x5A, 0xDC, 0x26, 0xB4, 0xD2, 0xC3}; + + uint8_t crc = 0xFF; + uint8_t magicByte = 0x00; + uint8_t counter = inputBytes[1] & 0x0F; // only the low byte of the couner is relevant + + switch (address) { + case 0x0040: // Airbag + magicByte = MB0040[counter]; + break; + case 0x00C0: // + magicByte = MB00C0[counter]; + break; + case 0x00FC: + magicByte = MB00FC[counter]; + break; + case 0x00FD: + magicByte = MB00FD[counter]; + break; + case 0x0097: // ?? + magicByte = MB0097[counter]; + break; + case 0x00F7: // ?? + magicByte = MB00F7[counter]; + break; + case 0x0124: // ?? + magicByte = MB0124[counter]; + break; + case 0x014C: // Motor + magicByte = MB014C[counter]; + break; + case 0x0153: // HYB30 + magicByte = MB0153[counter]; + break; + case 0x0187: // EV_Gearshift "Gear" selection data for EVs with no gearbox + magicByte = MB0187[counter]; + break; + case 0x03A6: // ?? + magicByte = MB03A6[counter]; + break; + case 0x03AF: // ?? + magicByte = MB03AF[counter]; + break; + case 0x03BE: // Motor + magicByte = MB03BE[counter]; + break; + case 0x03C0: // Klemmen status + magicByte = MB03C0[counter]; + break; + case 0x0503: // HVK + magicByte = MB0503[counter]; + break; + case 0x0578: // BMS DC + magicByte = MB0578[counter]; + break; + case 0x05CA: // BMS + magicByte = MB05CA[counter]; + break; + case 0x0641: // Motor + magicByte = MB0641[counter]; + break; + case 0x06A3: // ?? + magicByte = MB06A3[counter]; + break; + case 0x06A4: // ?? + magicByte = MB06A4[counter]; + break; + case 0x16A954A6: + magicByte = MB16A954A6[counter]; + break; + default: // this won't lead to correct CRC checksums + magicByte = 0x00; + break; + } + + for (uint8_t i = 1; i < length + 1; i++) { + // We skip the empty CRC position and start at the timer + // The last element is the VAG magic byte for address 0x187 depending on the counter value. + if (i < length) + crc ^= inputBytes[i]; + else + crc ^= magicByte; + + for (uint8_t j = 0; j < 8; j++) { + if (crc & 0x80) + crc = (crc << 1) ^ poly; + else + crc = (crc << 1); + } + } + + crc ^= xor_output; + + return crc; +} + +void update_values_battery() { //This function maps all the values fetched via CAN to the correct parameters used for modbus + + datalayer.battery.status.real_soc = battery_soc_polled * 10; + //Alternatively use battery_SOC for more precision + + datalayer.battery.status.soh_pptt; + + datalayer.battery.status.voltage_dV = battery_voltage_polled * 2.5; + + datalayer.battery.status.current_dA = (BMS_current / 10) - 1630; + + datalayer.battery.info.total_capacity_Wh = + ((float)datalayer.battery.info.number_of_cells) * 3.6458 * ((float)BMS_capacity_ah) * 0.2; + + datalayer.battery.status.remaining_capacity_Wh = usable_energy_amount_Wh * 5; + //Alternatively use battery_Wh_left + + datalayer.battery.status.max_charge_power_W = (max_charge_power_watt * 100); + + datalayer.battery.status.max_discharge_power_W = (max_discharge_power_watt * 100); + + //Power in watts, Negative = charging batt + datalayer.battery.status.active_power_W = + ((datalayer.battery.status.voltage_dV * datalayer.battery.status.current_dA) / 100); + + datalayer.battery.status.temperature_min_dC = (battery_min_temp - 350) / 2; + + datalayer.battery.status.temperature_max_dC = (battery_max_temp - 350) / 2; + + //Map all cell voltages to the global array + memcpy(datalayer.battery.status.cell_voltages_mV, cellvoltages_polled, 108 * sizeof(uint16_t)); + + // Initialize min and max, lets find which cells are min and max! + uint16_t min_cell_mv_value = std::numeric_limits::max(); + uint16_t max_cell_mv_value = 0; + // Loop to find the min and max while ignoring zero values + for (uint8_t i = 0; i < 108; ++i) { + uint16_t voltage_mV = datalayer.battery.status.cell_voltages_mV[i]; + if (voltage_mV != 0) { // Skip unread values (0) + min_cell_mv_value = std::min(min_cell_mv_value, voltage_mV); + max_cell_mv_value = std::max(max_cell_mv_value, voltage_mV); + } + } + // If all array values are 0, reset min/max to 3700 + if (min_cell_mv_value == std::numeric_limits::max()) { + min_cell_mv_value = 3700; + max_cell_mv_value = 3700; + } + + datalayer.battery.status.cell_min_voltage_mV = min_cell_mv_value; + datalayer.battery.status.cell_max_voltage_mV = max_cell_mv_value; + //TODO, use actual_cellvoltage_lowest_mV instead to save performance + + if (service_disconnect_switch_missing) { + set_event(EVENT_HVIL_FAILURE, 1); + } else { + clear_event(EVENT_HVIL_FAILURE); + } + if (pilotline_open) { + set_event(EVENT_HVIL_FAILURE, 2); + } else { + clear_event(EVENT_HVIL_FAILURE); + } + + // Update webserver datalayer for "More battery info" page + datalayer_extended.meb.SDSW = service_disconnect_switch_missing; + datalayer_extended.meb.pilotline = pilotline_open; + datalayer_extended.meb.transportmode = transportation_mode_active; + datalayer_extended.meb.componentprotection = component_protection_active; + datalayer_extended.meb.shutdown_active = shutdown_active; + datalayer_extended.meb.HVIL = BMS_HVIL_status; + datalayer_extended.meb.BMS_mode = BMS_mode; + datalayer_extended.meb.battery_diagnostic = battery_diagnostic; + datalayer_extended.meb.status_HV_line = status_HV_line; + datalayer_extended.meb.warning_support = warning_support; + datalayer_extended.meb.BMS_status_voltage_free = BMS_status_voltage_free; + datalayer_extended.meb.BMS_OBD_MIL = BMS_OBD_MIL; + datalayer_extended.meb.BMS_error_status = BMS_error_status; + datalayer_extended.meb.BMS_error_lamp_req = BMS_error_lamp_req; + datalayer_extended.meb.BMS_warning_lamp_req = BMS_warning_lamp_req; + datalayer_extended.meb.BMS_Kl30c_Status = BMS_Kl30c_Status; + datalayer_extended.meb.BMS_voltage_intermediate_dV = (BMS_voltage_intermediate - 2000) * 10 / 2; + datalayer_extended.meb.BMS_voltage_dV = BMS_voltage * 10 / 4; + datalayer_extended.meb.isolation_resistance = isolation_resistance_kOhm * 5; + datalayer_extended.meb.battery_heating = battery_heating_active; + datalayer_extended.meb.rt_overcurrent = realtime_overcurrent_monitor; + datalayer_extended.meb.rt_CAN_fault = realtime_CAN_communication_fault; + datalayer_extended.meb.rt_overcharge = realtime_overcharge_warning; + datalayer_extended.meb.rt_SOC_high = realtime_SOC_too_high; + datalayer_extended.meb.rt_SOC_low = realtime_SOC_too_low; + datalayer_extended.meb.rt_SOC_jumping = realtime_SOC_jumping_warning; + datalayer_extended.meb.rt_temp_difference = realtime_temperature_difference_warning; + datalayer_extended.meb.rt_cell_overtemp = realtime_cell_overtemperature_warning; + datalayer_extended.meb.rt_cell_undertemp = realtime_cell_undertemperature_warning; + datalayer_extended.meb.rt_battery_overvolt = realtime_battery_overvoltage_warning; + datalayer_extended.meb.rt_battery_undervol = realtime_battery_undervoltage_warning; + datalayer_extended.meb.rt_cell_overvolt = realtime_cell_overvoltage_warning; + datalayer_extended.meb.rt_cell_undervol = realtime_cell_undervoltage_warning; + datalayer_extended.meb.rt_cell_imbalance = realtime_cell_imbalance_warning; + datalayer_extended.meb.rt_battery_unathorized = realtime_warning_battery_unathorized; +} + +void receive_can_battery(CAN_frame rx_frame) { + last_can_msg_timestamp = millis(); + if (first_can_msg == 0) + first_can_msg = last_can_msg_timestamp; + switch (rx_frame.ID) { + case 0x17F0007B: // BMS 500ms + component_protection_active = (rx_frame.data.u8[0] & 0x01); + shutdown_active = ((rx_frame.data.u8[0] & 0x02) >> 1); + transportation_mode_active = ((rx_frame.data.u8[0] & 0x02) >> 1); + KL15_mode = ((rx_frame.data.u8[0] & 0xF0) >> 4); + //0 = communication only when terminal 15 = ON (no run-on, cannot be woken up) + //1 = communication after terminal 15 = OFF (run-on, cannot be woken up) + //2 = communication when terminal 15 = OFF (run-on, can be woken up) + bus_knockout_timer = rx_frame.data.u8[5]; + hybrid_wakeup_reason = rx_frame.data.u8[6]; //(if several active, lowest wins) + //0 = wakeup cause not known 1 = Bus wakeup2 = KL15 HW 3 = TPA active + break; + case 0x17FE007B: // BMS - Offboard tester diag response + break; + case 0x1B00007B: // BMS - 200ms + wakeup_type = + ((rx_frame.data.u8[1] & 0x10) >> 4); //0 passive, SG has not woken up, 1 active, SG has woken up the network + instrumentation_cluster_request = ((rx_frame.data.u8[1] & 0x40) >> 6); //True/false + break; + case 0x12DD54D0: // BMS Limits 100ms + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + max_discharge_power_watt = + ((rx_frame.data.u8[6] & 0x07) << 10) | (rx_frame.data.u8[5] << 2) | (rx_frame.data.u8[4] & 0xC0) >> 6; //*100 + max_discharge_current_amp = + ((rx_frame.data.u8[3] & 0x01) << 12) | (rx_frame.data.u8[2] << 4) | (rx_frame.data.u8[1] >> 4); //*0.2 + max_charge_power_watt = (rx_frame.data.u8[7] << 5) | (rx_frame.data.u8[6] >> 3); //*100 + max_charge_current_amp = ((rx_frame.data.u8[4] & 0x3F) << 7) | (rx_frame.data.u8[3] >> 1); //*0.2 + break; + case 0x12DD54D1: // BMS 100ms + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + battery_SOC = ((rx_frame.data.u8[3] & 0x0F) << 7) | (rx_frame.data.u8[2] >> 1); //*0.05 + usable_energy_amount_Wh = (rx_frame.data.u8[7] << 8) | rx_frame.data.u8[6]; //*5 + power_discharge_percentage = ((rx_frame.data.u8[4] & 0x3F) << 4) | rx_frame.data.u8[3] >> 4; //*0.2 + power_charge_percentage = (rx_frame.data.u8[5] << 2) | rx_frame.data.u8[4] >> 6; //*0.2 + status_HV_line = ((rx_frame.data.u8[2] & 0x01) << 1) | rx_frame.data.u8[1] >> 7; + warning_support = (rx_frame.data.u8[1] & 0x70) >> 4; + break; + case 0x12DD54D2: // BMS 100ms + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + battery_heating_active = (rx_frame.data.u8[4] & 0x40) >> 6; + heating_request = (rx_frame.data.u8[5] & 0xE0) >> 5; + cooling_request = (rx_frame.data.u8[5] & 0x1C) >> 2; + power_battery_heating_watt = rx_frame.data.u8[6]; + power_battery_heating_req_watt = rx_frame.data.u8[7]; + break; + case 0x1A555550: // BMS 500ms + balancing_active = (rx_frame.data.u8[1] & 0xC0) >> 6; + charging_active = (rx_frame.data.u8[2] & 0x01); + max_energy_Wh = ((rx_frame.data.u8[6] & 0x1F) << 8) | rx_frame.data.u8[5]; //*40 + max_charge_percent = ((rx_frame.data.u8[7] << 3) | rx_frame.data.u8[6] >> 5); //*0.05 + min_charge_percent = ((rx_frame.data.u8[4] << 3) | rx_frame.data.u8[3] >> 5); //*0.05 + isolation_resistance_kOhm = (((rx_frame.data.u8[3] & 0x1F) << 7) | rx_frame.data.u8[2] >> 1); //*5 + break; + case 0x1A555551: // BMS 500ms + battery_heating_installed = (rx_frame.data.u8[1] & 0x20) >> 5; + error_NT_circuit = (rx_frame.data.u8[1] & 0x40) >> 6; + pump_1_control = rx_frame.data.u8[2] & 0x0F; //*10, percent + pump_2_control = (rx_frame.data.u8[2] & 0xF0) >> 4; //*10, percent + status_valve_1 = (rx_frame.data.u8[3] & 0x1C) >> 2; + status_valve_2 = (rx_frame.data.u8[3] & 0xE0) >> 5; + temperature_request = (((rx_frame.data.u8[2] & 0x03) << 1) | rx_frame.data.u8[1] >> 7); + battery_temperature = rx_frame.data.u8[5]; //*0,5 -40 + target_flow_temperature_C = rx_frame.data.u8[6]; //*0,5 -40 + return_temperature_C = rx_frame.data.u8[7]; //*0,5 -40 + break; + case 0x1A5555B2: // BMS + performance_index_discharge_peak_temperature_percentage = + (((rx_frame.data.u8[3] & 0x07) << 6) | rx_frame.data.u8[2] >> 2); //*0.2 + performance_index_charge_peak_temperature_percentage = + (((rx_frame.data.u8[4] & 0x3F) << 3) | rx_frame.data.u8[3] >> 5); //*0.2 + temperature_status_discharge = (rx_frame.data.u8[1] & 0x70) >> 4; + temperature_status_charge = (((rx_frame.data.u8[2] & 0x03) << 1) | rx_frame.data.u8[1] >> 7); + break; + case 0x16A954A6: // BMS + BMS_16A954A6_CRC = rx_frame.data.u8[0]; // Can be used to check CAN signal integrity later on + BMS_16A954A6_counter = (rx_frame.data.u8[1] & 0x0F); // Can be used to check CAN signal integrity later on + isolation_fault = (rx_frame.data.u8[2] & 0xE0) >> 5; + isolation_status = (rx_frame.data.u8[2] & 0x1E) >> 1; + actual_temperature_highest_C = rx_frame.data.u8[3]; //*0,5 -40 + actual_temperature_lowest_C = rx_frame.data.u8[4]; //*0,5 -40 + actual_cellvoltage_highest_mV = (((rx_frame.data.u8[6] & 0x0F) << 8) | rx_frame.data.u8[5]); + actual_cellvoltage_lowest_mV = ((rx_frame.data.u8[7] << 4) | rx_frame.data.u8[6] >> 4); + break; + case 0x16A954F8: // BMS + predicted_power_dyn_standard_watt = ((rx_frame.data.u8[6] << 1) | rx_frame.data.u8[5] >> 7); //*50 + predicted_time_dyn_standard_minutes = rx_frame.data.u8[7]; + break; + case 0x16A954E8: // BMS Temperature and cellvoltages - 180ms + mux = (rx_frame.data.u8[0] & 0x0F); + switch (mux) { + case 0: // Temperatures 1-56. Value is 0xFD if sensor not present + for (uint8_t i = 0; i < 56; i++) { + celltemperature[i] = (rx_frame.data.u8[i + 1] / 2) - 40; + } + break; + case 1: // Cellvoltages 1-42 + cellvoltages[0] = (((rx_frame.data.u8[2] & 0x0F) << 8) | rx_frame.data.u8[1]) + 1000; + cellvoltages[1] = ((rx_frame.data.u8[3] << 4) | (rx_frame.data.u8[2] >> 4)) + 1000; + cellvoltages[2] = (((rx_frame.data.u8[5] & 0x0F) << 8) | rx_frame.data.u8[4]) + 1000; + cellvoltages[3] = ((rx_frame.data.u8[6] << 4) | (rx_frame.data.u8[5] >> 4)) + 1000; + cellvoltages[4] = (((rx_frame.data.u8[8] & 0x0F) << 8) | rx_frame.data.u8[7]) + 1000; + cellvoltages[5] = ((rx_frame.data.u8[9] << 4) | (rx_frame.data.u8[8] >> 4)) + 1000; + cellvoltages[6] = (((rx_frame.data.u8[11] & 0x0F) << 8) | rx_frame.data.u8[10]) + 1000; + cellvoltages[7] = ((rx_frame.data.u8[12] << 4) | (rx_frame.data.u8[11] >> 4)) + 1000; + cellvoltages[8] = (((rx_frame.data.u8[14] & 0x0F) << 8) | rx_frame.data.u8[13]) + 1000; + cellvoltages[9] = ((rx_frame.data.u8[15] << 4) | (rx_frame.data.u8[14] >> 4)) + 1000; + cellvoltages[10] = (((rx_frame.data.u8[17] & 0x0F) << 8) | rx_frame.data.u8[16]) + 1000; + cellvoltages[11] = ((rx_frame.data.u8[18] << 4) | (rx_frame.data.u8[17] >> 4)) + 1000; + cellvoltages[12] = (((rx_frame.data.u8[20] & 0x0F) << 8) | rx_frame.data.u8[19]) + 1000; + cellvoltages[13] = ((rx_frame.data.u8[21] << 4) | (rx_frame.data.u8[20] >> 4)) + 1000; + cellvoltages[14] = (((rx_frame.data.u8[23] & 0x0F) << 8) | rx_frame.data.u8[22]) + 1000; + cellvoltages[15] = ((rx_frame.data.u8[24] << 4) | (rx_frame.data.u8[23] >> 4)) + 1000; + cellvoltages[16] = (((rx_frame.data.u8[26] & 0x0F) << 8) | rx_frame.data.u8[25]) + 1000; + cellvoltages[17] = ((rx_frame.data.u8[27] << 4) | (rx_frame.data.u8[26] >> 4)) + 1000; + cellvoltages[18] = (((rx_frame.data.u8[29] & 0x0F) << 8) | rx_frame.data.u8[28]) + 1000; + cellvoltages[19] = ((rx_frame.data.u8[30] << 4) | (rx_frame.data.u8[29] >> 4)) + 1000; + cellvoltages[20] = (((rx_frame.data.u8[32] & 0x0F) << 8) | rx_frame.data.u8[31]) + 1000; + cellvoltages[21] = ((rx_frame.data.u8[33] << 4) | (rx_frame.data.u8[32] >> 4)) + 1000; + cellvoltages[22] = (((rx_frame.data.u8[35] & 0x0F) << 8) | rx_frame.data.u8[34]) + 1000; + cellvoltages[23] = ((rx_frame.data.u8[36] << 4) | (rx_frame.data.u8[35] >> 4)) + 1000; + cellvoltages[24] = (((rx_frame.data.u8[38] & 0x0F) << 8) | rx_frame.data.u8[37]) + 1000; + cellvoltages[25] = ((rx_frame.data.u8[39] << 4) | (rx_frame.data.u8[38] >> 4)) + 1000; + cellvoltages[26] = (((rx_frame.data.u8[41] & 0x0F) << 8) | rx_frame.data.u8[40]) + 1000; + cellvoltages[27] = ((rx_frame.data.u8[42] << 4) | (rx_frame.data.u8[41] >> 4)) + 1000; + cellvoltages[28] = (((rx_frame.data.u8[44] & 0x0F) << 8) | rx_frame.data.u8[43]) + 1000; + cellvoltages[29] = ((rx_frame.data.u8[45] << 4) | (rx_frame.data.u8[44] >> 4)) + 1000; + cellvoltages[30] = (((rx_frame.data.u8[47] & 0x0F) << 8) | rx_frame.data.u8[46]) + 1000; + cellvoltages[31] = ((rx_frame.data.u8[48] << 4) | (rx_frame.data.u8[47] >> 4)) + 1000; + cellvoltages[32] = (((rx_frame.data.u8[50] & 0x0F) << 8) | rx_frame.data.u8[49]) + 1000; + cellvoltages[33] = ((rx_frame.data.u8[51] << 4) | (rx_frame.data.u8[50] >> 4)) + 1000; + cellvoltages[34] = (((rx_frame.data.u8[53] & 0x0F) << 8) | rx_frame.data.u8[52]) + 1000; + cellvoltages[35] = ((rx_frame.data.u8[54] << 4) | (rx_frame.data.u8[53] >> 4)) + 1000; + cellvoltages[36] = (((rx_frame.data.u8[56] & 0x0F) << 8) | rx_frame.data.u8[55]) + 1000; + cellvoltages[37] = ((rx_frame.data.u8[57] << 4) | (rx_frame.data.u8[56] >> 4)) + 1000; + cellvoltages[38] = (((rx_frame.data.u8[59] & 0x0F) << 8) | rx_frame.data.u8[58]) + 1000; + cellvoltages[39] = ((rx_frame.data.u8[60] << 4) | (rx_frame.data.u8[59] >> 4)) + 1000; + cellvoltages[40] = (((rx_frame.data.u8[62] & 0x0F) << 8) | rx_frame.data.u8[61]) + 1000; + cellvoltages[41] = ((rx_frame.data.u8[63] << 4) | (rx_frame.data.u8[62] >> 4)) + 1000; + break; + case 2: // Cellvoltages 43-84 + cellvoltages[42] = (((rx_frame.data.u8[2] & 0x0F) << 8) | rx_frame.data.u8[1]) + 1000; + cellvoltages[43] = ((rx_frame.data.u8[3] << 4) | (rx_frame.data.u8[2] >> 4)) + 1000; + cellvoltages[44] = (((rx_frame.data.u8[5] & 0x0F) << 8) | rx_frame.data.u8[4]) + 1000; + cellvoltages[45] = ((rx_frame.data.u8[6] << 4) | (rx_frame.data.u8[5] >> 4)) + 1000; + cellvoltages[46] = (((rx_frame.data.u8[8] & 0x0F) << 8) | rx_frame.data.u8[7]) + 1000; + cellvoltages[47] = ((rx_frame.data.u8[9] << 4) | (rx_frame.data.u8[8] >> 4)) + 1000; + cellvoltages[48] = (((rx_frame.data.u8[11] & 0x0F) << 8) | rx_frame.data.u8[10]) + 1000; + cellvoltages[49] = ((rx_frame.data.u8[12] << 4) | (rx_frame.data.u8[11] >> 4)) + 1000; + cellvoltages[50] = (((rx_frame.data.u8[14] & 0x0F) << 8) | rx_frame.data.u8[13]) + 1000; + cellvoltages[51] = ((rx_frame.data.u8[15] << 4) | (rx_frame.data.u8[14] >> 4)) + 1000; + cellvoltages[52] = (((rx_frame.data.u8[17] & 0x0F) << 8) | rx_frame.data.u8[16]) + 1000; + cellvoltages[53] = ((rx_frame.data.u8[18] << 4) | (rx_frame.data.u8[17] >> 4)) + 1000; + cellvoltages[54] = (((rx_frame.data.u8[20] & 0x0F) << 8) | rx_frame.data.u8[19]) + 1000; + cellvoltages[55] = ((rx_frame.data.u8[21] << 4) | (rx_frame.data.u8[20] >> 4)) + 1000; + cellvoltages[56] = (((rx_frame.data.u8[23] & 0x0F) << 8) | rx_frame.data.u8[22]) + 1000; + cellvoltages[57] = ((rx_frame.data.u8[24] << 4) | (rx_frame.data.u8[23] >> 4)) + 1000; + cellvoltages[58] = (((rx_frame.data.u8[26] & 0x0F) << 8) | rx_frame.data.u8[25]) + 1000; + cellvoltages[59] = ((rx_frame.data.u8[27] << 4) | (rx_frame.data.u8[26] >> 4)) + 1000; + cellvoltages[60] = (((rx_frame.data.u8[29] & 0x0F) << 8) | rx_frame.data.u8[28]) + 1000; + cellvoltages[61] = ((rx_frame.data.u8[30] << 4) | (rx_frame.data.u8[29] >> 4)) + 1000; + cellvoltages[62] = (((rx_frame.data.u8[32] & 0x0F) << 8) | rx_frame.data.u8[31]) + 1000; + cellvoltages[63] = ((rx_frame.data.u8[33] << 4) | (rx_frame.data.u8[32] >> 4)) + 1000; + cellvoltages[64] = (((rx_frame.data.u8[35] & 0x0F) << 8) | rx_frame.data.u8[34]) + 1000; + cellvoltages[65] = ((rx_frame.data.u8[36] << 4) | (rx_frame.data.u8[35] >> 4)) + 1000; + cellvoltages[66] = (((rx_frame.data.u8[38] & 0x0F) << 8) | rx_frame.data.u8[37]) + 1000; + cellvoltages[67] = ((rx_frame.data.u8[39] << 4) | (rx_frame.data.u8[38] >> 4)) + 1000; + cellvoltages[68] = (((rx_frame.data.u8[41] & 0x0F) << 8) | rx_frame.data.u8[40]) + 1000; + cellvoltages[69] = ((rx_frame.data.u8[42] << 4) | (rx_frame.data.u8[41] >> 4)) + 1000; + cellvoltages[70] = (((rx_frame.data.u8[44] & 0x0F) << 8) | rx_frame.data.u8[43]) + 1000; + cellvoltages[71] = ((rx_frame.data.u8[45] << 4) | (rx_frame.data.u8[44] >> 4)) + 1000; + cellvoltages[72] = (((rx_frame.data.u8[47] & 0x0F) << 8) | rx_frame.data.u8[46]) + 1000; + cellvoltages[73] = ((rx_frame.data.u8[48] << 4) | (rx_frame.data.u8[47] >> 4)) + 1000; + cellvoltages[74] = (((rx_frame.data.u8[50] & 0x0F) << 8) | rx_frame.data.u8[49]) + 1000; + cellvoltages[75] = ((rx_frame.data.u8[51] << 4) | (rx_frame.data.u8[50] >> 4)) + 1000; + cellvoltages[76] = (((rx_frame.data.u8[53] & 0x0F) << 8) | rx_frame.data.u8[52]) + 1000; + cellvoltages[77] = ((rx_frame.data.u8[54] << 4) | (rx_frame.data.u8[53] >> 4)) + 1000; + cellvoltages[78] = (((rx_frame.data.u8[56] & 0x0F) << 8) | rx_frame.data.u8[55]) + 1000; + cellvoltages[79] = ((rx_frame.data.u8[57] << 4) | (rx_frame.data.u8[56] >> 4)) + 1000; + cellvoltages[80] = (((rx_frame.data.u8[59] & 0x0F) << 8) | rx_frame.data.u8[58]) + 1000; + cellvoltages[81] = ((rx_frame.data.u8[60] << 4) | (rx_frame.data.u8[59] >> 4)) + 1000; + cellvoltages[82] = (((rx_frame.data.u8[62] & 0x0F) << 8) | rx_frame.data.u8[61]) + 1000; + cellvoltages[83] = ((rx_frame.data.u8[63] << 4) | (rx_frame.data.u8[62] >> 4)) + 1000; + break; + case 3: // Cellvoltages 85-126 + cellvoltages[84] = (((rx_frame.data.u8[2] & 0x0F) << 8) | rx_frame.data.u8[1]) + 1000; + cellvoltages[85] = ((rx_frame.data.u8[3] << 4) | (rx_frame.data.u8[2] >> 4)) + 1000; + cellvoltages[86] = (((rx_frame.data.u8[5] & 0x0F) << 8) | rx_frame.data.u8[4]) + 1000; + cellvoltages[87] = ((rx_frame.data.u8[6] << 4) | (rx_frame.data.u8[5] >> 4)) + 1000; + cellvoltages[88] = (((rx_frame.data.u8[8] & 0x0F) << 8) | rx_frame.data.u8[7]) + 1000; + cellvoltages[89] = ((rx_frame.data.u8[9] << 4) | (rx_frame.data.u8[8] >> 4)) + 1000; + cellvoltages[90] = (((rx_frame.data.u8[11] & 0x0F) << 8) | rx_frame.data.u8[10]) + 1000; + cellvoltages[91] = ((rx_frame.data.u8[12] << 4) | (rx_frame.data.u8[11] >> 4)) + 1000; + cellvoltages[92] = (((rx_frame.data.u8[14] & 0x0F) << 8) | rx_frame.data.u8[13]) + 1000; + cellvoltages[93] = ((rx_frame.data.u8[15] << 4) | (rx_frame.data.u8[14] >> 4)) + 1000; + cellvoltages[94] = (((rx_frame.data.u8[17] & 0x0F) << 8) | rx_frame.data.u8[16]) + 1000; + cellvoltages[95] = ((rx_frame.data.u8[18] << 4) | (rx_frame.data.u8[17] >> 4)) + 1000; + cellvoltages[96] = (((rx_frame.data.u8[20] & 0x0F) << 8) | rx_frame.data.u8[19]) + 1000; + cellvoltages[97] = ((rx_frame.data.u8[21] << 4) | (rx_frame.data.u8[20] >> 4)) + 1000; + cellvoltages[98] = (((rx_frame.data.u8[23] & 0x0F) << 8) | rx_frame.data.u8[22]) + 1000; + cellvoltages[99] = ((rx_frame.data.u8[24] << 4) | (rx_frame.data.u8[23] >> 4)) + 1000; + cellvoltages[100] = (((rx_frame.data.u8[26] & 0x0F) << 8) | rx_frame.data.u8[25]) + 1000; + cellvoltages[101] = ((rx_frame.data.u8[27] << 4) | (rx_frame.data.u8[26] >> 4)) + 1000; + cellvoltages[102] = (((rx_frame.data.u8[29] & 0x0F) << 8) | rx_frame.data.u8[28]) + 1000; + cellvoltages[103] = ((rx_frame.data.u8[30] << 4) | (rx_frame.data.u8[29] >> 4)) + 1000; + cellvoltages[104] = (((rx_frame.data.u8[32] & 0x0F) << 8) | rx_frame.data.u8[31]) + 1000; + cellvoltages[105] = ((rx_frame.data.u8[33] << 4) | (rx_frame.data.u8[32] >> 4)) + 1000; + cellvoltages[106] = (((rx_frame.data.u8[35] & 0x0F) << 8) | rx_frame.data.u8[34]) + 1000; + cellvoltages[107] = ((rx_frame.data.u8[36] << 4) | (rx_frame.data.u8[35] >> 4)) + 1000; + cellvoltages[108] = (((rx_frame.data.u8[38] & 0x0F) << 8) | rx_frame.data.u8[37]) + 1000; + cellvoltages[109] = ((rx_frame.data.u8[39] << 4) | (rx_frame.data.u8[38] >> 4)) + 1000; + cellvoltages[110] = (((rx_frame.data.u8[41] & 0x0F) << 8) | rx_frame.data.u8[40]) + 1000; + cellvoltages[111] = ((rx_frame.data.u8[42] << 4) | (rx_frame.data.u8[41] >> 4)) + 1000; + cellvoltages[112] = (((rx_frame.data.u8[44] & 0x0F) << 8) | rx_frame.data.u8[43]) + 1000; + cellvoltages[113] = ((rx_frame.data.u8[45] << 4) | (rx_frame.data.u8[44] >> 4)) + 1000; + cellvoltages[114] = (((rx_frame.data.u8[47] & 0x0F) << 8) | rx_frame.data.u8[46]) + 1000; + cellvoltages[115] = ((rx_frame.data.u8[48] << 4) | (rx_frame.data.u8[47] >> 4)) + 1000; + cellvoltages[116] = (((rx_frame.data.u8[50] & 0x0F) << 8) | rx_frame.data.u8[49]) + 1000; + cellvoltages[117] = ((rx_frame.data.u8[51] << 4) | (rx_frame.data.u8[50] >> 4)) + 1000; + cellvoltages[118] = (((rx_frame.data.u8[53] & 0x0F) << 8) | rx_frame.data.u8[52]) + 1000; + cellvoltages[119] = ((rx_frame.data.u8[54] << 4) | (rx_frame.data.u8[53] >> 4)) + 1000; + cellvoltages[120] = (((rx_frame.data.u8[56] & 0x0F) << 8) | rx_frame.data.u8[55]) + 1000; + cellvoltages[121] = ((rx_frame.data.u8[57] << 4) | (rx_frame.data.u8[56] >> 4)) + 1000; + cellvoltages[122] = (((rx_frame.data.u8[59] & 0x0F) << 8) | rx_frame.data.u8[58]) + 1000; + cellvoltages[123] = ((rx_frame.data.u8[60] << 4) | (rx_frame.data.u8[59] >> 4)) + 1000; + cellvoltages[124] = (((rx_frame.data.u8[62] & 0x0F) << 8) | rx_frame.data.u8[61]) + 1000; + cellvoltages[125] = ((rx_frame.data.u8[63] << 4) | (rx_frame.data.u8[62] >> 4)) + 1000; + break; + case 4: //Cellvoltages 127-160 + cellvoltages[126] = (((rx_frame.data.u8[2] & 0x0F) << 8) | rx_frame.data.u8[1]) + 1000; + cellvoltages[127] = ((rx_frame.data.u8[3] << 4) | (rx_frame.data.u8[2] >> 4)) + 1000; + cellvoltages[128] = (((rx_frame.data.u8[5] & 0x0F) << 8) | rx_frame.data.u8[4]) + 1000; + cellvoltages[129] = ((rx_frame.data.u8[6] << 4) | (rx_frame.data.u8[5] >> 4)) + 1000; + cellvoltages[130] = (((rx_frame.data.u8[8] & 0x0F) << 8) | rx_frame.data.u8[7]) + 1000; + cellvoltages[131] = ((rx_frame.data.u8[9] << 4) | (rx_frame.data.u8[8] >> 4)) + 1000; + cellvoltages[132] = (((rx_frame.data.u8[11] & 0x0F) << 8) | rx_frame.data.u8[10]) + 1000; + cellvoltages[133] = ((rx_frame.data.u8[12] << 4) | (rx_frame.data.u8[11] >> 4)) + 1000; + cellvoltages[134] = (((rx_frame.data.u8[14] & 0x0F) << 8) | rx_frame.data.u8[13]) + 1000; + cellvoltages[135] = ((rx_frame.data.u8[15] << 4) | (rx_frame.data.u8[14] >> 4)) + 1000; + cellvoltages[136] = (((rx_frame.data.u8[17] & 0x0F) << 8) | rx_frame.data.u8[16]) + 1000; + cellvoltages[137] = ((rx_frame.data.u8[18] << 4) | (rx_frame.data.u8[17] >> 4)) + 1000; + cellvoltages[138] = (((rx_frame.data.u8[20] & 0x0F) << 8) | rx_frame.data.u8[19]) + 1000; + cellvoltages[139] = ((rx_frame.data.u8[21] << 4) | (rx_frame.data.u8[20] >> 4)) + 1000; + cellvoltages[140] = (((rx_frame.data.u8[23] & 0x0F) << 8) | rx_frame.data.u8[22]) + 1000; + cellvoltages[141] = ((rx_frame.data.u8[24] << 4) | (rx_frame.data.u8[23] >> 4)) + 1000; + cellvoltages[142] = (((rx_frame.data.u8[26] & 0x0F) << 8) | rx_frame.data.u8[25]) + 1000; + cellvoltages[143] = ((rx_frame.data.u8[27] << 4) | (rx_frame.data.u8[26] >> 4)) + 1000; + cellvoltages[144] = (((rx_frame.data.u8[29] & 0x0F) << 8) | rx_frame.data.u8[28]) + 1000; + cellvoltages[145] = ((rx_frame.data.u8[30] << 4) | (rx_frame.data.u8[29] >> 4)) + 1000; + cellvoltages[146] = (((rx_frame.data.u8[32] & 0x0F) << 8) | rx_frame.data.u8[31]) + 1000; + cellvoltages[147] = ((rx_frame.data.u8[33] << 4) | (rx_frame.data.u8[32] >> 4)) + 1000; + cellvoltages[148] = (((rx_frame.data.u8[35] & 0x0F) << 8) | rx_frame.data.u8[34]) + 1000; + cellvoltages[149] = ((rx_frame.data.u8[36] << 4) | (rx_frame.data.u8[35] >> 4)) + 1000; + cellvoltages[150] = (((rx_frame.data.u8[38] & 0x0F) << 8) | rx_frame.data.u8[37]) + 1000; + cellvoltages[151] = ((rx_frame.data.u8[39] << 4) | (rx_frame.data.u8[38] >> 4)) + 1000; + cellvoltages[152] = (((rx_frame.data.u8[41] & 0x0F) << 8) | rx_frame.data.u8[40]) + 1000; + cellvoltages[153] = ((rx_frame.data.u8[42] << 4) | (rx_frame.data.u8[41] >> 4)) + 1000; + cellvoltages[154] = (((rx_frame.data.u8[44] & 0x0F) << 8) | rx_frame.data.u8[43]) + 1000; + cellvoltages[155] = ((rx_frame.data.u8[45] << 4) | (rx_frame.data.u8[44] >> 4)) + 1000; + cellvoltages[156] = (((rx_frame.data.u8[47] & 0x0F) << 8) | rx_frame.data.u8[46]) + 1000; + cellvoltages[157] = ((rx_frame.data.u8[48] << 4) | (rx_frame.data.u8[47] >> 4)) + 1000; + cellvoltages[158] = (((rx_frame.data.u8[50] & 0x0F) << 8) | rx_frame.data.u8[49]) + 1000; + cellvoltages[159] = ((rx_frame.data.u8[51] << 4) | (rx_frame.data.u8[50] >> 4)) + 1000; + break; + default: //Invalid mux + //TODO: Add corrupted CAN message counter tick? + break; + } + break; + case 0x1C42017B: // BMS - Non-Cyclic, TP_ISO + //hybrid_01_response_fd_data (Whole frame) + break; + case 0x1A5555B0: // BMS 1000ms cyclic + duration_discharge_power_watt = ((rx_frame.data.u8[6] & 0x0F) << 8) | rx_frame.data.u8[5]; + duration_charge_power_watt = (rx_frame.data.u8[7] << 4) | rx_frame.data.u8[6] >> 4; + maximum_voltage = ((rx_frame.data.u8[3] & 0x3F) << 4) | rx_frame.data.u8[2] >> 4; + minimum_voltage = (rx_frame.data.u8[4] << 2) | rx_frame.data.u8[3] >> 6; + break; + case 0x1A5555B1: // BMS 1000ms cyclic + // All realtime_ have same enumeration, 0 = no fault, 1 = error level 1, 2 error level 2, 3 error level 3 + realtime_overcurrent_monitor = ((rx_frame.data.u8[3] & 0x01) << 2) | rx_frame.data.u8[2] >> 6; + realtime_CAN_communication_fault = (rx_frame.data.u8[3] & 0x0E) >> 1; + realtime_overcharge_warning = (rx_frame.data.u8[3] & 0x70) >> 4; + realtime_SOC_too_high = ((rx_frame.data.u8[4] & 0x03) << 1) | rx_frame.data.u8[3] >> 7; + realtime_SOC_too_low = (rx_frame.data.u8[4] & 0x1C) >> 2; + realtime_SOC_jumping_warning = (rx_frame.data.u8[4] & 0xE0) >> 5; + realtime_temperature_difference_warning = rx_frame.data.u8[5] & 0x07; + realtime_cell_overtemperature_warning = (rx_frame.data.u8[5] & 0x38) >> 3; + realtime_cell_undertemperature_warning = ((rx_frame.data.u8[6] & 0x01) << 2) | rx_frame.data.u8[5] >> 6; + realtime_battery_overvoltage_warning = (rx_frame.data.u8[6] & 0x0E) >> 1; + realtime_battery_undervoltage_warning = (rx_frame.data.u8[6] & 0x70) >> 4; + realtime_cell_overvoltage_warning = ((rx_frame.data.u8[7] & 0x03) << 1) | rx_frame.data.u8[6] >> 7; + realtime_cell_undervoltage_warning = (rx_frame.data.u8[7] & 0x1C) >> 2; + realtime_cell_imbalance_warning = (rx_frame.data.u8[7] & 0xE0) >> 5; + for (uint8_t i = 0; i < 26; i++) { // Frame 9 to 34 is S/N for battery + battery_serialnumber[i] = rx_frame.data.u8[i + 9]; + } + realtime_warning_battery_unathorized = (rx_frame.data.u8[40] & 0x07); + break; + case 0x2AF: // BMS 50ms + actual_battery_voltage = + ((rx_frame.data.u8[1] & 0x3F) << 8) | rx_frame.data.u8[0]; //*0.0625 // Seems to be 0.125 in logging + regen_battery = ((rx_frame.data.u8[5] & 0x7F) << 8) | rx_frame.data.u8[4]; + energy_extracted_from_battery = ((rx_frame.data.u8[7] & 0x7F) << 8) | rx_frame.data.u8[6]; + break; + case 0x578: // BMS 100ms + BMS_578_CRC = rx_frame.data.u8[0]; // Can be used to check CAN signal integrity later on + BMS_578_counter = (rx_frame.data.u8[1] & 0x0F); // Can be used to check CAN signal integrity later on + BMS_Status_DCLS = ((rx_frame.data.u8[1] & 0x30) >> 4); + DC_voltage_DCLS = (rx_frame.data.u8[2] << 6) | (rx_frame.data.u8[1] >> 6); + max_fastcharging_current_amp = ((rx_frame.data.u8[4] & 0x01) << 8) | rx_frame.data.u8[3]; + DC_voltage_chargeport = (rx_frame.data.u8[7] << 4) | (rx_frame.data.u8[6] >> 4); + break; + case 0x5A2: // BMS 500ms normal, 100ms fast + BMS_5A2_CRC = rx_frame.data.u8[0]; // Can be used to check CAN signal integrity later on + BMS_5A2_counter = (rx_frame.data.u8[1] & 0x0F); // Can be used to check CAN signal integrity later on + service_disconnect_switch_missing = (rx_frame.data.u8[1] & 0x20) >> 5; + pilotline_open = (rx_frame.data.u8[1] & 0x10) >> 4; + BMS_status_voltage_free = (rx_frame.data.u8[1] & 0xC0) >> 6; + BMS_OBD_MIL = (rx_frame.data.u8[2] & 0x01); + BMS_error_status = (rx_frame.data.u8[2] & 0x70) >> 4; + BMS_capacity_ah = ((rx_frame.data.u8[4] & 0x03) << 9) | (rx_frame.data.u8[3] << 1) | (rx_frame.data.u8[2] >> 7); + BMS_error_lamp_req = (rx_frame.data.u8[4] & 0x04) >> 2; + BMS_warning_lamp_req = (rx_frame.data.u8[4] & 0x08) >> 3; + BMS_Kl30c_Status = (rx_frame.data.u8[4] & 0x30) >> 4; + break; + case 0x5CA: // BMS 500ms + BMS_5CA_CRC = rx_frame.data.u8[0]; // Can be used to check CAN signal integrity later on + BMS_5CA_counter = (rx_frame.data.u8[1] & 0x0F); // Can be used to check CAN signal integrity later on + balancing_request = (rx_frame.data.u8[5] & 0x08) >> 3; //True/False + battery_diagnostic = (rx_frame.data.u8[3] & 0x07); + battery_Wh_left = + (rx_frame.data.u8[2] << 4) | (rx_frame.data.u8[1] >> 4); //*50 ! Not usable, seems to always contain 0x7F0 + battery_potential_status = + (rx_frame.data.u8[5] & 0x30) >> 4; //0 = function not enabled, 1= no potential, 2 = potential on, 3 = fault + battery_temperature_warning = + (rx_frame.data.u8[7] & 0x0C) >> 2; // 0 = no warning, 1 = temp level 1, 2=temp level 2 + battery_Wh_max = + ((rx_frame.data.u8[5] & 0x07) << 8) | rx_frame.data.u8[4]; //*50 ! Not usable, seems to always contain 0x7F0 + break; + case 0x0CF: //BMS 10ms + BMS_0CF_CRC = rx_frame.data.u8[0]; // Can be used to check CAN signal integrity later on + BMS_0CF_counter = (rx_frame.data.u8[1] & 0x0F); // Can be used to check CAN signal integrity later on + BMS_welded_contactors_status = (rx_frame.data.u8[1] & 0x60) >> 5; + BMS_ext_limits_active = (rx_frame.data.u8[1] & 0x80) >> 7; + BMS_mode = (rx_frame.data.u8[2] & 0x07); + switch (BMS_mode) { + case 1: + case 3: + case 4: + datalayer.system.status.battery_allows_contactor_closing = true; + break; + default: + datalayer.system.status.battery_allows_contactor_closing = false; + } + BMS_HVIL_status = (rx_frame.data.u8[2] & 0x18) >> 3; + BMS_error_shutdown = (rx_frame.data.u8[2] & 0x20) >> 5; + BMS_error_shutdown_request = (rx_frame.data.u8[2] & 0x40) >> 6; + BMS_fault_performance = (rx_frame.data.u8[2] & 0x80) >> 7; + BMS_current = ((rx_frame.data.u8[4] & 0x7F) << 8) | rx_frame.data.u8[3]; + BMS_fault_emergency_shutdown_crash = (rx_frame.data.u8[4] & 0x80) >> 7; + BMS_voltage_intermediate = (((rx_frame.data.u8[6] & 0x0F) << 8) + (rx_frame.data.u8[5])); + BMS_voltage = ((rx_frame.data.u8[7] << 4) + ((rx_frame.data.u8[6] & 0xF0) >> 4)); + break; + case 0x1C42007B: // Reply from battery + if (rx_frame.data.u8[0] == 0x10) { //PID header + transmit_can(&MEB_ACK_FRAME, can_config.battery); + } + if (rx_frame.DLC == 8) { + pid_reply = (rx_frame.data.u8[2] << 8) + rx_frame.data.u8[3]; + } else { //12 or 24bit message has reply in other location + pid_reply = (rx_frame.data.u8[3] << 8) + rx_frame.data.u8[4]; + } + + switch (pid_reply) { + case PID_SOC: + battery_soc_polled = rx_frame.data.u8[4] * 4; // 135*4 = 54.0% + case PID_VOLTAGE: + battery_voltage_polled = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_CURRENT: // IDLE 0A: 00 08 62 1E 3D (00 02) 49 F0 39 AA AA + battery_current_polled = ((rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6]); //TODO: right bits? + break; + case PID_MAX_TEMP: + battery_max_temp = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_MIN_TEMP: + battery_min_temp = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_MAX_CHARGE_VOLTAGE: + battery_max_charge_voltage = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_MIN_DISCHARGE_VOLTAGE: + battery_min_discharge_voltage = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_ALLOWED_CHARGE_POWER: + battery_allowed_charge_power = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_ALLOWED_DISCHARGE_POWER: + battery_allowed_discharge_power = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_CELLVOLTAGE_CELL_1: + cellvoltages_polled[0] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_2: + cellvoltages_polled[1] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_3: + cellvoltages_polled[2] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_4: + cellvoltages_polled[3] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_5: + cellvoltages_polled[4] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_6: + cellvoltages_polled[5] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_7: + cellvoltages_polled[6] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_8: + cellvoltages_polled[7] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_9: + cellvoltages_polled[8] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_10: + cellvoltages_polled[9] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_11: + cellvoltages_polled[10] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_12: + cellvoltages_polled[11] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_13: + cellvoltages_polled[12] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_14: + cellvoltages_polled[13] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_15: + cellvoltages_polled[14] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_16: + cellvoltages_polled[15] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_17: + cellvoltages_polled[16] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_18: + cellvoltages_polled[17] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_19: + cellvoltages_polled[18] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_20: + cellvoltages_polled[19] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_21: + cellvoltages_polled[20] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_22: + cellvoltages_polled[21] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_23: + cellvoltages_polled[22] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_24: + cellvoltages_polled[23] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_25: + cellvoltages_polled[24] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_26: + cellvoltages_polled[25] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_27: + cellvoltages_polled[26] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_28: + cellvoltages_polled[27] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_29: + cellvoltages_polled[28] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_30: + cellvoltages_polled[29] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_31: + cellvoltages_polled[30] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_32: + cellvoltages_polled[31] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_33: + cellvoltages_polled[32] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_34: + cellvoltages_polled[33] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_35: + cellvoltages_polled[34] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_36: + cellvoltages_polled[35] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_37: + cellvoltages_polled[36] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_38: + cellvoltages_polled[37] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_39: + cellvoltages_polled[38] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_40: + cellvoltages_polled[39] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_41: + cellvoltages_polled[40] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_42: + cellvoltages_polled[41] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_43: + cellvoltages_polled[42] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_44: + cellvoltages_polled[43] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_45: + cellvoltages_polled[44] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_46: + cellvoltages_polled[45] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_47: + cellvoltages_polled[46] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_48: + cellvoltages_polled[47] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_49: + cellvoltages_polled[48] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_50: + cellvoltages_polled[49] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_51: + cellvoltages_polled[50] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_52: + cellvoltages_polled[51] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_53: + cellvoltages_polled[52] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_54: + cellvoltages_polled[53] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_55: + cellvoltages_polled[54] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_56: + cellvoltages_polled[55] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_57: + cellvoltages_polled[56] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_58: + cellvoltages_polled[57] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_59: + cellvoltages_polled[58] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_60: + cellvoltages_polled[59] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_61: + cellvoltages_polled[60] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_62: + cellvoltages_polled[61] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_63: + cellvoltages_polled[62] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_64: + cellvoltages_polled[63] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_65: + cellvoltages_polled[64] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_66: + cellvoltages_polled[65] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_67: + cellvoltages_polled[66] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_68: + cellvoltages_polled[67] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_69: + cellvoltages_polled[68] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_70: + cellvoltages_polled[69] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_71: + cellvoltages_polled[70] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_72: + cellvoltages_polled[71] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_73: + cellvoltages_polled[72] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_74: + cellvoltages_polled[73] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_75: + cellvoltages_polled[74] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_76: + cellvoltages_polled[75] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_77: + cellvoltages_polled[76] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_78: + cellvoltages_polled[77] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_79: + cellvoltages_polled[78] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_80: + cellvoltages_polled[79] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_81: + cellvoltages_polled[80] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_82: + cellvoltages_polled[81] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_83: + cellvoltages_polled[82] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_84: + cellvoltages_polled[83] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_85: + tempval = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + if (tempval != 0xFFE) { + cellvoltages_polled[84] = (tempval + 1000); + } else { // Cell 85 unavailable. We have a 84S battery (48kWh) + datalayer.battery.info.number_of_cells = 84; + datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_84S_DV; + datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_84S_DV; + } + break; + case PID_CELLVOLTAGE_CELL_86: + tempval = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + if (tempval != 0xFFE) { + cellvoltages_polled[85] = (tempval + 1000); + } + break; + case PID_CELLVOLTAGE_CELL_87: + tempval = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + if (tempval != 0xFFE) { + cellvoltages_polled[86] = (tempval + 1000); + } + break; + case PID_CELLVOLTAGE_CELL_88: + tempval = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + if (tempval != 0xFFE) { + cellvoltages_polled[87] = (tempval + 1000); + } + break; + case PID_CELLVOLTAGE_CELL_89: + tempval = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + if (tempval != 0xFFE) { + cellvoltages_polled[88] = (tempval + 1000); + } + break; + case PID_CELLVOLTAGE_CELL_90: + tempval = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + if (tempval != 0xFFE) { + cellvoltages_polled[89] = (tempval + 1000); + } + break; + case PID_CELLVOLTAGE_CELL_91: + tempval = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + if (tempval != 0xFFE) { + cellvoltages_polled[90] = (tempval + 1000); + } + break; + case PID_CELLVOLTAGE_CELL_92: + tempval = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + if (tempval != 0xFFE) { + cellvoltages_polled[91] = (tempval + 1000); + } + break; + case PID_CELLVOLTAGE_CELL_93: + tempval = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + if (tempval != 0xFFE) { + cellvoltages_polled[92] = (tempval + 1000); + } + break; + case PID_CELLVOLTAGE_CELL_94: + tempval = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + if (tempval != 0xFFE) { + cellvoltages_polled[93] = (tempval + 1000); + } + break; + case PID_CELLVOLTAGE_CELL_95: + tempval = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + if (tempval != 0xFFE) { + cellvoltages_polled[94] = (tempval + 1000); + } + break; + case PID_CELLVOLTAGE_CELL_96: + tempval = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + if (tempval != 0xFFE) { + cellvoltages_polled[95] = (tempval + 1000); + } + break; + case PID_CELLVOLTAGE_CELL_97: + tempval = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + if (tempval != 0xFFE) { + cellvoltages_polled[96] = (tempval + 1000); + } else { // Cell 97 unavailable. We have a 96S battery (55kWh) (Unless already specified as 84S) + if (datalayer.battery.info.number_of_cells == 84) { + // Do nothing, we already identified it as 84S + } else { + datalayer.battery.info.number_of_cells = 96; + datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_96S_DV; + datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_96S_DV; + } + } + break; + case PID_CELLVOLTAGE_CELL_98: + tempval = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + if (tempval != 0xFFE) { + cellvoltages_polled[97] = (tempval + 1000); + } + break; + case PID_CELLVOLTAGE_CELL_99: + tempval = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + if (tempval != 0xFFE) { + cellvoltages_polled[98] = (tempval + 1000); + } + break; + case PID_CELLVOLTAGE_CELL_100: + tempval = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + if (tempval != 0xFFE) { + cellvoltages_polled[99] = (tempval + 1000); + } + break; + case PID_CELLVOLTAGE_CELL_101: + tempval = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + if (tempval != 0xFFE) { + cellvoltages_polled[100] = (tempval + 1000); + } + break; + case PID_CELLVOLTAGE_CELL_102: + tempval = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + if (tempval != 0xFFE) { + cellvoltages_polled[101] = (tempval + 1000); + } + break; + case PID_CELLVOLTAGE_CELL_103: + tempval = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + if (tempval != 0xFFE) { + cellvoltages_polled[102] = (tempval + 1000); + } + break; + case PID_CELLVOLTAGE_CELL_104: + tempval = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + if (tempval != 0xFFE) { + cellvoltages_polled[103] = (tempval + 1000); + } + break; + case PID_CELLVOLTAGE_CELL_105: + tempval = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + if (tempval != 0xFFE) { + cellvoltages_polled[104] = (tempval + 1000); + } + break; + case PID_CELLVOLTAGE_CELL_106: + tempval = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + if (tempval != 0xFFE) { + cellvoltages_polled[105] = (tempval + 1000); + } + break; + case PID_CELLVOLTAGE_CELL_107: + tempval = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + if (tempval != 0xFFE) { + cellvoltages_polled[106] = (tempval + 1000); + } + break; + case PID_CELLVOLTAGE_CELL_108: + tempval = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + if (tempval != 0xFFE) { + cellvoltages_polled[107] = (tempval + 1000); + datalayer.battery.info.number_of_cells = 108; + datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_108S_DV; + datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_108S_DV; + } + break; + default: + break; + } + break; + default: + break; + } +} + +void send_can_battery() { + unsigned long currentMillis = millis(); + // Send 10ms CAN Message + if (currentMillis > last_can_msg_timestamp + 500) { + first_can_msg = 0; + } + + if (currentMillis - previousMillis10ms >= INTERVAL_10_MS) { + // Check if sending of CAN messages has been delayed too much. + if ((currentMillis - previousMillis10ms >= INTERVAL_10_MS_DELAYED) && (currentMillis > BOOTUP_TIME)) { + set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis10ms)); + } else { + clear_event(EVENT_CAN_OVERRUN); + } + previousMillis10ms = currentMillis; + + MEB_0FC.data.u8[1] = ((MEB_0FC.data.u8[1] & 0xF0) | counter_10ms); + MEB_0FC.data.u8[0] = vw_crc_calc(MEB_0FC.data.u8, MEB_0FC.DLC, MEB_0FC.ID); + + counter_10ms = (counter_10ms + 1) % 16; //Goes from 0-1-2-3...15-0-1-2-3.. + + transmit_can(&MEB_0FC, can_config.battery); // Required for contactor closing + } + // Send 20ms CAN Message + if (currentMillis - previousMillis20ms >= INTERVAL_20_MS) { + previousMillis20ms = currentMillis; + + MEB_0FD.data.u8[1] = ((MEB_0FD.data.u8[1] & 0xF0) | counter_20ms); + MEB_0FD.data.u8[0] = vw_crc_calc(MEB_0FD.data.u8, MEB_0FD.DLC, MEB_0FD.ID); + + counter_20ms = (counter_20ms + 1) % 16; //Goes from 0-1-2-3...15-0-1-2-3.. + + transmit_can(&MEB_0FD, can_config.battery); // Required for contactor closing + } + // Send 40ms CAN Message + if (currentMillis - previousMillis40ms >= INTERVAL_40_MS) { + previousMillis40ms = currentMillis; + + /* Handle content for 0x040 message */ + /* Airbag message, needed for BMS to function */ + MEB_040.data.u8[7] = counter_040; + MEB_040.data.u8[1] = ((MEB_040.data.u8[1] & 0xF0) | counter_40ms); + MEB_040.data.u8[0] = vw_crc_calc(MEB_040.data.u8, MEB_040.DLC, MEB_040.ID); + counter_40ms = (counter_40ms + 1) % 16; //Goes from 0-1-2-3...15-0-1-2-3.. + if (toggle) { + counter_040 = (counter_040 + 1) % 256; // Increment only on every other pass + } + toggle = !toggle; // Flip the toggle each time the code block is executed + + transmit_can(&MEB_040, can_config.battery); // Airbag message - Needed for contactor closing + } + // Send 50ms CAN Message + if (currentMillis - previousMillis50ms >= INTERVAL_50_MS) { + previousMillis50ms = currentMillis; + + /* Handle content for 0x0C0 message */ + /* BMS needs to see this EM1 message. Content located in frame5&6 especially (can be static?)*/ + /* Also the voltage seen externally to battery is in frame 7&8. At least for the 62kWh ID3 version does not seem to matter, but we send it anyway. */ + MEB_0C0.data.u8[1] = ((MEB_0C0.data.u8[1] & 0xF0) | counter_50ms); + MEB_0C0.data.u8[7] = ((datalayer.battery.status.voltage_dV / 10) * 4) & 0x00FF; + MEB_0C0.data.u8[8] = + ((MEB_0C0.data.u8[8] & 0xF0) | ((((datalayer.battery.status.voltage_dV / 10) * 4) >> 8) & 0x0F)); + MEB_0C0.data.u8[0] = vw_crc_calc(MEB_0C0.data.u8, MEB_0C0.DLC, MEB_0C0.ID); + counter_50ms = (counter_50ms + 1) % 16; //Goes from 0-1-2-3...15-0-1-2-3.. + + transmit_can(&MEB_0C0, can_config.battery); // Needed for contactor closing + } + // Send 100ms CAN Message + if (currentMillis - previousMillis100ms >= INTERVAL_100_MS) { + previousMillis100ms = currentMillis; + + //HV request and DC/DC control lies in 0x503 + MEB_503.data.u8[3] = 0x00; + if (datalayer.battery.status.bms_status != FAULT && first_can_msg > 0 && currentMillis > first_can_msg + 2000) { + MEB_503.data.u8[1] = 0xB0; + MEB_503.data.u8[3] = BMS_TARGET_HV_ON; //BMS_TARGET_AC_CHARGING; //TODO, should we try AC_2 or DC charging? + MEB_503.data.u8[5] = 0x82; // Bordnetz Active + MEB_503.data.u8[6] = 0xE0; // Request emergency shutdown HV system == 0, false + } else if (first_can_msg > 0 && currentMillis > first_can_msg + 2000) { //FAULT STATE, open contactors + MEB_503.data.u8[1] = 0x90; + MEB_503.data.u8[3] = BMS_TARGET_HV_OFF; + MEB_503.data.u8[5] = 0x80; // Bordnetz Inactive + MEB_503.data.u8[6] = + 0xE3; // Request emergency shutdown HV system == init (3) (not sure if we dare activate this, this is done with 0xE1) + } + MEB_503.data.u8[1] = ((MEB_503.data.u8[1] & 0xF0) | counter_100ms); + MEB_503.data.u8[0] = vw_crc_calc(MEB_503.data.u8, MEB_503.DLC, MEB_503.ID); + + //Bidirectional charging message + MEB_272.data.u8[1] = + 0x00; //0x80; // Bidirectional charging active (Set to 0x00 incase no bidirectional charging wanted) + MEB_272.data.u8[2] = 0x00; + //0x01; // High load bidirectional charging active (Set to 0x00 incase no bidirectional charging wanted) + MEB_272.data.u8[5] = DC_FASTCHARGE_NO_START_REQUEST; //DC_FASTCHARGE_VEHICLE; //DC charging + + //Klemmen status + MEB_3C0.data.u8[2] = 0x00; //0x02; //bit to signal that KL_15 is ON // Always 0 in start4.log + MEB_3C0.data.u8[1] = ((MEB_3C0.data.u8[1] & 0xF0) | counter_100ms); + MEB_3C0.data.u8[0] = vw_crc_calc(MEB_3C0.data.u8, MEB_3C0.DLC, MEB_3C0.ID); + + MEB_3BE.data.u8[1] = ((MEB_3BE.data.u8[1] & 0xF0) | counter_100ms); + MEB_3BE.data.u8[0] = vw_crc_calc(MEB_3BE.data.u8, MEB_3BE.DLC, MEB_3BE.ID); + + MEB_14C.data.u8[1] = ((MEB_14C.data.u8[1] & 0xF0) | counter_100ms); + MEB_14C.data.u8[0] = vw_crc_calc(MEB_14C.data.u8, MEB_14C.DLC, MEB_14C.ID); + + counter_100ms = (counter_100ms + 1) % 16; //Goes from 0-1-2-3...15-0-1-2-3.. + transmit_can(&MEB_503, can_config.battery); + transmit_can(&MEB_272, can_config.battery); + transmit_can(&MEB_3C0, can_config.battery); + transmit_can(&MEB_3BE, can_config.battery); + transmit_can(&MEB_14C, can_config.battery); + } + //Send 200ms message + if (currentMillis - previousMillis200ms >= INTERVAL_200_MS) { + previousMillis200ms = currentMillis; + + //TODO: 153 does not seem to need CRC even though it has it? Empty in some logs and still works + + //TODO: MEB_1B0000B9 & MEB_1B000010 & MEB_1B000046 has CAN sleep commands, static OK? + + transmit_can(&MEB_5E1, can_config.battery); + transmit_can(&MEB_153, can_config.battery); + transmit_can(&MEB_1B0000B9, can_config.battery); + transmit_can(&MEB_1B000010, can_config.battery); + transmit_can(&MEB_1B000046, can_config.battery); + + switch (poll_pid) { + case PID_SOC: + MEB_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_SOC >> 8); // High byte + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_SOC; // Low byte + poll_pid = PID_VOLTAGE; + break; + case PID_VOLTAGE: + MEB_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_VOLTAGE >> 8); + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_VOLTAGE; + poll_pid = PID_CURRENT; + break; + case PID_CURRENT: + MEB_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_CURRENT >> 8); + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CURRENT; + poll_pid = PID_MAX_TEMP; + break; + case PID_MAX_TEMP: + MEB_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_MAX_TEMP >> 8); + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_MAX_TEMP; + poll_pid = PID_MIN_TEMP; + break; + case PID_MIN_TEMP: + MEB_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_MIN_TEMP >> 8); + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_MIN_TEMP; + poll_pid = PID_MAX_CHARGE_VOLTAGE; + break; + case PID_MAX_CHARGE_VOLTAGE: + MEB_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_MAX_CHARGE_VOLTAGE >> 8); + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_MAX_CHARGE_VOLTAGE; + poll_pid = PID_MIN_DISCHARGE_VOLTAGE; + break; + case PID_MIN_DISCHARGE_VOLTAGE: + MEB_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_MIN_DISCHARGE_VOLTAGE >> 8); + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_MIN_DISCHARGE_VOLTAGE; + poll_pid = PID_ALLOWED_CHARGE_POWER; + break; + case PID_ALLOWED_CHARGE_POWER: + MEB_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_ALLOWED_CHARGE_POWER >> 8); + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_ALLOWED_CHARGE_POWER; + poll_pid = PID_ALLOWED_DISCHARGE_POWER; + break; + case PID_ALLOWED_DISCHARGE_POWER: + MEB_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_ALLOWED_DISCHARGE_POWER >> 8); + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_ALLOWED_DISCHARGE_POWER; + poll_pid = PID_CELLVOLTAGE_CELL_1; // Start polling cell voltages + break; + // Cell Voltage Cases + case PID_CELLVOLTAGE_CELL_1: + MEB_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_CELLVOLTAGE_CELL_1 >> 8); + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_1; + poll_pid = PID_CELLVOLTAGE_CELL_2; + break; + case PID_CELLVOLTAGE_CELL_2: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_2; + poll_pid = PID_CELLVOLTAGE_CELL_3; + break; + case PID_CELLVOLTAGE_CELL_3: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_3; + poll_pid = PID_CELLVOLTAGE_CELL_4; + break; + case PID_CELLVOLTAGE_CELL_4: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_4; + poll_pid = PID_CELLVOLTAGE_CELL_5; + break; + case PID_CELLVOLTAGE_CELL_5: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_5; + poll_pid = PID_CELLVOLTAGE_CELL_6; + break; + case PID_CELLVOLTAGE_CELL_6: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_6; + poll_pid = PID_CELLVOLTAGE_CELL_7; + break; + case PID_CELLVOLTAGE_CELL_7: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_7; + poll_pid = PID_CELLVOLTAGE_CELL_8; + break; + case PID_CELLVOLTAGE_CELL_8: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_8; + poll_pid = PID_CELLVOLTAGE_CELL_9; + break; + case PID_CELLVOLTAGE_CELL_9: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_9; + poll_pid = PID_CELLVOLTAGE_CELL_10; + break; + case PID_CELLVOLTAGE_CELL_10: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_10; + poll_pid = PID_CELLVOLTAGE_CELL_11; + break; + case PID_CELLVOLTAGE_CELL_11: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_11; + poll_pid = PID_CELLVOLTAGE_CELL_12; + break; + case PID_CELLVOLTAGE_CELL_12: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_12; + poll_pid = PID_CELLVOLTAGE_CELL_13; + break; + case PID_CELLVOLTAGE_CELL_13: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_13; + poll_pid = PID_CELLVOLTAGE_CELL_14; + break; + case PID_CELLVOLTAGE_CELL_14: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_14; + poll_pid = PID_CELLVOLTAGE_CELL_15; + break; + case PID_CELLVOLTAGE_CELL_15: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_15; + poll_pid = PID_CELLVOLTAGE_CELL_16; + break; + case PID_CELLVOLTAGE_CELL_16: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_16; + poll_pid = PID_CELLVOLTAGE_CELL_17; + break; + case PID_CELLVOLTAGE_CELL_17: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_17; + poll_pid = PID_CELLVOLTAGE_CELL_18; + break; + case PID_CELLVOLTAGE_CELL_18: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_18; + poll_pid = PID_CELLVOLTAGE_CELL_19; + break; + case PID_CELLVOLTAGE_CELL_19: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_19; + poll_pid = PID_CELLVOLTAGE_CELL_20; + break; + case PID_CELLVOLTAGE_CELL_20: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_20; + poll_pid = PID_CELLVOLTAGE_CELL_21; + break; + case PID_CELLVOLTAGE_CELL_21: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_21; + poll_pid = PID_CELLVOLTAGE_CELL_22; + break; + case PID_CELLVOLTAGE_CELL_22: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_22; + poll_pid = PID_CELLVOLTAGE_CELL_23; + break; + case PID_CELLVOLTAGE_CELL_23: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_23; + poll_pid = PID_CELLVOLTAGE_CELL_24; + break; + case PID_CELLVOLTAGE_CELL_24: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_24; + poll_pid = PID_CELLVOLTAGE_CELL_25; + break; + case PID_CELLVOLTAGE_CELL_25: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_25; + poll_pid = PID_CELLVOLTAGE_CELL_26; + break; + case PID_CELLVOLTAGE_CELL_26: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_26; + poll_pid = PID_CELLVOLTAGE_CELL_27; + break; + case PID_CELLVOLTAGE_CELL_27: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_27; + poll_pid = PID_CELLVOLTAGE_CELL_28; + break; + case PID_CELLVOLTAGE_CELL_28: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_28; + poll_pid = PID_CELLVOLTAGE_CELL_29; + break; + case PID_CELLVOLTAGE_CELL_29: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_29; + poll_pid = PID_CELLVOLTAGE_CELL_30; + break; + case PID_CELLVOLTAGE_CELL_30: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_30; + poll_pid = PID_CELLVOLTAGE_CELL_31; + break; + case PID_CELLVOLTAGE_CELL_31: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_31; + poll_pid = PID_CELLVOLTAGE_CELL_32; + break; + case PID_CELLVOLTAGE_CELL_32: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_32; + poll_pid = PID_CELLVOLTAGE_CELL_33; + break; + case PID_CELLVOLTAGE_CELL_33: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_33; + poll_pid = PID_CELLVOLTAGE_CELL_34; + break; + case PID_CELLVOLTAGE_CELL_34: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_34; + poll_pid = PID_CELLVOLTAGE_CELL_35; + break; + case PID_CELLVOLTAGE_CELL_35: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_35; + poll_pid = PID_CELLVOLTAGE_CELL_36; + break; + case PID_CELLVOLTAGE_CELL_36: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_36; + poll_pid = PID_CELLVOLTAGE_CELL_37; + break; + case PID_CELLVOLTAGE_CELL_37: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_37; + poll_pid = PID_CELLVOLTAGE_CELL_38; + break; + case PID_CELLVOLTAGE_CELL_38: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_38; + poll_pid = PID_CELLVOLTAGE_CELL_39; + break; + case PID_CELLVOLTAGE_CELL_39: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_39; + poll_pid = PID_CELLVOLTAGE_CELL_40; + break; + case PID_CELLVOLTAGE_CELL_40: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_40; + poll_pid = PID_CELLVOLTAGE_CELL_41; + break; + case PID_CELLVOLTAGE_CELL_41: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_41; + poll_pid = PID_CELLVOLTAGE_CELL_42; + break; + case PID_CELLVOLTAGE_CELL_42: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_42; + poll_pid = PID_CELLVOLTAGE_CELL_43; + break; + case PID_CELLVOLTAGE_CELL_43: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_43; + poll_pid = PID_CELLVOLTAGE_CELL_44; + break; + case PID_CELLVOLTAGE_CELL_44: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_44; + poll_pid = PID_CELLVOLTAGE_CELL_45; + break; + case PID_CELLVOLTAGE_CELL_45: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_45; + poll_pid = PID_CELLVOLTAGE_CELL_46; + break; + case PID_CELLVOLTAGE_CELL_46: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_46; + poll_pid = PID_CELLVOLTAGE_CELL_47; + break; + case PID_CELLVOLTAGE_CELL_47: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_47; + poll_pid = PID_CELLVOLTAGE_CELL_48; + break; + case PID_CELLVOLTAGE_CELL_48: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_48; + poll_pid = PID_CELLVOLTAGE_CELL_49; + break; + case PID_CELLVOLTAGE_CELL_49: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_49; + poll_pid = PID_CELLVOLTAGE_CELL_50; + break; + case PID_CELLVOLTAGE_CELL_50: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_50; + poll_pid = PID_CELLVOLTAGE_CELL_51; + break; + case PID_CELLVOLTAGE_CELL_51: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_51; + poll_pid = PID_CELLVOLTAGE_CELL_52; + break; + case PID_CELLVOLTAGE_CELL_52: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_52; + poll_pid = PID_CELLVOLTAGE_CELL_53; + break; + case PID_CELLVOLTAGE_CELL_53: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_53; + poll_pid = PID_CELLVOLTAGE_CELL_54; + break; + case PID_CELLVOLTAGE_CELL_54: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_54; + poll_pid = PID_CELLVOLTAGE_CELL_55; + break; + case PID_CELLVOLTAGE_CELL_55: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_55; + poll_pid = PID_CELLVOLTAGE_CELL_56; + break; + case PID_CELLVOLTAGE_CELL_56: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_56; + poll_pid = PID_CELLVOLTAGE_CELL_57; + break; + case PID_CELLVOLTAGE_CELL_57: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_57; + poll_pid = PID_CELLVOLTAGE_CELL_58; + break; + case PID_CELLVOLTAGE_CELL_58: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_58; + poll_pid = PID_CELLVOLTAGE_CELL_59; + break; + case PID_CELLVOLTAGE_CELL_59: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_59; + poll_pid = PID_CELLVOLTAGE_CELL_60; + break; + case PID_CELLVOLTAGE_CELL_60: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_60; + poll_pid = PID_CELLVOLTAGE_CELL_61; + break; + case PID_CELLVOLTAGE_CELL_61: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_61; + poll_pid = PID_CELLVOLTAGE_CELL_62; + break; + case PID_CELLVOLTAGE_CELL_62: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_62; + poll_pid = PID_CELLVOLTAGE_CELL_63; + break; + case PID_CELLVOLTAGE_CELL_63: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_63; + poll_pid = PID_CELLVOLTAGE_CELL_64; + break; + case PID_CELLVOLTAGE_CELL_64: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_64; + poll_pid = PID_CELLVOLTAGE_CELL_65; + break; + case PID_CELLVOLTAGE_CELL_65: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_65; + poll_pid = PID_CELLVOLTAGE_CELL_66; + break; + case PID_CELLVOLTAGE_CELL_66: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_66; + poll_pid = PID_CELLVOLTAGE_CELL_67; + break; + case PID_CELLVOLTAGE_CELL_67: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_67; + poll_pid = PID_CELLVOLTAGE_CELL_68; + break; + case PID_CELLVOLTAGE_CELL_68: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_68; + poll_pid = PID_CELLVOLTAGE_CELL_69; + break; + case PID_CELLVOLTAGE_CELL_69: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_69; + poll_pid = PID_CELLVOLTAGE_CELL_70; + break; + case PID_CELLVOLTAGE_CELL_70: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_70; + poll_pid = PID_CELLVOLTAGE_CELL_71; + break; + case PID_CELLVOLTAGE_CELL_71: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_71; + poll_pid = PID_CELLVOLTAGE_CELL_72; + break; + case PID_CELLVOLTAGE_CELL_72: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_72; + poll_pid = PID_CELLVOLTAGE_CELL_73; + break; + case PID_CELLVOLTAGE_CELL_73: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_73; + poll_pid = PID_CELLVOLTAGE_CELL_74; + break; + case PID_CELLVOLTAGE_CELL_74: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_74; + poll_pid = PID_CELLVOLTAGE_CELL_75; + break; + case PID_CELLVOLTAGE_CELL_75: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_75; + poll_pid = PID_CELLVOLTAGE_CELL_76; + break; + case PID_CELLVOLTAGE_CELL_76: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_76; + poll_pid = PID_CELLVOLTAGE_CELL_77; + break; + case PID_CELLVOLTAGE_CELL_77: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_77; + poll_pid = PID_CELLVOLTAGE_CELL_78; + break; + case PID_CELLVOLTAGE_CELL_78: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_78; + poll_pid = PID_CELLVOLTAGE_CELL_79; + break; + case PID_CELLVOLTAGE_CELL_79: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_79; + poll_pid = PID_CELLVOLTAGE_CELL_80; + break; + case PID_CELLVOLTAGE_CELL_80: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_80; + poll_pid = PID_CELLVOLTAGE_CELL_81; + break; + case PID_CELLVOLTAGE_CELL_81: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_81; + poll_pid = PID_CELLVOLTAGE_CELL_82; + break; + case PID_CELLVOLTAGE_CELL_82: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_82; + poll_pid = PID_CELLVOLTAGE_CELL_83; + break; + case PID_CELLVOLTAGE_CELL_83: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_83; + poll_pid = PID_CELLVOLTAGE_CELL_84; + break; + case PID_CELLVOLTAGE_CELL_84: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_84; + poll_pid = PID_CELLVOLTAGE_CELL_85; + break; + case PID_CELLVOLTAGE_CELL_85: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_85; + poll_pid = PID_CELLVOLTAGE_CELL_86; + break; + case PID_CELLVOLTAGE_CELL_86: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_86; + poll_pid = PID_CELLVOLTAGE_CELL_87; + break; + case PID_CELLVOLTAGE_CELL_87: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_87; + poll_pid = PID_CELLVOLTAGE_CELL_88; + break; + case PID_CELLVOLTAGE_CELL_88: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_88; + poll_pid = PID_CELLVOLTAGE_CELL_89; + break; + case PID_CELLVOLTAGE_CELL_89: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_89; + poll_pid = PID_CELLVOLTAGE_CELL_90; + break; + case PID_CELLVOLTAGE_CELL_90: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_90; + poll_pid = PID_CELLVOLTAGE_CELL_91; + break; + case PID_CELLVOLTAGE_CELL_91: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_91; + poll_pid = PID_CELLVOLTAGE_CELL_92; + break; + case PID_CELLVOLTAGE_CELL_92: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_92; + poll_pid = PID_CELLVOLTAGE_CELL_93; + break; + case PID_CELLVOLTAGE_CELL_93: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_93; + poll_pid = PID_CELLVOLTAGE_CELL_94; + break; + case PID_CELLVOLTAGE_CELL_94: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_94; + poll_pid = PID_CELLVOLTAGE_CELL_95; + break; + case PID_CELLVOLTAGE_CELL_95: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_95; + poll_pid = PID_CELLVOLTAGE_CELL_96; + break; + case PID_CELLVOLTAGE_CELL_96: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_96; + poll_pid = PID_CELLVOLTAGE_CELL_97; + break; + case PID_CELLVOLTAGE_CELL_97: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_97; + poll_pid = PID_CELLVOLTAGE_CELL_98; + break; + case PID_CELLVOLTAGE_CELL_98: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_98; + poll_pid = PID_CELLVOLTAGE_CELL_99; + break; + case PID_CELLVOLTAGE_CELL_99: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_99; + poll_pid = PID_CELLVOLTAGE_CELL_100; + break; + case PID_CELLVOLTAGE_CELL_100: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_100; + poll_pid = PID_CELLVOLTAGE_CELL_101; + break; + case PID_CELLVOLTAGE_CELL_101: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_101; + poll_pid = PID_CELLVOLTAGE_CELL_102; + break; + case PID_CELLVOLTAGE_CELL_102: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_102; + poll_pid = PID_CELLVOLTAGE_CELL_103; + break; + case PID_CELLVOLTAGE_CELL_103: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_103; + poll_pid = PID_CELLVOLTAGE_CELL_104; + break; + case PID_CELLVOLTAGE_CELL_104: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_104; + poll_pid = PID_CELLVOLTAGE_CELL_105; + break; + case PID_CELLVOLTAGE_CELL_105: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_105; + poll_pid = PID_CELLVOLTAGE_CELL_106; + break; + case PID_CELLVOLTAGE_CELL_106: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_106; + poll_pid = PID_CELLVOLTAGE_CELL_107; + break; + case PID_CELLVOLTAGE_CELL_107: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_107; + poll_pid = PID_CELLVOLTAGE_CELL_108; + break; + case PID_CELLVOLTAGE_CELL_108: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_108; + poll_pid = PID_SOC; + break; + default: + poll_pid = PID_SOC; + break; + } + if (first_can_msg > 0 && currentMillis > first_can_msg + 2000) { + transmit_can(&MEB_POLLING_FRAME, can_config.battery); + } + } + + // Send 500ms CAN Message + if (currentMillis - previousMillis500ms >= INTERVAL_500_MS) { + previousMillis500ms = currentMillis; + + transmit_can(&MEB_16A954B4, can_config.battery); //eTM, Cooling valves and pumps for BMS + transmit_can(&MEB_569, can_config.battery); // Battery heating requests + transmit_can(&MEB_1A55552B, can_config.battery); //Climate, heatpump and priorities + transmit_can(&MEB_1A555548, can_config.battery); //ORU, OTA update message for reserving battery + transmit_can(&MEB_16A954FB, can_config.battery); //Climate, request to BMS for starting preconditioning + } + + //Send 1s CANFD message + if (currentMillis - previousMillis1s >= INTERVAL_1_S) { + previousMillis1s = currentMillis; + + MEB_641.data.u8[1] = ((MEB_641.data.u8[1] & 0xF0) | counter_1000ms); + MEB_641.data.u8[0] = vw_crc_calc(MEB_641.data.u8, MEB_641.DLC, MEB_641.ID); + + MEB_1A5555A6.data.u8[2] = 0x7F; //Outside temperature, factor 0.5, offset -50 + + MEB_6B2.data.u8[0] = //driving cycle counter, 0-254 wrap around. 255 = invalid value + //MEB_6B2.data.u8[1-2-3b0-4] // Odometer, km (20 bits long) + MEB_6B2.data.u8[3] = (uint8_t)((TIME_YEAR - 2000) << 4) | MEB_6B2.data.u8[3]; + MEB_6B2.data.u8[4] = (uint8_t)((TIME_DAY & 0x01) << 7 | TIME_MONTH << 3 | (TIME_YEAR - 2000) >> 4); + MEB_6B2.data.u8[5] = (uint8_t)((TIME_HOUR & 0x0F) << 4 | TIME_DAY >> 1); + MEB_6B2.data.u8[6] = (uint8_t)((seconds & 0x01) << 7 | TIME_MINUTE << 1 | TIME_HOUR >> 4); + MEB_6B2.data.u8[7] = (uint8_t)((seconds & 0x3E) >> 1); + seconds = (seconds + 1) % 60; + + counter_1000ms = (counter_1000ms + 1) % 16; //Goes from 0-1-2-3...15-0-1-2-3.. + transmit_can(&MEB_6B2, can_config.battery); // Diagnostics - Needed for contactor closing + transmit_can(&MEB_641, can_config.battery); // Motor - OBD + transmit_can(&MEB_5F5, can_config.battery); // Loading profile + transmit_can(&MEB_585, can_config.battery); // Systeminfo + transmit_can(&MEB_1A5555A6, can_config.battery); // Temperature QBit + } +} + +void setup_battery(void) { // Performs one time setup at startup + strncpy(datalayer.system.info.battery_protocol, "Volkswagen Group MEB platform via CAN-FD", 63); + datalayer.system.info.battery_protocol[63] = '\0'; + datalayer.battery.info.number_of_cells = 108; //Startup in 108S mode. We figure out the actual count later. + datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_108S_DV; //Defined later to correct pack size + datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_84S_DV; //Defined later to correct pack size + datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV; + datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV; + datalayer.battery.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV; +} + +#endif diff --git a/Software/src/battery/MEB-BATTERY.h b/Software/src/battery/MEB-BATTERY.h new file mode 100644 index 00000000..3648dec4 --- /dev/null +++ b/Software/src/battery/MEB-BATTERY.h @@ -0,0 +1,138 @@ +#ifndef MEB_BATTERY_H +#define MEB_BATTERY_H +#include +#include "../include.h" + +#define BATTERY_SELECTED +#define MAX_PACK_VOLTAGE_84S_DV 3528 //5000 = 500.0V +#define MIN_PACK_VOLTAGE_84S_DV 2520 +#define MAX_PACK_VOLTAGE_96S_DV 4032 +#define MIN_PACK_VOLTAGE_96S_DV 2880 +#define MAX_PACK_VOLTAGE_108S_DV 4536 +#define MIN_PACK_VOLTAGE_108S_DV 3240 +#define MAX_CELL_DEVIATION_MV 150 +#define MAX_CELL_VOLTAGE_MV 4250 //Battery is put into emergency stop if one cell goes over this value +#define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value + +#define PID_SOC 0x028C +#define PID_VOLTAGE 0x1E3B +#define PID_CURRENT 0x1E3D +#define PID_MAX_TEMP 0x1E0E +#define PID_MIN_TEMP 0x1E0F +#define PID_MAX_CHARGE_VOLTAGE 0x5171 +#define PID_MIN_DISCHARGE_VOLTAGE 0x5170 +#define PID_ALLOWED_CHARGE_POWER 0x1E1B +#define PID_ALLOWED_DISCHARGE_POWER 0x1E1C +#define PID_CELLVOLTAGE_CELL_1 0x1E40 +#define PID_CELLVOLTAGE_CELL_2 0x1E41 +#define PID_CELLVOLTAGE_CELL_3 0x1E42 +#define PID_CELLVOLTAGE_CELL_4 0x1E43 +#define PID_CELLVOLTAGE_CELL_5 0x1E44 +#define PID_CELLVOLTAGE_CELL_6 0x1E45 +#define PID_CELLVOLTAGE_CELL_7 0x1E46 +#define PID_CELLVOLTAGE_CELL_8 0x1E47 +#define PID_CELLVOLTAGE_CELL_9 0x1E48 +#define PID_CELLVOLTAGE_CELL_10 0x1E49 +#define PID_CELLVOLTAGE_CELL_11 0x1E4A +#define PID_CELLVOLTAGE_CELL_12 0x1E4B +#define PID_CELLVOLTAGE_CELL_13 0x1E4C +#define PID_CELLVOLTAGE_CELL_14 0x1E4D +#define PID_CELLVOLTAGE_CELL_15 0x1E4E +#define PID_CELLVOLTAGE_CELL_16 0x1E4F +#define PID_CELLVOLTAGE_CELL_17 0x1E50 +#define PID_CELLVOLTAGE_CELL_18 0x1E51 +#define PID_CELLVOLTAGE_CELL_19 0x1E52 +#define PID_CELLVOLTAGE_CELL_20 0x1E53 +#define PID_CELLVOLTAGE_CELL_21 0x1E54 +#define PID_CELLVOLTAGE_CELL_22 0x1E55 +#define PID_CELLVOLTAGE_CELL_23 0x1E56 +#define PID_CELLVOLTAGE_CELL_24 0x1E57 +#define PID_CELLVOLTAGE_CELL_25 0x1E58 +#define PID_CELLVOLTAGE_CELL_26 0x1E59 +#define PID_CELLVOLTAGE_CELL_27 0x1E5A +#define PID_CELLVOLTAGE_CELL_28 0x1E5B +#define PID_CELLVOLTAGE_CELL_29 0x1E5C +#define PID_CELLVOLTAGE_CELL_30 0x1E5D +#define PID_CELLVOLTAGE_CELL_31 0x1E5E +#define PID_CELLVOLTAGE_CELL_32 0x1E5F +#define PID_CELLVOLTAGE_CELL_33 0x1E60 +#define PID_CELLVOLTAGE_CELL_34 0x1E61 +#define PID_CELLVOLTAGE_CELL_35 0x1E62 +#define PID_CELLVOLTAGE_CELL_36 0x1E63 +#define PID_CELLVOLTAGE_CELL_37 0x1E64 +#define PID_CELLVOLTAGE_CELL_38 0x1E65 +#define PID_CELLVOLTAGE_CELL_39 0x1E66 +#define PID_CELLVOLTAGE_CELL_40 0x1E67 +#define PID_CELLVOLTAGE_CELL_41 0x1E68 +#define PID_CELLVOLTAGE_CELL_42 0x1E69 +#define PID_CELLVOLTAGE_CELL_43 0x1E6A +#define PID_CELLVOLTAGE_CELL_44 0x1E6B +#define PID_CELLVOLTAGE_CELL_45 0x1E6C +#define PID_CELLVOLTAGE_CELL_46 0x1E6D +#define PID_CELLVOLTAGE_CELL_47 0x1E6E +#define PID_CELLVOLTAGE_CELL_48 0x1E6F +#define PID_CELLVOLTAGE_CELL_49 0x1E70 +#define PID_CELLVOLTAGE_CELL_50 0x1E71 +#define PID_CELLVOLTAGE_CELL_51 0x1E72 +#define PID_CELLVOLTAGE_CELL_52 0x1E73 +#define PID_CELLVOLTAGE_CELL_53 0x1E74 +#define PID_CELLVOLTAGE_CELL_54 0x1E75 +#define PID_CELLVOLTAGE_CELL_55 0x1E76 +#define PID_CELLVOLTAGE_CELL_56 0x1E77 +#define PID_CELLVOLTAGE_CELL_57 0x1E78 +#define PID_CELLVOLTAGE_CELL_58 0x1E79 +#define PID_CELLVOLTAGE_CELL_59 0x1E7A +#define PID_CELLVOLTAGE_CELL_60 0x1E7B +#define PID_CELLVOLTAGE_CELL_61 0x1E7C +#define PID_CELLVOLTAGE_CELL_62 0x1E7D +#define PID_CELLVOLTAGE_CELL_63 0x1E7E +#define PID_CELLVOLTAGE_CELL_64 0x1E7F +#define PID_CELLVOLTAGE_CELL_65 0x1E80 +#define PID_CELLVOLTAGE_CELL_66 0x1E81 +#define PID_CELLVOLTAGE_CELL_67 0x1E82 +#define PID_CELLVOLTAGE_CELL_68 0x1E83 +#define PID_CELLVOLTAGE_CELL_69 0x1E84 +#define PID_CELLVOLTAGE_CELL_70 0x1E85 +#define PID_CELLVOLTAGE_CELL_71 0x1E86 +#define PID_CELLVOLTAGE_CELL_72 0x1E87 +#define PID_CELLVOLTAGE_CELL_73 0x1E88 +#define PID_CELLVOLTAGE_CELL_74 0x1E89 +#define PID_CELLVOLTAGE_CELL_75 0x1E8A +#define PID_CELLVOLTAGE_CELL_76 0x1E8B +#define PID_CELLVOLTAGE_CELL_77 0x1E8C +#define PID_CELLVOLTAGE_CELL_78 0x1E8D +#define PID_CELLVOLTAGE_CELL_79 0x1E8E +#define PID_CELLVOLTAGE_CELL_80 0x1E8F +#define PID_CELLVOLTAGE_CELL_81 0x1E90 +#define PID_CELLVOLTAGE_CELL_82 0x1E91 +#define PID_CELLVOLTAGE_CELL_83 0x1E92 +#define PID_CELLVOLTAGE_CELL_84 0x1E93 +#define PID_CELLVOLTAGE_CELL_85 0x1E94 +#define PID_CELLVOLTAGE_CELL_86 0x1E95 +#define PID_CELLVOLTAGE_CELL_87 0x1E96 +#define PID_CELLVOLTAGE_CELL_88 0x1E97 +#define PID_CELLVOLTAGE_CELL_89 0x1E98 +#define PID_CELLVOLTAGE_CELL_90 0x1E99 +#define PID_CELLVOLTAGE_CELL_91 0x1E9A +#define PID_CELLVOLTAGE_CELL_92 0x1E9B +#define PID_CELLVOLTAGE_CELL_93 0x1E9C +#define PID_CELLVOLTAGE_CELL_94 0x1E9D +#define PID_CELLVOLTAGE_CELL_95 0x1E9E +#define PID_CELLVOLTAGE_CELL_96 0x1E9F +#define PID_CELLVOLTAGE_CELL_97 0x1EA0 +#define PID_CELLVOLTAGE_CELL_98 0x1EA1 +#define PID_CELLVOLTAGE_CELL_99 0x1EA2 +#define PID_CELLVOLTAGE_CELL_100 0x1EA3 +#define PID_CELLVOLTAGE_CELL_101 0x1EA4 +#define PID_CELLVOLTAGE_CELL_102 0x1EA5 +#define PID_CELLVOLTAGE_CELL_103 0x1EA6 +#define PID_CELLVOLTAGE_CELL_104 0x1EA7 +#define PID_CELLVOLTAGE_CELL_105 0x1EA8 +#define PID_CELLVOLTAGE_CELL_106 0x1EA9 +#define PID_CELLVOLTAGE_CELL_107 0x1EAA +#define PID_CELLVOLTAGE_CELL_108 0x1EAB + +void setup_battery(void); +void transmit_can(CAN_frame* tx_frame, int interface); + +#endif diff --git a/Software/src/datalayer/datalayer_extended.h b/Software/src/datalayer/datalayer_extended.h index 7a9c2cd1..e4aa0c02 100644 --- a/Software/src/datalayer/datalayer_extended.h +++ b/Software/src/datalayer/datalayer_extended.h @@ -278,6 +278,78 @@ typedef struct { } DATALAYER_INFO_NISSAN_LEAF; +typedef struct { + /** uint8_t */ + /** Service disconnect switch status */ + bool SDSW = 0; + /** uint8_t */ + /** Pilotline status */ + bool pilotline = 0; + /** uint8_t */ + /** Transportation mode status */ + bool transportmode = 0; + /** uint8_t */ + /** Componentprotection mode status */ + bool componentprotection = 0; + /** uint8_t */ + /** Shutdown status */ + bool shutdown_active = 0; + /** uint8_t */ + /** Battery heating status */ + bool battery_heating = 0; + /** uint8_t */ + /** All realtime_ warnings have same enumeration, 0 = no fault, 1 = error level 1, 2 error level 2, 3 error level 3 */ + uint8_t rt_overcurrent = 0; + uint8_t rt_CAN_fault = 0; + uint8_t rt_overcharge = 0; + uint8_t rt_SOC_high = 0; + uint8_t rt_SOC_low = 0; + uint8_t rt_SOC_jumping = 0; + uint8_t rt_temp_difference = 0; + uint8_t rt_cell_overtemp = 0; + uint8_t rt_cell_undertemp = 0; + uint8_t rt_battery_overvolt = 0; + uint8_t rt_battery_undervol = 0; + uint8_t rt_cell_overvolt = 0; + uint8_t rt_cell_undervol = 0; + uint8_t rt_cell_imbalance = 0; + uint8_t rt_battery_unathorized = 0; + /** uint8_t */ + /** HVIL status, 0 = Init, 1 = Closed, 2 = Open!, 3 = Fault */ + uint8_t HVIL = 0; + /** uint8_t */ + /** 0 = HV inactive, 1 = HV active, 2 = Balancing, 3 = Extern charging, 4 = AC charging, 5 = Battery error, 6 = DC charging, 7 = init */ + uint8_t BMS_mode = 0; + /** uint8_t */ + /** 1 = Battery display, 4 = Battery display OK, 4 = Display battery charging, 6 = Display battery check, 7 = Fault */ + uint8_t battery_diagnostic = 0; + /** uint8_t */ + /** 0 = init, 1 = no open HV line detected, 2 = open HV line , 3 = fault */ + uint8_t status_HV_line = 0; + /** uint8_t */ + /** 0 = OK, 1 = Not OK, 0x06 = init, 0x07 = fault */ + uint8_t warning_support = 0; + /** uint32_t */ + /** Isolation resistance in kOhm */ + uint32_t isolation_resistance = 0; + /** uint8_t */ + /** 0=Init, 1=BMS intermediate circuit voltage-free (U_Zwkr < 20V), 2=BMS intermediate circuit not voltage-free (U_Zwkr >/= 25V, hysteresis), 3=Error */ + uint8_t BMS_status_voltage_free = 0; + /** uint8_t */ + /** 0 Component_IO, 1 Restricted_CompFkt_Isoerror_I, 2 Restricted_CompFkt_Isoerror_II, 3 Restricted_CompFkt_Interlock, 4 Restricted_CompFkt_SD, 5 Restricted_CompFkt_Performance red, 6 = No component function, 7 = Init */ + uint8_t BMS_error_status = 0; + /** uint8_t */ + /** 0 init, 1 closed, 2 open, 3 fault */ + uint8_t BMS_Kl30c_Status = 0; + /** bool */ + /** true if BMS requests error/warning light */ + bool BMS_OBD_MIL = 0; + bool BMS_error_lamp_req = 0; + bool BMS_warning_lamp_req = 0; + int32_t BMS_voltage_intermediate_dV = 0; + int32_t BMS_voltage_dV = 0; +} DATALAYER_INFO_MEB; + typedef struct { /** uint16_t */ /** Values WIP*/ @@ -332,6 +404,7 @@ class DataLayerExtended { DATALAYER_INFO_CELLPOWER cellpower; DATALAYER_INFO_TESLA tesla; DATALAYER_INFO_NISSAN_LEAF nissanleaf; + DATALAYER_INFO_MEB meb; DATALAYER_INFO_ZOE_PH2 zoePH2; }; diff --git a/Software/src/devboard/safety/safety.cpp b/Software/src/devboard/safety/safety.cpp index ee72af88..4c557246 100644 --- a/Software/src/devboard/safety/safety.cpp +++ b/Software/src/devboard/safety/safety.cpp @@ -97,7 +97,7 @@ void update_machineryprotection() { clear_event(EVENT_SOH_LOW); } -#if !defined(PYLON_BATTERY) && !defined(RENAULT_TWIZY_BATTERY) +#ifdef NISSAN_LEAF_BATTERY // Check if SOC% is plausible if (datalayer.battery.status.voltage_dV > (datalayer.battery.info.max_design_voltage_dV - @@ -108,7 +108,7 @@ void update_machineryprotection() { clear_event(EVENT_SOC_PLAUSIBILITY_ERROR); } } -#endif +#endif //NISSAN_LEAF_BATTERY // Check diff between highest and lowest cell cell_deviation_mV = (datalayer.battery.status.cell_max_voltage_mV - datalayer.battery.status.cell_min_voltage_mV); diff --git a/Software/src/devboard/utils/events.cpp b/Software/src/devboard/utils/events.cpp index 79584885..b0c3e285 100644 --- a/Software/src/devboard/utils/events.cpp +++ b/Software/src/devboard/utils/events.cpp @@ -144,6 +144,7 @@ void init_events(void) { events.entries[EVENT_CANMCP_INIT_FAILURE].level = EVENT_LEVEL_WARNING; events.entries[EVENT_CANFD_BUFFER_FULL].level = EVENT_LEVEL_WARNING; events.entries[EVENT_CAN_OVERRUN].level = EVENT_LEVEL_INFO; + events.entries[EVENT_CANFD_RX_OVERRUN].level = EVENT_LEVEL_WARNING; events.entries[EVENT_CAN_RX_FAILURE].level = EVENT_LEVEL_ERROR; events.entries[EVENT_CAN2_RX_FAILURE].level = EVENT_LEVEL_WARNING; events.entries[EVENT_CANFD_RX_FAILURE].level = EVENT_LEVEL_ERROR; @@ -270,6 +271,8 @@ const char* get_event_message_string(EVENTS_ENUM_TYPE event) { return "CAN-FD buffer overflowed. Some CAN messages were not sent. Contact developers."; case EVENT_CAN_OVERRUN: return "CAN message failed to send within defined time. Contact developers, CPU load might be too high."; + case EVENT_CANFD_RX_OVERRUN: + return "CAN-FD failed to receive all messages from CAN bus. Contact developers, CPU load might be too high."; case EVENT_CAN_RX_FAILURE: return "No CAN communication detected for 60s. Shutting down battery control."; case EVENT_CAN2_RX_FAILURE: diff --git a/Software/src/devboard/utils/events.h b/Software/src/devboard/utils/events.h index dcdcfd6a..b50d7de4 100644 --- a/Software/src/devboard/utils/events.h +++ b/Software/src/devboard/utils/events.h @@ -30,6 +30,7 @@ XX(EVENT_CANMCP_INIT_FAILURE) \ XX(EVENT_CANFD_BUFFER_FULL) \ XX(EVENT_CAN_OVERRUN) \ + XX(EVENT_CANFD_RX_OVERRUN) \ XX(EVENT_CAN_RX_FAILURE) \ XX(EVENT_CAN2_RX_FAILURE) \ XX(EVENT_CANFD_RX_FAILURE) \ diff --git a/Software/src/devboard/utils/types.h b/Software/src/devboard/utils/types.h index 5af0845f..126aff26 100644 --- a/Software/src/devboard/utils/types.h +++ b/Software/src/devboard/utils/types.h @@ -13,7 +13,9 @@ enum led_color { GREEN, YELLOW, RED, BLUE, RGB }; #define INTERVAL_10_MS 10 #define INTERVAL_20_MS 20 #define INTERVAL_30_MS 30 +#define INTERVAL_40_MS 40 #define INTERVAL_50_MS 50 +#define INTERVAL_70_MS 70 #define INTERVAL_100_MS 100 #define INTERVAL_200_MS 200 #define INTERVAL_250_MS 250 diff --git a/Software/src/devboard/webserver/advanced_battery_html.cpp b/Software/src/devboard/webserver/advanced_battery_html.cpp index b3cba77f..5806bb32 100644 --- a/Software/src/devboard/webserver/advanced_battery_html.cpp +++ b/Software/src/devboard/webserver/advanced_battery_html.cpp @@ -420,6 +420,205 @@ String advanced_battery_processor(const String& var) { content += "

Challenge failed: " + String(datalayer_extended.nissanleaf.challengeFailed) + "

"; #endif +#ifdef MEB_BATTERY + content += datalayer_extended.meb.SDSW ? "

Service disconnect switch: Missing!

" + : "

Service disconnect switch: OK

"; + content += datalayer_extended.meb.pilotline ? "

Pilotline: Open!

" : "

Pilotline: OK

"; + content += datalayer_extended.meb.transportmode ? "

Transportmode: Locked!

" : "

Transportmode: OK

"; + content += datalayer_extended.meb.shutdown_active ? "

Shutdown: Active!

" : "

Shutdown: No

"; + content += datalayer_extended.meb.componentprotection ? "

Component protection: Active!

" + : "

Component protection: No

"; + content += "

HVIL status: "; + switch (datalayer_extended.meb.HVIL) { + case 0: + content += String("Init"); + break; + case 1: + content += String("Closed"); + break; + case 2: + content += String("Open!"); + break; + case 3: + content += String("Fault"); + break; + default: + content += String("?"); + } + content += "

KL30C status: "; + switch (datalayer_extended.meb.BMS_Kl30c_Status) { + case 0: + content += String("Init"); + break; + case 1: + content += String("Closed"); + break; + case 2: + content += String("Open!"); + break; + case 3: + content += String("Fault"); + break; + default: + content += String("?"); + } + content += "

BMS mode: "; + switch (datalayer_extended.meb.BMS_mode) { + case 0: + content += String("HV inactive"); + break; + case 1: + content += String("HV active"); + break; + case 2: + content += String("Balancing"); + break; + case 3: + content += String("Extern charging"); + break; + case 4: + content += String("AC charging"); + break; + case 5: + content += String("Battery error"); + break; + case 6: + content += String("DC charging"); + break; + case 7: + content += String("Init"); + break; + default: + content += String("?"); + } + content += "

Diagnostic: "; + switch (datalayer_extended.meb.battery_diagnostic) { + case 0: + content += String("Init"); + break; + case 1: + content += String("Battery display"); + break; + case 4: + content += String("Battery display OK"); + break; + case 6: + content += String("Battery display check"); + break; + case 7: + content += String("Fault"); + break; + default: + content += String("?"); + } + content += "

HV line status: "; + switch (datalayer_extended.meb.status_HV_line) { + case 0: + content += String("Init"); + break; + case 1: + content += String("No open HV line detected"); + break; + case 2: + content += String("Open HV line"); + break; + case 3: + content += String("Fault"); + break; + default: + content += String("? ") + String(datalayer_extended.meb.status_HV_line); + } + content += "

Warning support: "; + switch (datalayer_extended.meb.warning_support) { + case 0: + content += String("OK"); + break; + case 1: + content += String("Not OK"); + break; + case 6: + content += String("Init"); + break; + case 7: + content += String("Fault"); + break; + default: + content += String("?"); + } + content += "

Interm. Voltage (" + String(datalayer_extended.meb.BMS_voltage_intermediate_dV / 10.0, 1) + + "V) status: "; + switch (datalayer_extended.meb.BMS_status_voltage_free) { + case 0: + content += String("Init"); + break; + case 1: + content += String("BMS interm circuit voltage free (U<20V)"); + break; + case 2: + content += String("BMS interm circuit not voltage free (U >= 25V)"); + break; + case 3: + content += String("Error"); + break; + default: + content += String("?"); + } + content += "

BMS error status: "; + switch (datalayer_extended.meb.BMS_error_status) { + case 0: + content += String("Component IO"); + break; + case 1: + content += String("Iso Error 1"); + break; + case 2: + content += String("Iso Error 2"); + break; + case 3: + content += String("Interlock"); + break; + case 4: + content += String("SD"); + break; + case 5: + content += String("Performance red"); + break; + case 6: + content += String("No component function"); + break; + case 7: + content += String("Init"); + break; + default: + content += String("?"); + } + content += "

BMS voltage: " + String(datalayer_extended.meb.BMS_voltage_dV / 10.0, 1) + "

"; + content += datalayer_extended.meb.BMS_OBD_MIL ? "

OBD MIL: ON!

" : "

OBD MIL: Off

"; + content += + datalayer_extended.meb.BMS_error_lamp_req ? "

Red error lamp: ON!

" : "

Red error lamp: Off

"; + content += datalayer_extended.meb.BMS_warning_lamp_req ? "

Yellow warning lamp: ON!

" + : "

Yellow warning lamp: Off

"; + content += "

Isolation resistance: " + String(datalayer_extended.meb.isolation_resistance) + " kOhm

"; + content += + datalayer_extended.meb.battery_heating ? "

Battery heating: Active!

" : "

Battery heating: Off

"; + const char* rt_enum[] = {"No", "Error level 1", "Error level 2", "Error level 3"}; + content += "

Overcurrent: " + String(rt_enum[datalayer_extended.meb.rt_overcurrent]) + "

"; + content += "

CAN fault: " + String(rt_enum[datalayer_extended.meb.rt_CAN_fault]) + "

"; + content += "

Overcharged: " + String(rt_enum[datalayer_extended.meb.rt_overcharge]) + "

"; + content += "

SOC too high: " + String(rt_enum[datalayer_extended.meb.rt_SOC_high]) + "

"; + content += "

SOC too low: " + String(rt_enum[datalayer_extended.meb.rt_SOC_low]) + "

"; + content += "

SOC jumping: " + String(rt_enum[datalayer_extended.meb.rt_SOC_jumping]) + "

"; + content += "

Temp difference: " + String(rt_enum[datalayer_extended.meb.rt_temp_difference]) + "

"; + content += "

Cell overtemp: " + String(rt_enum[datalayer_extended.meb.rt_cell_overtemp]) + "

"; + content += "

Cell undertemp: " + String(rt_enum[datalayer_extended.meb.rt_cell_undertemp]) + "

"; + content += "

Battery overvoltage: " + String(rt_enum[datalayer_extended.meb.rt_battery_overvolt]) + "

"; + content += "

Battery undervoltage: " + String(rt_enum[datalayer_extended.meb.rt_battery_undervol]) + "

"; + content += "

Cell overvoltage: " + String(rt_enum[datalayer_extended.meb.rt_cell_overvolt]) + "

"; + content += "

Cell undervoltage: " + String(rt_enum[datalayer_extended.meb.rt_cell_undervol]) + "

"; + content += "

Cell imbalance: " + String(rt_enum[datalayer_extended.meb.rt_cell_imbalance]) + "

"; + content += "

Battery unathorized: " + String(rt_enum[datalayer_extended.meb.rt_battery_unathorized]) + "

"; +#endif //MEB_BATTERY + #ifdef RENAULT_ZOE_GEN2_BATTERY content += "

soc: " + String(datalayer_extended.zoePH2.battery_soc) + "

"; content += "

usable soc: " + String(datalayer_extended.zoePH2.battery_usable_soc) + "

"; @@ -467,8 +666,9 @@ String advanced_battery_processor(const String& var) { content += "

soc max: " + String(datalayer_extended.zoePH2.battery_soc_max) + "

"; #endif //RENAULT_ZOE_GEN2_BATTERY -#if !defined(TESLA_BATTERY) && !defined(NISSAN_LEAF_BATTERY) && !defined(BMW_I3_BATTERY) && \ - !defined(BYD_ATTO_3_BATTERY) && !defined(RENAULT_ZOE_GEN2_BATTERY) && !defined(CELLPOWER_BMS) +#if !defined(TESLA_BATTERY) && !defined(NISSAN_LEAF_BATTERY) && !defined(BMW_I3_BATTERY) && \ + !defined(BYD_ATTO_3_BATTERY) && !defined(RENAULT_ZOE_GEN2_BATTERY) && !defined(CELLPOWER_BMS) && \ + !defined(MEB_BATTERY) //Only the listed types have extra info content += "No extra information available for this battery type"; #endif diff --git a/Software/src/devboard/webserver/events_html.cpp b/Software/src/devboard/webserver/events_html.cpp index 18915e6a..3cb3edbe 100644 --- a/Software/src/devboard/webserver/events_html.cpp +++ b/Software/src/devboard/webserver/events_html.cpp @@ -60,8 +60,8 @@ String events_processor(const String& var) { order_events.clear(); content.concat(FPSTR(EVENTS_HTML_END)); return content; - return String(); } + return String(); } /* Script for displaying event log before it gets minified From 025a9feb02cbc53d15dff9116444bc31e53e87b1 Mon Sep 17 00:00:00 2001 From: mvgalen Date: Mon, 2 Dec 2024 22:31:25 +0100 Subject: [PATCH 204/210] Improvement: General MEB improvements (#654) * Update MEB-BATTERY.cpp - Only use values form CAN message if BMS is not in init state. - Quickly determine number of cell of the pack - Use broadcast battery_voltage and battery_SOC - Set default SOC to 0.05% to prevent battery empty event on startup * Add BMS_HVIL_STATUS as source for HVIL_FAILURE event --- Software/src/battery/MEB-BATTERY.cpp | 63 +++++++++++++++++++--------- 1 file changed, 44 insertions(+), 19 deletions(-) diff --git a/Software/src/battery/MEB-BATTERY.cpp b/Software/src/battery/MEB-BATTERY.cpp index c6dc918c..643824ee 100644 --- a/Software/src/battery/MEB-BATTERY.cpp +++ b/Software/src/battery/MEB-BATTERY.cpp @@ -8,12 +8,15 @@ /* TODO list -- Get contactors closing -- What CAN messages needs to be sent towards the battery to keep it alive - Check value mappings on the PID polls -- Check value mappings on the constantly broadcasted messages - Check all TODO:s in the code - 0x1B000044 & 1B00008F seems to be missing from logs? (Classic CAN) +- Scaled remaining capacity, should take already scaled total capacity into account, or we +should undo the scaling on the total capacity (which is calculated from the ah value now, +which is scaled already). +- Investigate why opening and then closing contactors from webpage does not always work +- Invertigate why contactors don't close when lilygo and battery are powered on simultaneously -> timeout on can msgs triggers to late, reset when open contactors is executed +- Find out how to get the battery in balancing mode */ /* Do not change code below unless you are sure what you are doing */ @@ -38,7 +41,8 @@ static uint8_t counter_040 = 0; static uint8_t counter_0F7 = 0; static uint8_t counter_3b5 = 0; -static uint32_t poll_pid = 0; +static uint32_t poll_pid = PID_CELLVOLTAGE_CELL_85; // We start here to quickly determine the cell size of the pack. +static bool nof_cells_determined = false; static uint32_t pid_reply = 0; static uint16_t battery_soc_polled = 0; static uint16_t battery_voltage_polled = 1480; @@ -74,7 +78,7 @@ static uint16_t BMS_current = 16300; static bool BMS_fault_emergency_shutdown_crash = false; //Error: Safety-critical error (crash detection) Battery contactors are already opened / will be opened immediately Signal is read directly by the EMS and initiates an AKS of the PWR and an active discharge of the DC link static uint32_t BMS_voltage_intermediate = 0; -static uint32_t BMS_voltage = 0; +static uint32_t BMS_voltage = 1480; static uint8_t BMS_status_voltage_free = 0; //0=Init, 1=BMS intermediate circuit voltage-free (U_Zwkr < 20V), 2=BMS intermediate circuit not voltage-free (U_Zwkr >/= 25V, hysteresis), 3=Error static bool BMS_OBD_MIL = false; @@ -96,7 +100,7 @@ static uint16_t max_discharge_power_watt = 0; static uint16_t max_discharge_current_amp = 0; static uint16_t max_charge_power_watt = 0; static uint16_t max_charge_current_amp = 0; -static uint16_t battery_SOC = 0; +static uint16_t battery_SOC = 1; static uint16_t usable_energy_amount_Wh = 0; static uint8_t status_HV_line = 0; //0 init, 1 No open HV line, 2 open HV line detected, 3 fault static uint8_t warning_support = 0; @@ -514,12 +518,12 @@ uint8_t vw_crc_calc(uint8_t* inputBytes, uint8_t length, uint16_t address) { void update_values_battery() { //This function maps all the values fetched via CAN to the correct parameters used for modbus - datalayer.battery.status.real_soc = battery_soc_polled * 10; + datalayer.battery.status.real_soc = battery_SOC * 5; //*0.05*100 battery_soc_polled * 10; //Alternatively use battery_SOC for more precision datalayer.battery.status.soh_pptt; - datalayer.battery.status.voltage_dV = battery_voltage_polled * 2.5; + datalayer.battery.status.voltage_dV = BMS_voltage * 2.5; // *0.25*10 datalayer.battery.status.current_dA = (BMS_current / 10) - 1630; @@ -570,7 +574,7 @@ void update_values_battery() { //This function maps all the values fetched via } else { clear_event(EVENT_HVIL_FAILURE); } - if (pilotline_open) { + if (pilotline_open || BMS_HVIL_status == 2) { set_event(EVENT_HVIL_FAILURE, 2); } else { clear_event(EVENT_HVIL_FAILURE); @@ -649,10 +653,12 @@ void receive_can_battery(CAN_frame rx_frame) { break; case 0x12DD54D1: // BMS 100ms datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; - battery_SOC = ((rx_frame.data.u8[3] & 0x0F) << 7) | (rx_frame.data.u8[2] >> 1); //*0.05 - usable_energy_amount_Wh = (rx_frame.data.u8[7] << 8) | rx_frame.data.u8[6]; //*5 - power_discharge_percentage = ((rx_frame.data.u8[4] & 0x3F) << 4) | rx_frame.data.u8[3] >> 4; //*0.2 - power_charge_percentage = (rx_frame.data.u8[5] << 2) | rx_frame.data.u8[4] >> 6; //*0.2 + if (rx_frame.data.u8[6] != 0xFE || rx_frame.data.u8[7] != 0xFF) { // Init state, values below invalid + battery_SOC = ((rx_frame.data.u8[3] & 0x0F) << 7) | (rx_frame.data.u8[2] >> 1); //*0.05 + usable_energy_amount_Wh = (rx_frame.data.u8[7] << 8) | rx_frame.data.u8[6]; //*5 + power_discharge_percentage = ((rx_frame.data.u8[4] & 0x3F) << 4) | rx_frame.data.u8[3] >> 4; //*0.2 + power_charge_percentage = (rx_frame.data.u8[5] << 2) | rx_frame.data.u8[4] >> 6; //*0.2 + } status_HV_line = ((rx_frame.data.u8[2] & 0x01) << 1) | rx_frame.data.u8[1] >> 7; warning_support = (rx_frame.data.u8[1] & 0x70) >> 4; break; @@ -939,10 +945,12 @@ void receive_can_battery(CAN_frame rx_frame) { BMS_status_voltage_free = (rx_frame.data.u8[1] & 0xC0) >> 6; BMS_OBD_MIL = (rx_frame.data.u8[2] & 0x01); BMS_error_status = (rx_frame.data.u8[2] & 0x70) >> 4; - BMS_capacity_ah = ((rx_frame.data.u8[4] & 0x03) << 9) | (rx_frame.data.u8[3] << 1) | (rx_frame.data.u8[2] >> 7); BMS_error_lamp_req = (rx_frame.data.u8[4] & 0x04) >> 2; BMS_warning_lamp_req = (rx_frame.data.u8[4] & 0x08) >> 3; BMS_Kl30c_Status = (rx_frame.data.u8[4] & 0x30) >> 4; + if (BMS_Kl30c_Status != 0) { // init state + BMS_capacity_ah = ((rx_frame.data.u8[4] & 0x03) << 9) | (rx_frame.data.u8[3] << 1) | (rx_frame.data.u8[2] >> 7); + } break; case 0x5CA: // BMS 500ms BMS_5CA_CRC = rx_frame.data.u8[0]; // Can be used to check CAN signal integrity later on @@ -977,10 +985,12 @@ void receive_can_battery(CAN_frame rx_frame) { BMS_error_shutdown = (rx_frame.data.u8[2] & 0x20) >> 5; BMS_error_shutdown_request = (rx_frame.data.u8[2] & 0x40) >> 6; BMS_fault_performance = (rx_frame.data.u8[2] & 0x80) >> 7; - BMS_current = ((rx_frame.data.u8[4] & 0x7F) << 8) | rx_frame.data.u8[3]; BMS_fault_emergency_shutdown_crash = (rx_frame.data.u8[4] & 0x80) >> 7; - BMS_voltage_intermediate = (((rx_frame.data.u8[6] & 0x0F) << 8) + (rx_frame.data.u8[5])); - BMS_voltage = ((rx_frame.data.u8[7] << 4) + ((rx_frame.data.u8[6] & 0xF0) >> 4)); + if (BMS_mode != 7) { // Init state, values below are invalid + BMS_current = ((rx_frame.data.u8[4] & 0x7F) << 8) | rx_frame.data.u8[3]; + BMS_voltage_intermediate = (((rx_frame.data.u8[6] & 0x0F) << 8) + (rx_frame.data.u8[5])); + BMS_voltage = ((rx_frame.data.u8[7] << 4) + ((rx_frame.data.u8[6] & 0xF0) >> 4)); + } break; case 0x1C42007B: // Reply from battery if (rx_frame.data.u8[0] == 0x10) { //PID header @@ -1277,6 +1287,7 @@ void receive_can_battery(CAN_frame rx_frame) { cellvoltages_polled[84] = (tempval + 1000); } else { // Cell 85 unavailable. We have a 84S battery (48kWh) datalayer.battery.info.number_of_cells = 84; + nof_cells_determined = true; datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_84S_DV; datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_84S_DV; } @@ -1356,6 +1367,7 @@ void receive_can_battery(CAN_frame rx_frame) { // Do nothing, we already identified it as 84S } else { datalayer.battery.info.number_of_cells = 96; + nof_cells_determined = true; datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_96S_DV; datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_96S_DV; } @@ -1423,6 +1435,8 @@ void receive_can_battery(CAN_frame rx_frame) { break; case PID_CELLVOLTAGE_CELL_108: tempval = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + nof_cells_determined = true; // This is placed outside of the if, to make + // sure we only take the shortcuts to determine the number of cells once. if (tempval != 0xFFE) { cellvoltages_polled[107] = (tempval + 1000); datalayer.battery.info.number_of_cells = 108; @@ -1948,7 +1962,15 @@ void send_can_battery() { break; case PID_CELLVOLTAGE_CELL_84: MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_84; - poll_pid = PID_CELLVOLTAGE_CELL_85; + if (datalayer.battery.info.number_of_cells > 84) { + if (nof_cells_determined) { + poll_pid = PID_CELLVOLTAGE_CELL_85; + } else { + poll_pid = PID_CELLVOLTAGE_CELL_97; + } + } else { + poll_pid = PID_SOC; + } break; case PID_CELLVOLTAGE_CELL_85: MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_85; @@ -1996,7 +2018,10 @@ void send_can_battery() { break; case PID_CELLVOLTAGE_CELL_96: MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_96; - poll_pid = PID_CELLVOLTAGE_CELL_97; + if (datalayer.battery.info.number_of_cells > 96) + poll_pid = PID_CELLVOLTAGE_CELL_97; + else + poll_pid = PID_SOC; break; case PID_CELLVOLTAGE_CELL_97: MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_97; From 3d1a3eb2c3bbc65e077e835bd2c9806681938f70 Mon Sep 17 00:00:00 2001 From: mvgalen Date: Wed, 4 Dec 2024 10:26:21 +0100 Subject: [PATCH 205/210] Change web based CAN log file format to CANdump (#655) * Update web page CAN logging to CANdump format * Improve performance of web based CAN logging by removing unnecessary strcat and strlen calls. --- Software/Software.ino | 46 +++++++++++++----------------- Software/src/datalayer/datalayer.h | 1 + 2 files changed, 21 insertions(+), 26 deletions(-) diff --git a/Software/Software.ino b/Software/Software.ino index 1994978f..79e99bf8 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -642,44 +642,38 @@ void print_can_frame(CAN_frame frame, frameDirection msgDir) { #endif //#DEBUG_CAN_DATA if (datalayer.system.info.can_logging_active) { // If user clicked on CAN Logging page in webserver, start recording + char* message_string = datalayer.system.info.logged_can_messages; + int offset = datalayer.system.info.logged_can_messages_offset; // Keeps track of the current position in the buffer + size_t message_string_size = sizeof(datalayer.system.info.logged_can_messages); - char message_string[128]; // Buffer to hold the message string - int offset = 0; // Keeps track of the current position in the buffer - + if (offset + 128 > sizeof(datalayer.system.info.logged_can_messages)) { + // Not enough space, reset and start from the beginning + offset = 0; + } + unsigned long currentTime = millis(); // Add timestamp - offset += snprintf(message_string + offset, sizeof(message_string) - offset, "%lu ", millis()); + offset += snprintf(message_string + offset, message_string_size - offset, "(%lu.%03lu) ", currentTime / 1000, + currentTime % 1000); - // Add direction + // Add direction. The 0 and 1 after RX and TX ensures that SavvyCAN puts TX and RX in a different bus. offset += - snprintf(message_string + offset, sizeof(message_string) - offset, "%s ", (msgDir == MSG_RX) ? "RX" : "TX"); + snprintf(message_string + offset, message_string_size - offset, "%s ", (msgDir == MSG_RX) ? "RX0" : "TX1"); // Add ID and DLC - offset += snprintf(message_string + offset, sizeof(message_string) - offset, "%X %u ", frame.ID, frame.DLC); + offset += snprintf(message_string + offset, message_string_size - offset, "%X [%u] ", frame.ID, frame.DLC); // Add data bytes for (uint8_t i = 0; i < frame.DLC; i++) { - offset += snprintf(message_string + offset, sizeof(message_string) - offset, "%s%X ", - frame.data.u8[i] < 16 ? "0" : "", frame.data.u8[i]); + if (i < frame.DLC - 1) { + offset += snprintf(message_string + offset, message_string_size - offset, "%02X ", frame.data.u8[i]); + } else { + offset += snprintf(message_string + offset, message_string_size - offset, "%02X", frame.data.u8[i]); + } } // Add linebreak - offset += snprintf(message_string + offset, sizeof(message_string) - offset, "\n"); - - // Ensure the string is null-terminated - message_string[sizeof(message_string) - 1] = '\0'; - - // Append the message string to the system info structure - size_t current_len = - strnlen(datalayer.system.info.logged_can_messages, sizeof(datalayer.system.info.logged_can_messages)); - size_t available_space = - sizeof(datalayer.system.info.logged_can_messages) - current_len - 1; // Space left for new data - - if (available_space < strlen(message_string) + 1) { - // Not enough space, reset and start from the beginning - current_len = 0; - datalayer.system.info.logged_can_messages[0] = '\0'; - } + offset += snprintf(message_string + offset, message_string_size - offset, "\n"); - strncat(datalayer.system.info.logged_can_messages, message_string, available_space); + datalayer.system.info.logged_can_messages_offset = offset; // Update offset in buffer } } diff --git a/Software/src/datalayer/datalayer.h b/Software/src/datalayer/datalayer.h index 81814336..e6de68ce 100644 --- a/Software/src/datalayer/datalayer.h +++ b/Software/src/datalayer/datalayer.h @@ -133,6 +133,7 @@ typedef struct { char inverter_protocol[64] = {0}; /** array with incoming CAN messages, for displaying on webserver */ char logged_can_messages[15000] = {0}; + size_t logged_can_messages_offset = 0; /** bool, determines if CAN messages should be logged for webserver */ bool can_logging_active = false; From e900888d400a79359d97030ccd6d0811082b074e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Sun, 8 Dec 2024 11:45:46 +0200 Subject: [PATCH 206/210] Update Software.ino --- Software/Software.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Software/Software.ino b/Software/Software.ino index 79e99bf8..9bd0225e 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -53,7 +53,7 @@ Preferences settings; // Store user settings // The current software version, shown on webserver -const char* version_number = "7.9.dev"; +const char* version_number = "7.9.0"; // Interval settings uint16_t intervalUpdateValues = INTERVAL_1_S; // Interval at which to update inverter values / Modbus registers From 30507e78bd5f733ef3bf6aeda5a51cb7e118dd27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Sun, 8 Dec 2024 11:53:07 +0200 Subject: [PATCH 207/210] Update Software.ino --- Software/Software.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Software/Software.ino b/Software/Software.ino index 9bd0225e..df46dca0 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -53,7 +53,7 @@ Preferences settings; // Store user settings // The current software version, shown on webserver -const char* version_number = "7.9.0"; +const char* version_number = "8.0.dev"; // Interval settings uint16_t intervalUpdateValues = INTERVAL_1_S; // Interval at which to update inverter values / Modbus registers From a467575a57916d3f8b3efb59f51b434fae6f3ab2 Mon Sep 17 00:00:00 2001 From: Bryant Eadon Date: Wed, 11 Dec 2024 10:07:56 -0800 Subject: [PATCH 208/210] Update cellvoltage writing for second battery This fixes issue 663 --- Software/src/battery/BMW-I3-BATTERY.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Software/src/battery/BMW-I3-BATTERY.cpp b/Software/src/battery/BMW-I3-BATTERY.cpp index e43eaa5a..cf535f5f 100644 --- a/Software/src/battery/BMW-I3-BATTERY.cpp +++ b/Software/src/battery/BMW-I3-BATTERY.cpp @@ -785,7 +785,7 @@ void receive_can_battery2(CAN_frame rx_frame) { datalayer.battery2.status.cell_voltages_mV[3] = ((rx_frame.data.u8[4] * 10) + 1800); datalayer.battery2.status.cell_voltages_mV[4] = ((rx_frame.data.u8[5] * 10) + 1800); datalayer.battery2.status.cell_voltages_mV[5] = ((rx_frame.data.u8[6] * 10) + 1800); - datalayer.battery2.status.cell_voltages_mV[5] = ((rx_frame.data.u8[7] * 10) + 1800); + datalayer.battery2.status.cell_voltages_mV[6] = ((rx_frame.data.u8[7] * 10) + 1800); } break; case 0x430: //BMS [1s] - Charging status of high-voltage battery - 2 From 3e781999c07feee9554b614ee7bab681c457bddc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Thu, 12 Dec 2024 12:02:32 +0200 Subject: [PATCH 209/210] Update scaling on real SOC There was one decimal too much --- Software/src/battery/BYD-ATTO-3-BATTERY.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Software/src/battery/BYD-ATTO-3-BATTERY.cpp b/Software/src/battery/BYD-ATTO-3-BATTERY.cpp index 5ad4220f..eaa01bae 100644 --- a/Software/src/battery/BYD-ATTO-3-BATTERY.cpp +++ b/Software/src/battery/BYD-ATTO-3-BATTERY.cpp @@ -118,7 +118,7 @@ void update_values_battery() { //This function maps all the values fetched via datalayer.battery.status.real_soc = estimateSOC(datalayer.battery.status.voltage_dV); SOC_method = ESTIMATED; #else // Pack is not crashed, we can use periodically transmitted SOC - datalayer.battery.status.real_soc = battery_highprecision_SOC * 100; + datalayer.battery.status.real_soc = battery_highprecision_SOC * 10; SOC_method = MEASURED; #endif From 30a6eef3269c169dacbe44edf57f603ef3ef24e8 Mon Sep 17 00:00:00 2001 From: lenvm Date: Thu, 12 Dec 2024 21:56:52 +0100 Subject: [PATCH 210/210] sort battery and inverter options lexicographically, and add missing options to workflows --- .github/workflows/compile-all-batteries.yml | 3 ++- .github/workflows/compile-all-combinations.yml | 16 +++++++++++++++- .github/workflows/compile-all-inverters.yml | 13 ++++--------- Software/USER_SETTINGS.h | 14 +++++++------- 4 files changed, 28 insertions(+), 18 deletions(-) diff --git a/.github/workflows/compile-all-batteries.yml b/.github/workflows/compile-all-batteries.yml index 7f95c73d..49d083a0 100644 --- a/.github/workflows/compile-all-batteries.yml +++ b/.github/workflows/compile-all-batteries.yml @@ -40,8 +40,8 @@ jobs: - CHADEMO_BATTERY - IMIEV_CZERO_ION_BATTERY - JAGUAR_IPACE_BATTERY - - KIA_HYUNDAI_64_BATTERY - KIA_E_GMP_BATTERY + - KIA_HYUNDAI_64_BATTERY - KIA_HYUNDAI_HYBRID_BATTERY - MEB_BATTERY - MG_5_BATTERY @@ -55,6 +55,7 @@ jobs: - RENAULT_ZOE_GEN2_BATTERY - SANTA_FE_PHEV_BATTERY - TESLA_MODEL_3Y_BATTERY + - TESLA_MODEL_SX_BATTERY - VOLVO_SPA_BATTERY - TEST_FAKE_BATTERY - SERIAL_LINK_RECEIVER diff --git a/.github/workflows/compile-all-combinations.yml b/.github/workflows/compile-all-combinations.yml index c194e97a..91df0bbb 100644 --- a/.github/workflows/compile-all-combinations.yml +++ b/.github/workflows/compile-all-combinations.yml @@ -37,29 +37,43 @@ jobs: # These are the batteries for which the code will be compiled. battery: - BMW_I3_BATTERY + - BMW_IX_BATTERY - BYD_ATTO_3_BATTERY + - CELLPOWER_BMS - CHADEMO_BATTERY - IMIEV_CZERO_ION_BATTERY + - JAGUAR_IPACE_BATTERY + - KIA_E_GMP_BATTERY - KIA_HYUNDAI_64_BATTERY - KIA_HYUNDAI_HYBRID_BATTERY + - MEB_BATTERY + - MG_5_BATTERY - NISSAN_LEAF_BATTERY - PYLON_BATTERY - RJXZS_BMS + - RANGE_ROVER_PHEV_BATTERY - RENAULT_KANGOO_BATTERY + - RENAULT_TWIZY_BATTERY - RENAULT_ZOE_GEN1_BATTERY - RENAULT_ZOE_GEN2_BATTERY + - SANTA_FE_PHEV_BATTERY - TESLA_MODEL_3Y_BATTERY + - TESLA_MODEL_SX_BATTERY - VOLVO_SPA_BATTERY - TEST_FAKE_BATTERY # These are the emulated inverter communication protocols for which the code will be compiled. inverter: + - AFORE_CAN - BYD_CAN - BYD_KOSTAL_RS485 - - BYD_SMA - BYD_MODBUS + - BYD_SMA - FOXESS_CAN - PYLON_CAN + - PYLON_LV_CAN + - SCHNEIDER_CAN - SMA_CAN + - SMA_LV_CAN - SMA_TRIPOWER_CAN - SOFAR_CAN - SOLAX_CAN diff --git a/.github/workflows/compile-all-inverters.yml b/.github/workflows/compile-all-inverters.yml index af2ce379..fb4afbf3 100644 --- a/.github/workflows/compile-all-inverters.yml +++ b/.github/workflows/compile-all-inverters.yml @@ -33,30 +33,25 @@ jobs: #- esp32:esp32:esp32s3 # These are the batteries for which the code will be compiled. battery: -# - BMW_I3_BATTERY -# - CHADEMO_BATTERY -# - IMIEV_CZERO_ION_BATTERY -# - KIA_HYUNDAI_64_BATTERY - NISSAN_LEAF_BATTERY -# - RENAULT_ZOE_BATTERY -# - TESLA_MODEL_3Y_BATTERY # These are the emulated inverter communication protocols for which the code will be compiled. inverter: - AFORE_CAN - BYD_CAN - BYD_KOSTAL_RS485 - - BYD_SMA - BYD_MODBUS + - BYD_SMA - FOXESS_CAN - - PYLON_LV_CAN - PYLON_CAN + - PYLON_LV_CAN - SCHNEIDER_CAN - SMA_CAN + - SMA_LV_CAN - SMA_TRIPOWER_CAN - SOFAR_CAN - SOLAX_CAN - SERIAL_LINK_TRANSMITTER - - NISSANLEAF_CHARGER # Last element is not an inverter, but good to also test if charger compiles + - NISSANLEAF_CHARGER # Last element is not an inverter, but good to also test if the charger compiles # This is the platform GitHub will use to run our workflow. runs-on: ubuntu-latest diff --git a/Software/USER_SETTINGS.h b/Software/USER_SETTINGS.h index c846aa09..fafc2492 100644 --- a/Software/USER_SETTINGS.h +++ b/Software/USER_SETTINGS.h @@ -16,8 +16,8 @@ //#define CHADEMO_BATTERY //NOTE: inherently enables CONTACTOR_CONTROL below //#define IMIEV_CZERO_ION_BATTERY //#define JAGUAR_IPACE_BATTERY -//#define KIA_HYUNDAI_64_BATTERY //#define KIA_E_GMP_BATTERY +//#define KIA_HYUNDAI_64_BATTERY //#define KIA_HYUNDAI_HYBRID_BATTERY //#define MEB_BATTERY //#define MG_5_BATTERY @@ -30,18 +30,18 @@ //#define RENAULT_ZOE_GEN1_BATTERY //#define RENAULT_ZOE_GEN2_BATTERY //#define SANTA_FE_PHEV_BATTERY -//#define TESLA_MODEL_SX_BATTERY //#define TESLA_MODEL_3Y_BATTERY +//#define TESLA_MODEL_SX_BATTERY //#define VOLVO_SPA_BATTERY //#define TEST_FAKE_BATTERY //#define DOUBLE_BATTERY //Enable this line if you use two identical batteries at the same time (requires DUAL_CAN setup) /* Select inverter communication protocol. See Wiki for which to use with your inverter: https://github.com/dalathegreat/BYD-Battery-Emulator-For-Gen24/wiki */ -//#define AFORE_CAN //Enable this line to emulate an "Afore battery" over CAN bus -//#define BYD_CAN //Enable this line to emulate a "BYD Battery-Box Premium HVS" over CAN Bus -//#define BYD_SMA //Enable this line to emulate a SMA compatible "BYD Battery-Box HVS 10.2KW battery" over CAN bus -//#define BYD_MODBUS //Enable this line to emulate a "BYD 11kWh HVM battery" over Modbus RTU -//#define BYD_KOSTAL_RS485 //Enable this line to emulate a "BYD 11kWh HVM battery" over Kostal RS485 +//#define AFORE_CAN //Enable this line to emulate an "Afore battery" over CAN bus +//#define BYD_CAN //Enable this line to emulate a "BYD Battery-Box Premium HVS" over CAN Bus +//#define BYD_KOSTAL_RS485 //Enable this line to emulate a "BYD 11kWh HVM battery" over Kostal RS485 +//#define BYD_MODBUS //Enable this line to emulate a "BYD 11kWh HVM battery" over Modbus RTU +//#define BYD_SMA //Enable this line to emulate a SMA compatible "BYD Battery-Box HVS 10.2KW battery" over CAN bus //#define FOXESS_CAN //Enable this line to emulate a "HV2600/ECS4100 battery" over CAN bus //#define PYLON_LV_CAN //Enable this line to emulate a "48V Pylontech battery" over CAN bus //#define PYLON_CAN //Enable this line to emulate a "High Voltage Pylontech battery" over CAN bus