add battery folder

This commit is contained in:
lenvm 2023-11-05 23:16:49 +01:00
parent 40526f4029
commit a3583b6897
16 changed files with 29 additions and 29 deletions

View file

@ -0,0 +1,32 @@
#ifndef BATTERIES_H
#define BATTERIES_H
#ifdef BATTERY_TYPE_LEAF
#include "NISSAN-LEAF-BATTERY.h" //See this file for more LEAF battery settings
#endif
#ifdef TESLA_MODEL_3_BATTERY
#include "TESLA-MODEL-3-BATTERY.h" //See this file for more Tesla battery settings
#endif
#ifdef RENAULT_ZOE_BATTERY
#include "RENAULT-ZOE-BATTERY.h" //See this file for more Zoe battery settings
#endif
#ifdef BMW_I3_BATTERY
#include "BMW-I3-BATTERY.h" //See this file for more i3 battery settings
#endif
#ifdef IMIEV_ION_CZERO_BATTERY
#include "IMIEV-CZERO-ION-BATTERY.h" //See this file for more triplet battery settings
#endif
#ifdef KIA_HYUNDAI_64_BATTERY
#include "KIA-HYUNDAI-64-BATTERY.h" //See this file for more 64kWh battery settings
#endif
#ifdef CHADEMO
#include "CHADEMO-BATTERY.h" //See this file for more Chademo settings
#endif
#endif

View file

@ -0,0 +1,192 @@
#include "BMW-I3-BATTERY.h"
#include "../../ESP32CAN.h"
#include "../../CAN_config.h"
//TODO before using
// Map the final values in update_values_i3_battery, set some to static values if not available (current, discharge max , charge max)
// Check if I3 battery stays alive with only 10B and 512. If not, add 12F. If that doesn't help, add more from CAN log (ask Dala)
/* 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 previousMillis600 = 0; // will store last time a 600ms CAN Message was send
static const int interval20 = 20; // interval (ms) at which send CAN Messages
static const int interval600 = 600; // interval (ms) at which send CAN Messages
static uint8_t CANstillAlive = 12; //counter for checking if CAN is still alive
#define LB_MAX_SOC 1000 //BMS never goes over this value. We use this info to rescale SOC% sent to Inverter
#define LB_MIN_SOC 0 //BMS never goes below this value. We use this info to rescale SOC% sent to Inverter
CAN_frame_t BMW_10B = {.FIR = {.B = {.DLC = 3,.FF = CAN_frame_std,}},.MsgID = 0x10B,.data = {0xCD, 0x01, 0xFC}};
CAN_frame_t BMW_512 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_std,}},.MsgID = 0x512,.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12}}; //0x512 Network management edme VCU
//CAN_frame_t BMW_12F //Might be needed, Wake up ,VCU 100ms
//These CAN messages need to be sent towards the battery to keep it alive
static const uint8_t BMW_10B_0[15] = {0xCD,0x19,0x94,0x6D,0xE0,0x34,0x78,0xDB,0x97,0x43,0x0F,0xF6,0xBA,0x6E,0x81};
static const uint8_t BMW_10B_1[15] = {0x01,0x02,0x33,0x34,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x00};
static uint8_t BMW_10B_counter = 0;
static int16_t Battery_Current = 0;
static uint16_t Battery_Capacity_kWh = 0;
static uint16_t Voltage_Setpoint = 0;
static uint16_t Low_SOC = 0;
static uint16_t High_SOC = 0;
static uint16_t Display_SOC = 0;
static uint16_t Calculated_SOC = 0;
static uint16_t Battery_Volts = 0;
static uint16_t HVBatt_SOC = 0;
static uint16_t Battery_Status = 0;
static uint16_t DC_link = 0;
static int16_t Battery_Power = 0;
void update_values_i3_battery()
{ //This function maps all the values fetched via CAN to the correct parameters used for modbus
bms_status = ACTIVE; //Startout in active mode
//Calculate the SOC% value to send to inverter
Calculated_SOC = (Display_SOC * 10); //Increase decimal amount
Calculated_SOC = LB_MIN_SOC + (LB_MAX_SOC - LB_MIN_SOC) * (Calculated_SOC - MINPERCENTAGE) / (MAXPERCENTAGE - MINPERCENTAGE);
if (Calculated_SOC < 0)
{ //We are in the real SOC% range of 0-20%, always set SOC sent to inverter as 0%
Calculated_SOC = 0;
}
if (Calculated_SOC > 1000)
{ //We are in the real SOC% range of 80-100%, always set SOC sent to inverter as 100%
Calculated_SOC = 1000;
}
SOC = (Calculated_SOC * 10); //increase LB_SOC range from 0-100.0 -> 100.00
battery_voltage = Battery_Volts; //Unit V+1 (5000 = 500.0V)
battery_current = Battery_Current;
capacity_Wh = BATTERY_WH_MAX;
remaining_capacity_Wh = (Battery_Capacity_kWh * 1000);
if(SOC > 9900) //If Soc is over 99%, stop charging
{
max_target_charge_power = 0;
}
else
{
max_target_charge_power = 5000; //Hardcoded value for testing. TODO, read real value from battery when discovered
}
if(SOC < 500) //If Soc is under 5%, stop dicharging
{
max_target_discharge_power = 0;
}
else
{
max_target_discharge_power = 5000; //Hardcoded value for testing. TODO, read real value from battery when discovered
}
Battery_Power = (Battery_Current * (Battery_Volts/10));
stat_batt_power = Battery_Power; //TODO, is mapping OK?
temperature_min; //hardcoded to 5*C in startup, TODO, find from battery CAN later
temperature_max; //hardcoded to 6*C in startup, TODO, find from battery CAN later
/* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/
if(!CANstillAlive)
{
bms_status = FAULT;
Serial.println("No CAN communication detected for 60s. Shutting down battery control.");
}
else
{
CANstillAlive--;
}
#ifdef DEBUG_VIA_USB
Serial.print("SOC% battery: ");
Serial.print(Display_SOC);
Serial.print(" SOC% sent to inverter: ");
Serial.print(SOC);
Serial.print(" Battery voltage: ");
Serial.print(battery_voltage);
Serial.print(" Remaining Wh: ");
Serial.print(remaining_capacity_Wh);
Serial.print(" Max charge power: ");
Serial.print(max_target_charge_power);
Serial.print(" Max discharge power: ");
Serial.print(max_target_discharge_power);
#endif
}
void receive_can_i3_battery(CAN_frame_t rx_frame)
{
CANstillAlive = 12;
switch (rx_frame.MsgID)
{
case 0x431: //Battery capacity [200ms]
Battery_Capacity_kWh = (((rx_frame.data.u8[1] & 0x0F) << 8 | rx_frame.data.u8[5])) / 50;
break;
case 0x432: //SOC% charged [200ms]
Voltage_Setpoint = ((rx_frame.data.u8[1] << 4 | rx_frame.data.u8[0] >> 4)) / 10;
Low_SOC = (rx_frame.data.u8[2] / 2);
High_SOC = (rx_frame.data.u8[3] / 2);
Display_SOC = (rx_frame.data.u8[4] / 2);
break;
case 0x112: //BMS status [10ms]
CANstillAlive = 12;
Battery_Current = ((rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]) / 10) - 819; //Amps
Battery_Volts = (rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]); //500.0 V
HVBatt_SOC = ((rx_frame.data.u8[5] & 0x0F) << 4 | rx_frame.data.u8[4]) / 10;
Battery_Status = (rx_frame.data.u8[6] & 0x0F);
DC_link = rx_frame.data.u8[7];
break;
case 0x430:
break;
case 0x1FA:
break;
case 0x40D:
break;
case 0x2FF:
break;
case 0x239:
break;
case 0x2BD:
break;
case 0x2F5:
break;
case 0x3EB:
break;
case 0x363:
break;
case 0x507:
break;
case 0x41C:
break;
default:
break;
}
}
void send_can_i3_battery()
{
unsigned long currentMillis = millis();
// Send 600ms CAN Message
if (currentMillis - previousMillis600 >= interval600)
{
previousMillis600 = currentMillis;
ESP32Can.CANWriteFrame(&BMW_512);
}
//Send 20ms message
if (currentMillis - previousMillis20 >= interval20)
{
previousMillis20 = currentMillis;
BMW_10B.data.u8[0] = BMW_10B_0[BMW_10B_counter];
BMW_10B.data.u8[1] = BMW_10B_1[BMW_10B_counter];
BMW_10B_counter++;
if(BMW_10B_counter > 14)
{
BMW_10B_counter = 0;
}
ESP32Can.CANWriteFrame(&BMW_10B);
}
}

View file

@ -0,0 +1,38 @@
#ifndef BMW_I3_BATTERY_H
#define BMW_I3_BATTERY_H
#include <Arduino.h>
#include "../../ESP32CAN.h"
#include "../../USER_SETTINGS.h"
#define ABSOLUTE_MAX_VOLTAGE 4040 // 404.4V,if battery voltage goes over this, charging is not possible (goes into forced discharge)
#define ABSOLUTE_MIN_VOLTAGE 3100 // 310.0V if battery voltage goes under this, discharging further is disabled
// These parameters need to be mapped for the inverter
extern uint16_t SOC;
extern uint16_t StateOfHealth;
extern uint16_t battery_voltage;
extern uint16_t battery_current;
extern uint16_t capacity_Wh;
extern uint16_t remaining_capacity_Wh;
extern uint16_t max_target_discharge_power;
extern uint16_t max_target_charge_power;
extern uint16_t bms_status;
extern uint16_t bms_char_dis_status;
extern uint16_t stat_batt_power;
extern uint16_t temperature_min;
extern uint16_t temperature_max;
extern uint16_t CANerror;
extern uint8_t batteryAllowsContactorClosing;
// Definitions for BMS status
#define STANDBY 0
#define INACTIVE 1
#define DARKSTART 2
#define ACTIVE 3
#define FAULT 4
#define UPDATING 5
void update_values_i3_battery();
void receive_can_i3_battery(CAN_frame_t rx_frame);
void send_can_i3_battery();
#endif

View file

@ -0,0 +1,201 @@
#include "CHADEMO-BATTERY.h"
#include "../../ESP32CAN.h"
#include "../../CAN_config.h"
/* Do not change code below unless you are sure what you are doing */
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
static const int interval100 = 100; // interval (ms) at which send CAN Messages
const int rx_queue_size = 10; // Receive Queue size
static uint8_t CANstillAlive = 12; //counter for checking if CAN is still alive
static uint8_t errorCode = 0; //stores if we have an error code active from battery control logic
CAN_frame_t CHADEMO_108 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_std,}},.MsgID = 0x108,.data = {0x01, 0xF4, 0x01, 0x0F, 0xB3, 0x01, 0x00, 0x00}};
CAN_frame_t CHADEMO_109 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_std,}},.MsgID = 0x109,.data = {0x02, 0x00, 0x00, 0x00, 0x01, 0x20, 0xFF, 0xFF}};
//For chademo v2.0 only
CAN_frame_t CHADEMO_118 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_std,}},.MsgID = 0x118,.data = {0x10, 0x64, 0x00, 0xB0, 0x00, 0x1E, 0x00, 0x8F}};
CAN_frame_t CHADEMO_208 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_std,}},.MsgID = 0x208,.data = {0xFF, 0xF4, 0x01, 0xF0, 0x00, 0x00, 0xFA, 0x00}};
CAN_frame_t CHADEMO_209 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_std,}},.MsgID = 0x209,.data = {0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
//H100
uint8_t MinimumChargeCurrent = 0;
uint16_t MinumumBatteryVoltage = 0;
uint16_t MaximumBatteryVoltage = 0;
uint8_t ConstantOfChargingRateIndication = 0;
//H101
uint8_t MaxChargingTime10sBit = 0;
uint8_t MaxChargingTime1minBit = 0;
uint8_t EstimatedChargingTime = 0;
uint16_t RatedBatteryCapacity = 0;
//H102
uint8_t ControlProtocolNumberEV = 0;
uint16_t TargetBatteryVoltage = 0;
uint8_t ChargingCurrentRequest = 0;
uint8_t FaultBatteryVoltageDeviation = 0;
uint8_t FaultHighBatteryTemperature = 0;
uint8_t FaultBatteryCurrentDeviation = 0;
uint8_t FaultBatteryUndervoltage = 0;
uint8_t FaultBatteryOvervoltage = 0;
uint8_t StatusNormalStopRequest = 0;
uint8_t StatusVehicle = 0;
uint8_t StatusChargingSystem = 0;
uint8_t StatusVehicleShifterPosition = 0;
uint8_t StatusVehicleCharging = 0;
uint8_t ChargingRate = 0;
//H200
uint8_t MaximumDischargeCurrent = 0;
uint16_t MinimumDischargeVoltage = 0;
uint8_t MinimumBatteryDischargeLevel = 0;
uint8_t MaxRemainingCapacityForCharging = 0;
//H201
uint8_t V2HchargeDischargeSequenceNum = 0;
uint16_t ApproxDischargeCompletionTime = 0;
uint16_t AvailableVehicleEnergy = 0;
//H700
uint8_t AutomakerCode = 0;
uint32_t OptionalContent = 0;
//H118
uint8_t DynamicControlStatus = 0;
uint8_t HighCurrentControlStatus = 0;
uint8_t HighVoltageControlStatus = 0;
void update_values_chademo_battery()
{ //This function maps all the values fetched via CAN to the correct parameters used for the inverter
bms_status = ACTIVE; //Startout in active mode
SOC = ChargingRate;
max_target_discharge_power = (MaximumDischargeCurrent*MaximumBatteryVoltage); //In Watts, Convert A to P
battery_voltage = TargetBatteryVoltage; //Todo, scaling?
capacity_Wh = ((RatedBatteryCapacity/0.11)*1000); //(Added in CHAdeMO v1.0.1), maybe handle hardcoded on lower protocol version?
remaining_capacity_Wh = (SOC/100)*capacity_Wh;
/* Check if the Vehicle is still sending CAN messages. If we go 60s without messages we raise an error*/
if(!CANstillAlive)
{
bms_status = FAULT;
errorCode = 7;
Serial.println("No CAN communication detected for 60s. Shutting down battery control.");
}
else
{
CANstillAlive--;
}
#ifdef DEBUG_VIA_USB
if(errorCode > 0)
{
Serial.print("ERROR CODE ACTIVE IN SYSTEM. NUMBER: ");
Serial.println(errorCode);
}
Serial.print("BMS Status (3=OK): ");
Serial.println(bms_status);
switch (bms_char_dis_status)
{
case 0:
Serial.println("Battery Idle");
break;
case 1:
Serial.println("Battery Discharging");
break;
case 2:
Serial.println("Battery Charging");
break;
default:
break;
}
Serial.print("Max discharge power: ");
Serial.println(max_target_discharge_power);
Serial.print("Max charge power: ");
Serial.println(max_target_charge_power);
Serial.print("SOH%: ");
Serial.println(StateOfHealth);
Serial.print("SOC% to Inverter: ");
Serial.println(SOC);
Serial.print("Temperature Min: ");
Serial.println(temperature_min);
Serial.print("Temperature Max: ");
Serial.println(temperature_max);
#endif
}
void receive_can_chademo_battery(CAN_frame_t rx_frame)
{
CANstillAlive == 12; //We are getting CAN messages from the vehicle, inform the watchdog
switch (rx_frame.MsgID)
{
case 0x100:
MinimumChargeCurrent = rx_frame.data.u8[0];
MinumumBatteryVoltage = ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]);
MaximumBatteryVoltage = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
ConstantOfChargingRateIndication = rx_frame.data.u8[6];
break;
case 0x101:
MaxChargingTime10sBit = rx_frame.data.u8[1];
MaxChargingTime1minBit = rx_frame.data.u8[2];
EstimatedChargingTime = rx_frame.data.u8[3];
RatedBatteryCapacity = ((rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6]);
break;
case 0x102:
ControlProtocolNumberEV = rx_frame.data.u8[0];
TargetBatteryVoltage = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2]);
ChargingCurrentRequest = rx_frame.data.u8[3];
FaultBatteryOvervoltage = (rx_frame.data.u8[4] & 0x01);
FaultBatteryUndervoltage = (rx_frame.data.u8[4] & 0x02) >> 1;
FaultBatteryCurrentDeviation = (rx_frame.data.u8[4] & 0x04) >> 2;
FaultHighBatteryTemperature = (rx_frame.data.u8[4] & 0x08) >> 3;
FaultBatteryVoltageDeviation = (rx_frame.data.u8[4] & 0x10) >> 4;
StatusVehicleCharging = (rx_frame.data.u8[5] & 0x01);
StatusVehicleShifterPosition = (rx_frame.data.u8[5] & 0x02) >> 1;
StatusChargingSystem = (rx_frame.data.u8[5] & 0x04) >> 2;
StatusVehicle = (rx_frame.data.u8[5] & 0x08) >> 3;
StatusNormalStopRequest = (rx_frame.data.u8[5] & 0x10) >> 4;
ChargingRate = rx_frame.data.u8[6];
break;
case 0x200: //For V2X
MaximumDischargeCurrent = rx_frame.data.u8[0];
MinimumDischargeVoltage = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
MinimumBatteryDischargeLevel = rx_frame.data.u8[6];
MaxRemainingCapacityForCharging = rx_frame.data.u8[7];
break;
case 0x201: //For V2X
V2HchargeDischargeSequenceNum = rx_frame.data.u8[0];
ApproxDischargeCompletionTime = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2]);
AvailableVehicleEnergy = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4]);
break;
case 0x700:
AutomakerCode = rx_frame.data.u8[0];
OptionalContent = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2]); //Actually more bytes, but not needed for our purpose
break;
case 0x110: //Only present on Chademo v2.0
DynamicControlStatus = (rx_frame.data.u8[0] & 0x01);
HighCurrentControlStatus = (rx_frame.data.u8[0] & 0x02) >> 1;
HighVoltageControlStatus = (rx_frame.data.u8[0] & 0x04) >> 2;
default:
break;
}
}
void send_can_chademo_battery()
{
unsigned long currentMillis = millis();
// Send 100ms CAN Message
if (currentMillis - previousMillis100 >= interval100)
{
previousMillis100 = currentMillis;
ESP32Can.CANWriteFrame(&CHADEMO_108);
ESP32Can.CANWriteFrame(&CHADEMO_109);
ESP32Can.CANWriteFrame(&CHADEMO_208);
ESP32Can.CANWriteFrame(&CHADEMO_209);
if(ControlProtocolNumberEV >= 0x03)
{ //Only send the following on Chademo 2.0 vehicles?
ESP32Can.CANWriteFrame(&CHADEMO_118);
}
}
}

View file

@ -0,0 +1,37 @@
#ifndef CHADEMO_BATTERY_H
#define CHADEMO_BATTERY_H
#include <Arduino.h>
#include "../../ESP32CAN.h"
#include "../../USER_SETTINGS.h"
#define ABSOLUTE_MAX_VOLTAGE 4040 // 404.4V,if battery voltage goes over this, charging is not possible (goes into forced discharge)
#define ABSOLUTE_MIN_VOLTAGE 3100 // 310.0V if battery voltage goes under this, discharging further is disabled
// These parameters need to be mapped
extern uint16_t SOC;
extern uint16_t StateOfHealth;
extern uint16_t battery_voltage;
extern uint16_t battery_current;
extern uint16_t capacity_Wh;
extern uint16_t remaining_capacity_Wh;
extern uint16_t max_target_discharge_power;
extern uint16_t max_target_charge_power;
extern uint16_t bms_status;
extern uint16_t bms_char_dis_status;
extern uint16_t stat_batt_power;
extern uint16_t temperature_min;
extern uint16_t temperature_max;
extern uint16_t CANerror;
// Definitions for BMS status
#define STANDBY 0
#define INACTIVE 1
#define DARKSTART 2
#define ACTIVE 3
#define FAULT 4
#define UPDATING 5
void update_values_chademo_battery();
void receive_can_chademo_battery(CAN_frame_t rx_frame);
void send_can_chademo_battery();
#endif

View file

@ -0,0 +1,242 @@
#include "IMIEV-CZERO-ION-BATTERY.h"
#include "../../ESP32CAN.h"
#include "../../CAN_config.h"
//Code still work in progress, TODO:
//Figure out if CAN messages need to be sent to keep the system happy?
/* Do not change code below unless you are sure what you are doing */
#define BMU_MAX_SOC 1000 //BMS never goes over this value. We use this info to rescale SOC% sent to inverter
#define BMU_MIN_SOC 0 //BMS never goes below this value. We use this info to rescale SOC% sent to inverter
static uint8_t CANstillAlive = 12; //counter for checking if CAN is still alive
static uint8_t errorCode = 0; //stores if we have an error code active from battery control logic
static uint8_t BMU_Detected = 0;
static uint8_t CMU_Detected = 0;
static unsigned long previousMillis10 = 0; // will store last time a 10ms CAN Message was sent
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was sent
static const int interval10 = 10; // interval (ms) at which send CAN Messages
static const int interval100 = 100; // interval (ms) at which send CAN Messages
static int pid_index = 0;
static int cmu_id = 0;
static int voltage_index = 0;
static int temp_index = 0;
static uint8_t BMU_SOC = 0;
static int temp_value = 0;
static double temp1 = 0;
static double temp2 = 0;
static double temp3 = 0;
static double voltage1 = 0;
static double voltage2 = 0;
static double BMU_Current = 0;
static double BMU_PackVoltage = 0;
static double BMU_Power = 0;
static double cell_voltages[89]; //array with all the cellvoltages //Todo, what is max array size? 80/88 cells?
static double cell_temperatures[89]; //array with all the celltemperatures //Todo, what is max array size? 80/88cells?
static double max_volt_cel = 3.70;
static double min_volt_cel = 3.70;
static double max_temp_cel = 20.00;
static double min_temp_cel = 19.00;
void update_values_imiev_battery()
{ //This function maps all the values fetched via CAN to the correct parameters used for modbus
bms_status = ACTIVE; //Startout in active mode
SOC = (uint16_t)(BMU_SOC * 100); //increase BMU_SOC range from 0-100 -> 100.00
battery_voltage = (uint16_t)(BMU_PackVoltage * 10); // Multiply by 10 and cast to uint16_t
battery_current = (BMU_Current*10); //Todo, scaling?
capacity_Wh = BATTERY_WH_MAX; //Hardcoded to header value
remaining_capacity_Wh = (uint16_t)((SOC/10000)*capacity_Wh);
//We do not know if the max charge power is sent by the battery. So we estimate the value based on SOC%
if(SOC == 10000){ //100.00%
max_target_charge_power = 0; //When battery is 100% full, set allowed charge W to 0
}
else{
max_target_charge_power = 10000; //Otherwise we can push 10kW into the pack!
}
if(SOC < 200){ //2.00%
max_target_discharge_power = 0; //When battery is empty (below 2%), set allowed discharge W to 0
}
else{
max_target_discharge_power = 10000; //Otherwise we can discharge 10kW from the pack!
}
stat_batt_power = 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++) {
if (cell_voltages[i] > max_volt_cel) {
max_volt_cel = cell_voltages[i]; // Update max if we find a larger element
}
}
min_volt_cel = cell_voltages[0]; // Initialize min with the first element of the array
for (int i = 1; i < n; i++) {
if (cell_voltages[i] < min_volt_cel) {
min_volt_cel = cell_voltages[i]; // Update min if we find a smaller element
}
}
static int m = sizeof(cell_voltages) / sizeof(cell_temperatures[0]);
max_temp_cel = cell_temperatures[0]; // Initialize max with the first element of the array
for (int i = 1; i < m; i++) {
if (cell_temperatures[i] > max_temp_cel) {
max_temp_cel = cell_temperatures[i]; // Update max if we find a larger element
}
}
min_temp_cel = cell_temperatures[0]; // Initialize min with the first element of the array
for (int i = 1; i < m; i++) {
if (cell_temperatures[i] < min_temp_cel) {
if(min_temp_cel != -50.00){ //-50.00 means this sensor not connected
min_temp_cel = cell_temperatures[i]; // Update max if we find a smaller element
}
}
}
cell_max_voltage = (uint16_t)(max_volt_cel*1000);
cell_min_voltage = (uint16_t)(min_volt_cel*1000);
temperature_min = (uint16_t)(min_temp_cel*1000);
temperature_max = (uint16_t)(max_temp_cel*1000);
/* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/
if(!CANstillAlive)
{
bms_status = FAULT;
Serial.println("No CAN communication detected for 60s. Shutting down battery control.");
}
else
{
CANstillAlive--;
}
if(!BMU_Detected){
Serial.println("BMU not detected, check wiring!");
}
#ifdef DEBUG_VIA_USB
Serial.println("Battery Values");
Serial.print("BMU SOC: ");
Serial.print(BMU_SOC);
Serial.print(" BMU Current: ");
Serial.print(BMU_Current);
Serial.print(" BMU Battery Voltage: ");
Serial.print(BMU_PackVoltage);
Serial.print(" BMU_Power: ");
Serial.print(BMU_Power);
Serial.print(" Cell max voltage: ");
Serial.print(max_volt_cel);
Serial.print(" Cell min voltage: ");
Serial.print(min_volt_cel);
Serial.print(" Cell max temp: ");
Serial.print(max_temp_cel);
Serial.print(" Cell min temp: ");
Serial.println(min_temp_cel);
Serial.println("Values sent to inverter");
Serial.print("SOC% (0-100.00): ");
Serial.print(SOC);
Serial.print(" Voltage (0-400.0): ");
Serial.print(battery_voltage);
Serial.print(" Capacity WH full (0-60000): ");
Serial.print(capacity_Wh);
Serial.print(" Capacity WH remain (0-60000): ");
Serial.print(remaining_capacity_Wh);
Serial.print(" Max charge power W (0-10000): ");
Serial.print(max_target_charge_power);
Serial.print(" Max discharge power W (0-10000): ");
Serial.print(max_target_discharge_power);
Serial.print(" Temp max ");
Serial.print(temperature_max);
Serial.print(" Temp min ");
Serial.print(temperature_min);
Serial.print(" Cell mV max ");
Serial.print(cell_max_voltage);
Serial.print(" Cell mV min ");
Serial.print(cell_min_voltage);
#endif
}
void receive_can_imiev_battery(CAN_frame_t rx_frame)
{
CANstillAlive = 12; //Todo, move this inside a known message ID to prevent CAN inverter from keeping battery alive detection going
switch (rx_frame.MsgID)
{
case 0x374: //BMU message, 10ms - SOC
temp_value = ((rx_frame.data.u8[1] - 10) / 2);
if(temp_value >= 0 && temp_value <= 101){
BMU_SOC = temp_value;
}
break;
case 0x373: //BMU message, 100ms - Pack Voltage and current
BMU_Current = ((((((rx_frame.data.u8[2] * 256.0) + rx_frame.data.u8[3])) - 32768)) * 0.01);
BMU_PackVoltage = ((rx_frame.data.u8[4] * 256.0 + rx_frame.data.u8[5]) * 0.1);
BMU_Power = (BMU_Current * BMU_PackVoltage);
break;
case 0x6e1: //BMU message, 25ms - Battery temperatures and voltages
case 0x6e2:
case 0x6e3:
case 0x6e4:
BMU_Detected = 1;
//Pid index 0-3
pid_index = (rx_frame.MsgID) - 1761;
//cmu index 1-12: ignore high order nibble which appears to sometimes contain other status bits
cmu_id = (rx_frame.data.u8[0] & 0x0f);
//
temp1 = rx_frame.data.u8[1] - 50.0;
temp2 = rx_frame.data.u8[2] - 50.0;
temp3 = rx_frame.data.u8[3] - 50.0;
voltage1 = (((rx_frame.data.u8[4] * 256.0 + rx_frame.data.u8[5]) * 0.005) + 2.1);
voltage2 = (((rx_frame.data.u8[6] * 256.0 + rx_frame.data.u8[7]) * 0.005) + 2.1);
voltage_index = ((cmu_id - 1) * 8 + (2 * pid_index));
temp_index = ((cmu_id - 1) * 6 + (2 * pid_index));
if (cmu_id >= 7)
{
voltage_index -= 4;
temp_index -= 3;
}
cell_voltages[voltage_index] = voltage1;
cell_voltages[voltage_index + 1] = voltage2;
if (pid_index == 0)
{
cell_temperatures[temp_index] = temp2;
cell_temperatures[temp_index + 1] = temp3;
}
else
{
cell_temperatures[temp_index] = temp1;
if (cmu_id != 6 && cmu_id != 12)
{
cell_temperatures[temp_index + 1] = temp2;
}
}
break;
default:
break;
}
}
void send_can_imiev_battery()
{
unsigned long currentMillis = millis();
// Send 100ms CAN Message
if (currentMillis - previousMillis100 >= interval100)
{
previousMillis100 = currentMillis;
}
}

View file

@ -0,0 +1,41 @@
#ifndef IMIEV_CZERO_ION_BATTERY_H
#define IMIEV_CZERO_ION_BATTERY_H
#include <Arduino.h>
#include "../../ESP32CAN.h"
#include "../../USER_SETTINGS.h"
#define ABSOLUTE_MAX_VOLTAGE 4040 // 404.4V,if battery voltage goes over this, charging is not possible (goes into forced discharge)
#define ABSOLUTE_MIN_VOLTAGE 3100 // 310.0V if battery voltage goes under this, discharging further is disabled
// These parameters need to be mapped for the Gen24
extern uint16_t SOC;
extern uint16_t StateOfHealth;
extern uint16_t battery_voltage;
extern uint16_t battery_current;
extern uint16_t capacity_Wh;
extern uint16_t remaining_capacity_Wh;
extern uint16_t max_target_discharge_power;
extern uint16_t max_target_charge_power;
extern uint16_t bms_status;
extern uint16_t bms_char_dis_status;
extern uint16_t stat_batt_power;
extern uint16_t temperature_min;
extern uint16_t temperature_max;
extern uint16_t CANerror;
extern uint16_t cell_max_voltage;
extern uint16_t cell_min_voltage;
extern uint8_t batteryAllowsContactorClosing;
extern uint8_t LEDcolor;
// Definitions for BMS status
#define STANDBY 0
#define INACTIVE 1
#define DARKSTART 2
#define ACTIVE 3
#define FAULT 4
#define UPDATING 5
void update_values_imiev_battery();
void receive_can_imiev_battery(CAN_frame_t rx_frame);
void send_can_imiev_battery();
#endif

View file

@ -0,0 +1,134 @@
#include "KIA-HYUNDAI-64-BATTERY.h"
#include "../../ESP32CAN.h"
#include "../../CAN_config.h"
/* Do not change code below unless you are sure what you are doing */
static unsigned long previousMillis10 = 0; // will store last time a 10ms CAN Message was send
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
static const int interval10 = 10; // interval (ms) at which send CAN Messages
static const int interval100 = 100; // interval (ms) at which send CAN Messages
static uint8_t CANstillAlive = 12; //counter for checking if CAN is still alive
#define LB_MAX_SOC 1000 //BMS never goes over this value. We use this info to rescale SOC% sent to Inverter
#define LB_MIN_SOC 0 //BMS never goes below this value. We use this info to rescale SOC% sent to Inverter
static int SOC_1 = 0;
static int SOC_2 = 0;
static int SOC_3 = 0;
void update_values_kiaHyundai_64_battery()
{ //This function maps all the values fetched via CAN to the correct parameters used for modbus
bms_status = ACTIVE; //Startout in active mode
SOC;
battery_voltage;
battery_current;
capacity_Wh = BATTERY_WH_MAX;
remaining_capacity_Wh;
max_target_discharge_power;
max_target_charge_power;
stat_batt_power;
temperature_min;
temperature_max;
/* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/
if(!CANstillAlive)
{
bms_status = FAULT;
Serial.println("No CAN communication detected for 60s. Shutting down battery control.");
}
else
{
CANstillAlive--;
}
#ifdef DEBUG_VIA_USB
Serial.print("SOC% candidate 1: ");
Serial.println(SOC_1);
Serial.print("SOC% candidate 2: ");
Serial.println(SOC_2);
Serial.print("SOC% candidate 3: ");
Serial.println(SOC_3);
#endif
}
void receive_can_kiaHyundai_64_battery(CAN_frame_t rx_frame)
{
CANstillAlive = 12;
switch (rx_frame.MsgID)
{
case 0x3F6:
break;
case 0x491:
break;
case 0x493:
break;
case 0x497:
break;
case 0x498:
break;
case 0x4DD:
break;
case 0x4DE:
break;
case 0x4E2:
break;
case 0x542:
SOC_1 = rx_frame.data.u8[0];
break;
case 0x594:
SOC_2 = rx_frame.data.u8[5];
break;
case 0x595:
break;
case 0x596:
break;
case 0x597:
break;
case 0x598:
SOC_3 = (rx_frame.data.u8[4] * 256.0 + rx_frame.data.u8[5]);
break;
case 0x599:
break;
case 0x59C:
break;
case 0x59E:
break;
case 0x5A3:
break;
case 0x5D5:
break;
case 0x5D6:
break;
case 0x5D7:
break;
case 0x5D8:
break;
default:
break;
}
}
void send_can_kiaHyundai_64_battery()
{
unsigned long currentMillis = millis();
// Send 100ms CAN Message
if (currentMillis - previousMillis100 >= interval100)
{
previousMillis100 = currentMillis;
}
//Send 10ms message
if (currentMillis - previousMillis10 >= interval10)
{
previousMillis10 = currentMillis;
}
}

View file

@ -0,0 +1,38 @@
#ifndef KIA_HYUNDAI_64_BATTERY_H
#define KIA_HYUNDAI_64_BATTERY_H
#include <Arduino.h>
#include "../../ESP32CAN.h"
#include "../../USER_SETTINGS.h"
#define ABSOLUTE_MAX_VOLTAGE 4040 // 404.4V,if battery voltage goes over this, charging is not possible (goes into forced discharge)
#define ABSOLUTE_MIN_VOLTAGE 3100 // 310.0V if battery voltage goes under this, discharging further is disabled
// These parameters need to be mapped for the Gen24
extern uint16_t SOC;
extern uint16_t StateOfHealth;
extern uint16_t battery_voltage;
extern uint16_t battery_current;
extern uint16_t capacity_Wh;
extern uint16_t remaining_capacity_Wh;
extern uint16_t max_target_discharge_power;
extern uint16_t max_target_charge_power;
extern uint16_t bms_status;
extern uint16_t bms_char_dis_status;
extern uint16_t stat_batt_power;
extern uint16_t temperature_min;
extern uint16_t temperature_max;
extern uint16_t CANerror;
extern uint8_t batteryAllowsContactorClosing;
// Definitions for BMS status
#define STANDBY 0
#define INACTIVE 1
#define DARKSTART 2
#define ACTIVE 3
#define FAULT 4
#define UPDATING 5
void update_values_kiaHyundai_64_battery();
void receive_can_kiaHyundai_64_battery(CAN_frame_t rx_frame);
void send_can_kiaHyundai_64_battery();
#endif

View file

@ -0,0 +1,872 @@
#include "NISSAN-LEAF-BATTERY.h"
#include "../../ESP32CAN.h"
#include "../../CAN_config.h"
/* Do not change code below unless you are sure what you are doing */
static unsigned long previousMillis10 = 0; // will store last time a 10ms CAN Message was send
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
static unsigned long previousMillis10s = 0; // will store last time a 1s CAN Message was send
static const int interval10 = 10; // interval (ms) at which send CAN Messages
static const int interval100 = 100; // interval (ms) at which send CAN Messages
static const int interval10s = 10000; // interval (ms) at which send CAN Messages
uint16_t CANerror = 0; //counter on how many CAN errors encountered
#define MAX_CAN_FAILURES 5000 //Amount of malformed CAN messages to allow before raising a warning
static uint8_t CANstillAlive = 12; //counter for checking if CAN is still alive
static uint8_t errorCode = 0; //stores if we have an error code active from battery control logic
static uint8_t mprun10r = 0; //counter 0-20 for 0x1F2 message
static byte mprun10 = 0; //counter 0-3
static byte mprun100 = 0; //counter 0-3
CAN_frame_t LEAF_1F2 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_std,}},.MsgID = 0x1F2,.data = {0x10, 0x64, 0x00, 0xB0, 0x00, 0x1E, 0x00, 0x8F}};
CAN_frame_t LEAF_50B = {.FIR = {.B = {.DLC = 7,.FF = CAN_frame_std,}},.MsgID = 0x50B,.data = {0x00, 0x00, 0x06, 0xC0, 0x00, 0x00, 0x00}};
CAN_frame_t LEAF_50C = {.FIR = {.B = {.DLC = 6,.FF = CAN_frame_std,}},.MsgID = 0x50C,.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame_t LEAF_1D4 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_std,}},.MsgID = 0x1D4,.data = {0x6E, 0x6E, 0x00, 0x04, 0x07, 0x46, 0xE0, 0x44}};
//These CAN messages need to be sent towards the battery to keep it alive
CAN_frame_t LEAF_GROUP_REQUEST = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_std,}},.MsgID = 0x79B,.data = {2, 0x21, 1, 0, 0, 0, 0, 0}};
const CAN_frame_t LEAF_NEXT_LINE_REQUEST = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_std,}},.MsgID = 0x79B,.data = {0x30, 1, 0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}};
// The Li-ion battery controller only accepts a multi-message query. In fact, the LBC transmits many
// groups: the first one contains lots of High Voltage battery data as SOC, currents, and voltage; the second
// replies with all the batterys cells voltages in millivolt, the third and the fifth one are still unknown, the
// fourth contains the four battery packs temperatures, and the last one tells which cell has the shunt active.
// There are also two more groups: group 61, which replies with lots of CAN messages (up to 48); here we
// found the SOH value, and group 84 that replies with the HV battery production serial.
static uint8_t crctable[256] = {0,133,143,10,155,30,20,145,179,54,60,185,40,173,167,34,227,102,108,233,120,253,247,114,80,213,223,90,203,78,68,193,67,
198,204,73,216,93,87,210,240,117,127,250,107,238,228,97,160,37,47,170,59,190,180,49,19,150,156,25,136,13,7,130,134,3,9,
140,29,152,146,23,53,176,186,63,174,43,33,164,101,224,234,111,254,123,113,244,214,83,89,220,77,200,194,71,197,64,74,207,
94,219,209,84,118,243,249,124,237,104,98,231,38,163,169,44,189,56,50,183,149,16,26,159,14,139,129,4,137,12,6,131,18,151,
157,24,58,191,181,48,161,36,46,171,106,239,229,96,241,116,126,251,217,92,86,211,66,199,205,72,202,79,69,192,81,212,222,
91,121,252,246,115,226,103,109,232,41,172,166,35,178,55,61,184,154,31,21,144,1,132,142,11,15,138,128,5,148,17,27,158,188,
57,51,182,39,162,168,45,236,105,99,230,119,242,248,125,95,218,208,85,196,65,75,206,76,201,195,70,215,82,88,221,255,122,
112,245,100,225,235,110,175,42,32,165,52,177,187,62,28,153,147,22,135,2,8,141};
//Nissan LEAF battery parameters from constantly sent CAN
#define ZE0_BATTERY 0
#define AZE0_BATTERY 1
#define ZE1_BATTERY 2
static uint8_t LEAF_Battery_Type = ZE0_BATTERY;
#define MAX_CELL_VOLTAGE 4250 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE 2700 //Battery is put into emergency stop if one cell goes below this value
#define MAX_CELL_DEVIATION 500 //LED turns yellow on the board if mv delta exceeds this value
#define WH_PER_GID 77 //One GID is this amount of Watt hours
#define LB_MAX_SOC 1000 //LEAF BMS never goes over this value. We use this info to rescale SOC% sent to Fronius
#define LB_MIN_SOC 0 //LEAF BMS never goes below this value. We use this info to rescale SOC% sent to Fronius
static uint16_t LB_Discharge_Power_Limit = 0; //Limit in kW
static uint16_t LB_Charge_Power_Limit = 0; //Limit in kW
static int16_t LB_MAX_POWER_FOR_CHARGER = 0; //Limit in kW
static int16_t LB_SOC = 500; //0 - 100.0 % (0-1000) The real SOC% in the battery
static int16_t CalculatedSOC = 0; // Temporary value used for calculating SOC
static uint16_t LB_TEMP = 0; //Temporary value used in status checks
static uint16_t LB_Wh_Remaining = 0; //Amount of energy in battery, in Wh
static uint16_t LB_GIDS = 0;
static uint16_t LB_MAX = 0;
static uint16_t LB_Max_GIDS = 273; //Startup in 24kWh mode
static uint16_t LB_StateOfHealth = 99; //State of health %
static uint16_t LB_Total_Voltage = 370; //Battery voltage (0-450V)
static int16_t LB_Current = 0; //Current in A going in/out of battery
static int16_t LB_Power = 0; //Watts going in/out of battery
static int16_t LB_HistData_Temperature_MAX = 6; //-40 to 86*C
static int16_t LB_HistData_Temperature_MIN = 5; //-40 to 86*C
static int16_t LB_AverageTemperature = 6; //Only available on ZE0, in celcius, -40 to +55
static uint8_t LB_Relay_Cut_Request = 0; //LB_FAIL
static uint8_t LB_Failsafe_Status = 0; //LB_STATUS = 000b = normal start Request
//001b = Main Relay OFF Request
//010b = Charging Mode Stop Request
//011b = Main Relay OFF Request
//100b = Caution Lamp Request
//101b = Caution Lamp Request & Main Relay OFF Request
//110b = Caution Lamp Request & Charging Mode Stop Request
//111b = Caution Lamp Request & Main Relay OFF Request
static byte LB_Interlock = 1; //Contains info on if HV leads are seated (Note, to use this both HV connectors need to be inserted)
static byte LB_Full_CHARGE_flag = 0; //LB_FCHGEND , Goes to 1 if battery is fully charged
static byte LB_MainRelayOn_flag = 0; //No-Permission=0, Main Relay On Permission=1
static byte LB_Capacity_Empty = 0; //LB_EMPTY, , Goes to 1 if battery is empty
// Nissan LEAF battery data from polled CAN messages
static uint8_t battery_request_idx = 0;
static uint8_t group_7bb = 0;
static uint8_t group = 1;
static uint8_t stop_battery_query = 1;
static uint8_t hold_off_with_polling_10seconds = 10;
static uint16_t cell_voltages[97]; //array with all the cellvoltages
static uint16_t cellcounter = 0;
static uint16_t min_max_voltage[2]; //contains cell min[0] and max[1] values in mV
static uint16_t cell_deviation_mV = 0; //contains the deviation between highest and lowest cell in mV
static uint16_t HX = 0; //Internal resistance
static uint16_t insulation = 0; //Insulation resistance
static int32_t Battery_current_1 = 0; //High Voltage battery current; its positive if discharged, negative when charging
static int32_t Battery_current_2 = 0; //High Voltage battery current; its positive if discharged, negative when charging (unclear why two values exist)
static uint16_t temp_raw_1 = 0;
static uint8_t temp_raw_2_highnibble = 0;
static uint16_t temp_raw_2 = 0;
static uint16_t temp_raw_3 = 0;
static uint16_t temp_raw_4 = 0;
static uint16_t temp_raw_max = 0;
static uint16_t temp_raw_min = 0;
static int16_t temp_polled_max = 0;
static int16_t temp_polled_min = 0;
void print_with_units(char *header, int value, char *units) {
Serial.print(header);
Serial.print(value);
Serial.print(units);
}
void update_values_leaf_battery()
{ /* This function maps all the values fetched via CAN to the correct parameters used for modbus */
bms_status = ACTIVE; //Startout in active mode
/* Start with mapping all values */
StateOfHealth = (LB_StateOfHealth * 100); //Increase range from 99% -> 99.00%
//Calculate the SOC% value to send to Fronius
CalculatedSOC = LB_SOC;
CalculatedSOC = LB_MIN_SOC + (LB_MAX_SOC - LB_MIN_SOC) * (CalculatedSOC - MINPERCENTAGE) / (MAXPERCENTAGE - MINPERCENTAGE);
if (CalculatedSOC < 0){ //We are in the real SOC% range of 0-20%, always set SOC sent to Fronius as 0%
CalculatedSOC = 0;
}
if (CalculatedSOC > 1000){ //We are in the real SOC% range of 80-100%, always set SOC sent to Fronius as 100%
CalculatedSOC = 1000;
}
SOC = (CalculatedSOC * 10); //increase CalculatedSOC range from 0-100.0 -> 100.00
battery_voltage = (LB_Total_Voltage*10); //One more decimal needed
battery_current = convert2unsignedint16(LB_Current*10); //One more decimal needed, sign if needed
capacity_Wh = (LB_Max_GIDS * WH_PER_GID);
remaining_capacity_Wh = LB_Wh_Remaining;
LB_Power = LB_Total_Voltage * LB_Current;//P = U * I
stat_batt_power = convert2unsignedint16(LB_Power); //add sign if needed
//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
temperature_min = convert2unsignedint16((LB_AverageTemperature * 10)-10); //add sign if negative and increase range
temperature_max = convert2unsignedint16((LB_AverageTemperature * 10));
}
else if(LEAF_Battery_Type == AZE0_BATTERY){
//Use the value sent constantly via CAN in 5C0 (only available on AZE0)
temperature_min = convert2unsignedint16((LB_HistData_Temperature_MIN * 10)); //add sign if negative and increase range
temperature_max = convert2unsignedint16((LB_HistData_Temperature_MAX * 10));
}
else
{ // ZE1 (TODO: Once the muxed value in 5C0 becomes known, switch to using that instead of this complicated polled value)
if(temp_raw_min != 0) //We have a polled value available
{
temp_polled_min = ((Temp_fromRAW_to_F(temp_raw_min) - 320 ) * 5) / 9; //Convert from F to C
temp_polled_max = ((Temp_fromRAW_to_F(temp_raw_max) - 320 ) * 5) / 9; //Convert from F to C
if(temp_polled_min < temp_polled_max){ //Catch any edge cases from Temp_fromRAW_to_F function
temperature_min = convert2unsignedint16((temp_polled_min));
temperature_max = convert2unsignedint16((temp_polled_max));
}
else{
temperature_min = convert2unsignedint16((temp_polled_max));
temperature_max = convert2unsignedint16((temp_polled_min));
}
}
}
// Define power able to be discharged from battery
if(LB_Discharge_Power_Limit > 30) { //if >30kW can be pulled from battery
max_target_discharge_power = 30000; //cap value so we don't go over the Fronius limits
}
else{
max_target_discharge_power = (LB_Discharge_Power_Limit * 1000); //kW to W
}
if(SOC == 0){ //Scaled SOC% value is 0.00%, we should not discharge battery further
max_target_discharge_power = 0;
}
// Define power able to be put into the battery
if(LB_Charge_Power_Limit > 30){ //if >30kW can be put into the battery
max_target_charge_power = 30000; //cap value so we don't go over the Fronius limits
}
else{
max_target_charge_power = (LB_Charge_Power_Limit * 1000); //kW to W
}
if(SOC == 10000) //Scaled SOC% value is 100.00%
{
max_target_charge_power = 0; //No need to charge further, set max power to 0
}
/*Extra safety functions below*/
if(LB_GIDS < 10) //800Wh left in battery
{ //Battery is running abnormally low, some discharge logic might have failed. Zero it all out.
SOC = 0;
max_target_discharge_power = 0;
}
if(LB_Full_CHARGE_flag)
{ //Battery reports that it is fully charged stop all further charging incase it hasn't already
max_target_charge_power = 0;
}
if(LB_Relay_Cut_Request)
{ //LB_FAIL, BMS requesting shutdown and contactors to be opened
Serial.println("Battery requesting immediate 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
errorCode = 1;
max_target_discharge_power = 0;
max_target_charge_power = 0;
}
if(LB_Failsafe_Status > 0) // 0 is normal, start charging/discharging
{
switch(LB_Failsafe_Status)
{
case(1):
//Normal Stop Request
//This means that battery is fully discharged and it's OK to stop the session. For stationary storage we don't disconnect contactors, so we do nothing here.
break;
case(2):
//Charging Mode Stop Request
//This means that battery is fully charged and it's OK to stop the session. For stationary storage we don't disconnect contactors, so we do nothing here.
break;
case(3):
//Charging Mode Stop Request & Normal Stop Request
//Normal stop request. For stationary storage we don't disconnect contactors, so we ignore this.
break;
case(4):
//Caution Lamp Request
Serial.println("ERROR: Battery raised caution indicator. Inspect battery status!");
break;
case(5):
//Caution Lamp Request & Normal Stop Request
bms_status = FAULT;
errorCode = 2;
Serial.println("ERROR: Battery raised caution indicator AND requested discharge stop. Inspect battery status!");
break;
case(6):
//Caution Lamp Request & Charging Mode Stop Request
bms_status = FAULT;
errorCode = 3;
Serial.println("ERROR: Battery raised caution indicator AND requested charge stop. Inspect battery status!");
break;
case(7):
//Caution Lamp Request & Charging Mode Stop Request & Normal Stop Request
bms_status = FAULT;
errorCode = 4;
Serial.println("ERROR: Battery raised caution indicator AND requested charge/discharge stop. Inspect battery status!");
break;
default:
break;
}
}
if(LB_StateOfHealth < 25)
{ //Battery is extremely degraded, not fit for secondlifestorage. Zero it all out.
if(LB_StateOfHealth != 0)
{ //Extra check to see that we actually have a SOH Value available
Serial.println("ERROR: State of health critically low. Battery internal resistance too high to continue. Recycle battery.");
bms_status = FAULT;
errorCode = 5;
max_target_discharge_power = 0;
max_target_charge_power = 0;
}
}
#ifdef INTERLOCK_REQUIRED
if(!LB_Interlock)
{
Serial.println("ERROR: Battery interlock loop broken. Check that high voltage connectors are seated. Battery will be disabled!");
bms_status = FAULT;
errorCode = 6;
SOC = 0;
max_target_discharge_power = 0;
max_target_charge_power = 0;
}
#endif
/* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/
if(!CANstillAlive)
{
bms_status = FAULT;
errorCode = 7;
Serial.println("ERROR: No CAN communication detected for 60s. Shutting down battery control.");
}
else
{
CANstillAlive--;
}
if(CANerror > MAX_CAN_FAILURES) //Also check if we have recieved too many malformed CAN messages. If so, signal via LED
{
errorCode = 10;
LEDcolor = YELLOW;
Serial.println("ERROR: High amount of corrupted CAN messages detected. Check CAN wire shielding!");
}
/*Finally print out values to serial if configured to do so*/
#ifdef DEBUG_VIA_USB
if(errorCode > 0)
{
Serial.print("ERROR CODE ACTIVE IN SYSTEM. NUMBER: ");
Serial.println(errorCode);
}
Serial.println("Values going to inverter");
print_with_units("SOH%: ", (StateOfHealth*0.01), "% ");
print_with_units(", SOC% scaled: ", (SOC*0.01), "% ");
print_with_units(", Voltage: ", LB_Total_Voltage, "V ");
print_with_units(", Max discharge power: ", max_target_discharge_power, "W ");
print_with_units(", Max charge power: ", max_target_charge_power, "W ");
print_with_units(", Max temp: ", (temperature_max*0.1), "°C ");
print_with_units(", Min temp: ", (temperature_min*0.1), "°C ");
Serial.println("");
Serial.print("BMS Status: ");
if(bms_status == 3){
Serial.print("Active, ");
}
else{
Serial.print("FAULT, ");
}
switch (bms_char_dis_status){
case 0:
Serial.print("Idle");
break;
case 1:
Serial.print("Discharging");
break;
case 2:
Serial.print("Charging");
break;
default:
break;
}
print_with_units(", Power: ", LB_Power, "W ");
Serial.println("");
Serial.println("Values from battery");
print_with_units("Real SOC%: ", (LB_SOC*0.1), "% ");
print_with_units(", GIDS: ", LB_GIDS, " (x77Wh) ");
print_with_units(", Battery gen: ", LEAF_Battery_Type, " ");
print_with_units(", Max cell voltage: ", min_max_voltage[1], "mV ");
print_with_units(", Min cell voltage: ", min_max_voltage[0], "mV ");
print_with_units(", Cell deviation: ", cell_deviation_mV, "mV ");
#endif
}
void receive_can_leaf_battery(CAN_frame_t rx_frame)
{
switch (rx_frame.MsgID)
{
case 0x1DB:
if(is_message_corrupt(rx_frame)){
CANerror++;
break; //Message content malformed, abort reading data from it
}
LB_Current = (rx_frame.data.u8[0] << 3) | (rx_frame.data.u8[1] & 0xe0) >> 5;
if (LB_Current & 0x0400){
// negative so extend the sign bit
LB_Current |= 0xf800;
}
// Scale down the value by 0.5
LB_Current /= 2;
LB_Total_Voltage = ((rx_frame.data.u8[2] << 2) | (rx_frame.data.u8[3] & 0xc0) >> 6) / 2;
//Collect various data from the BMS
LB_Relay_Cut_Request = ((rx_frame.data.u8[1] & 0x18) >> 3);
LB_Failsafe_Status = (rx_frame.data.u8[1] & 0x07);
LB_MainRelayOn_flag = (byte) ((rx_frame.data.u8[3] & 0x20) >> 5);
if(LB_MainRelayOn_flag){
batteryAllowsContactorClosing = 1;
}
else{
batteryAllowsContactorClosing = 0;
}
LB_Full_CHARGE_flag = (byte) ((rx_frame.data.u8[3] & 0x10) >> 4);
LB_Interlock = (byte) ((rx_frame.data.u8[3] & 0x08) >> 3);
break;
case 0x1DC:
if(is_message_corrupt(rx_frame)){
CANerror++;
break; //Message content malformed, abort reading data from it
}
LB_Discharge_Power_Limit = ((rx_frame.data.u8[0] << 2 | rx_frame.data.u8[1] >> 6) / 4.0);
LB_Charge_Power_Limit = (((rx_frame.data.u8[1] & 0x3F) << 4 | rx_frame.data.u8[2] >> 4) / 4.0);
LB_MAX_POWER_FOR_CHARGER = ((((rx_frame.data.u8[2] & 0x0F) << 6 | rx_frame.data.u8[3] >> 2) / 10.0) - 10);
break;
case 0x55B:
if(is_message_corrupt(rx_frame)){
CANerror++;
break; //Message content malformed, abort reading data from it
}
LB_TEMP = (rx_frame.data.u8[0] << 2 | rx_frame.data.u8[1] >> 6);
if (LB_TEMP != 0x3ff) { //3FF is unavailable value
LB_SOC = LB_TEMP;
}
break;
case 0x5BC:
CANstillAlive = 12; //Indicate that we are still getting CAN messages from the BMS
LB_MAX = ((rx_frame.data.u8[5] & 0x10) >> 4);
if (LB_MAX){
LB_Max_GIDS = (rx_frame.data.u8[0] << 2) | ((rx_frame.data.u8[1] & 0xC0) >> 6);
//Max gids active, do nothing
//Only the 30/40/62kWh packs have this mux
}
else{ //Normal current GIDS value is transmitted
LB_GIDS = (rx_frame.data.u8[0] << 2) | ((rx_frame.data.u8[1] & 0xC0) >> 6);
LB_Wh_Remaining = (LB_GIDS * WH_PER_GID);
}
if(LEAF_Battery_Type == ZE0_BATTERY){
LB_AverageTemperature = (rx_frame.data.u8[3] - 40); //In celcius, -40 to +55
}
LB_TEMP = (rx_frame.data.u8[4] >> 1);
if (LB_TEMP != 0){
LB_StateOfHealth = LB_TEMP; //Collect state of health from battery
}
break;
case 0x5C0: //This method only works for 2013-2017 AZE0 LEAF packs, the mux is different on other generations
if(LEAF_Battery_Type == AZE0_BATTERY){
if ((rx_frame.data.u8[0]>>6) == 1){ // Battery MAX temperature. Effectively has only 7-bit precision, as the bottom bit is always 0.
LB_HistData_Temperature_MAX = ((rx_frame.data.u8[2] / 2) - 40);
}
if ((rx_frame.data.u8[0]>>6) == 3){ // Battery MIN temperature. Effectively has only 7-bit precision, as the bottom bit is always 0.
LB_HistData_Temperature_MIN = ((rx_frame.data.u8[2] / 2) - 40);
}
}
break;
case 0x59E:
//AZE0 2013-2017 or ZE1 2018-2023 battery detected
//Only detect as AZE0 if not already set as ZE1
if(LEAF_Battery_Type != ZE1_BATTERY){
LEAF_Battery_Type = AZE0_BATTERY;
}
break;
case 0x1ED:
case 0x1C2:
//ZE1 2018-2023 battery detected!
LEAF_Battery_Type = ZE1_BATTERY;
break;
case 0x79B:
stop_battery_query = 1; //Someone is trying to read data with Leafspy, stop our own polling!
hold_off_with_polling_10seconds = 10; //Polling is paused for 100s
break;
case 0x7BB:
//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];
if (group_7bb != 1 && group_7bb != 2 && group_7bb != 4) { //We are only interested in groups 1,2 and 4
break;
}
}
if(stop_battery_query){ //Leafspy is active, stop our own polling
break;
}
ESP32Can.CANWriteFrame(&LEAF_NEXT_LINE_REQUEST); //Request the next frame for the group
if(group_7bb == 1) //High precision SOC, Current, voltages etc.
{
if(rx_frame.data.u8[0] == 0x10){ //First frame
Battery_current_1 = (rx_frame.data.u8[4] << 24) | (rx_frame.data.u8[5] << 16 | ((rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]));
if(Battery_current_1 & 0x8000000 == 0x8000000){
Battery_current_1 = (( Battery_current_1 | -0x100000000 ) / 1024);
}
else{
Battery_current_1 = (Battery_current_1 / 1024);
}
}
if(rx_frame.data.u8[0] == 0x21){ //Second frame
Battery_current_2 = (rx_frame.data.u8[3] << 24) | (rx_frame.data.u8[4] << 16 | ((rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6]));
if(Battery_current_2 & 0x8000000 == 0x8000000){
Battery_current_2 = (( Battery_current_2 | -0x100000000 ) / 1024);
}
else{
Battery_current_2 = (Battery_current_2 / 1024);
}
}
if(rx_frame.data.u8[0] == 0x23){ // Fourth frame
insulation = (uint16_t) ((rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6]);
}
if(rx_frame.data.u8[0] == 0x24){ // Fifth frame
HX = (uint16_t) ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) / 102.4;
}
}
if(group_7bb == 2) //Cell Voltages
{
if(rx_frame.data.u8[0] == 0x10){ //first frame is anomalous
battery_request_idx = 0;
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7];
break;
}
if(rx_frame.data.u8[6] == 0xFF && rx_frame.data.u8[0] == 0x2C){ //Last frame
//Last frame does not contain any cell data, calculate the result
min_max_voltage[0] = 9999;
min_max_voltage[1] = 0;
for(cellcounter = 0; cellcounter < 96; cellcounter++){
if(min_max_voltage[0] > cell_voltages[cellcounter]) min_max_voltage[0] = cell_voltages[cellcounter];
if(min_max_voltage[1] < cell_voltages[cellcounter]) min_max_voltage[1] = cell_voltages[cellcounter];
}
cell_deviation_mV = (min_max_voltage[1] - min_max_voltage[0]);
cell_max_voltage = min_max_voltage[1];
cell_min_voltage = min_max_voltage[0];
if(cell_deviation_mV > MAX_CELL_DEVIATION){
LEDcolor = YELLOW;
Serial.println("HIGH CELL DEVIATION!!! Inspect battery!");
}
if(min_max_voltage[1] >= MAX_CELL_VOLTAGE){
bms_status = FAULT;
errorCode = 8;
Serial.println("CELL OVERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!");
}
if(min_max_voltage[0] <= MIN_CELL_VOLTAGE){
bms_status = FAULT;
errorCode = 9;
Serial.println("CELL UNDERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!");
}
break;
}
if((rx_frame.data.u8[0] % 2) == 0){ //even frames
cell_voltages[battery_request_idx++] |= rx_frame.data.u8[1];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7];
} else { //odd frames
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
cell_voltages[battery_request_idx] = (rx_frame.data.u8[7] << 8);
}
}
if(group_7bb == 4) { //Temperatures
if (rx_frame.data.u8[0] == 0x10) { //First message
temp_raw_1 = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
temp_raw_2_highnibble = rx_frame.data.u8[7];
}
if (rx_frame.data.u8[0] == 0x21) { //Second message
temp_raw_2 = (temp_raw_2_highnibble << 8) | rx_frame.data.u8[1];
temp_raw_3 = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
temp_raw_4 = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7];
}
if (rx_frame.data.u8[0] == 0x22) { //Third message
//All values read, let's figure out the min/max!
if(temp_raw_3 == 65535){ //We are on a 2013+ pack that only has three temp sensors.
//Start with finding max value
temp_raw_max = temp_raw_1;
if(temp_raw_2 > temp_raw_max){
temp_raw_max = temp_raw_2;
}
if(temp_raw_4 > temp_raw_max){
temp_raw_max = temp_raw_4;
}
//Then find min
temp_raw_min = temp_raw_1;
if(temp_raw_2 < temp_raw_min){
temp_raw_min = temp_raw_2;
}
if(temp_raw_4 < temp_raw_min){
temp_raw_min = temp_raw_4;
}
}
else{ //All 4 temp sensors available on 2011-2012
//Start with finding max value
temp_raw_max = temp_raw_1;
if(temp_raw_2 > temp_raw_max){
temp_raw_max = temp_raw_2;
}
if(temp_raw_3 > temp_raw_max){
temp_raw_max = temp_raw_3;
}
if(temp_raw_4 > temp_raw_max){
temp_raw_max = temp_raw_4;
}
//Then find min
temp_raw_min = temp_raw_1;
if(temp_raw_2 < temp_raw_min){
temp_raw_min = temp_raw_2;
}
if(temp_raw_3 < temp_raw_min){
temp_raw_min = temp_raw_2;
}
if(temp_raw_4 < temp_raw_min){
temp_raw_min = temp_raw_4;
}
}
}
}
break;
default:
break;
}
}
void send_can_leaf_battery()
{
unsigned long currentMillis = millis();
// Send 100ms CAN Message
if (currentMillis - previousMillis100 >= interval100)
{
previousMillis100 = currentMillis;
ESP32Can.CANWriteFrame(&LEAF_50B); //Always send 50B as a static message (Contains HCM_WakeUpSleepCommand == 11b == WakeUp, and CANMASK = 1)
mprun100++;
if (mprun100 > 3){
mprun100 = 0;
}
if (mprun100 == 0){
LEAF_50C.data.u8[3] = 0x00;
LEAF_50C.data.u8[4] = 0x5D;
LEAF_50C.data.u8[5] = 0xC8;
}
else if(mprun100 == 1){
LEAF_50C.data.u8[3] = 0x01;
LEAF_50C.data.u8[4] = 0xB2;
LEAF_50C.data.u8[5] = 0x31;
}
else if(mprun100 == 2){
LEAF_50C.data.u8[3] = 0x02;
LEAF_50C.data.u8[4] = 0x5D;
LEAF_50C.data.u8[5] = 0x63;
}
else if(mprun100 == 3){
LEAF_50C.data.u8[3] = 0x03;
LEAF_50C.data.u8[4] = 0xB2;
LEAF_50C.data.u8[5] = 0x9A;
}
ESP32Can.CANWriteFrame(&LEAF_50C);
}
//Send 10ms message
if (currentMillis - previousMillis10 >= interval10)
{
previousMillis10 = currentMillis;
if(mprun10 == 0){
LEAF_1D4.data.u8[4] = 0x07;
LEAF_1D4.data.u8[7] = 0x12;
}
else if(mprun10 == 1){
LEAF_1D4.data.u8[4] = 0x47;
LEAF_1D4.data.u8[7] = 0xD5;
}
else if(mprun10 == 2){
LEAF_1D4.data.u8[4] = 0x87;
LEAF_1D4.data.u8[7] = 0x19;
}
else if(mprun10 == 3){
LEAF_1D4.data.u8[4] = 0xC7;
LEAF_1D4.data.u8[7] = 0xDE;
}
ESP32Can.CANWriteFrame(&LEAF_1D4);
mprun10++;
if (mprun10 > 3){
mprun10 = 0;
}
switch(mprun10r)
{
case(0):
LEAF_1F2.data.u8[3] = 0xB0;
LEAF_1F2.data.u8[6] = 0x00;
LEAF_1F2.data.u8[7] = 0x8F;
break;
case(1):
LEAF_1F2.data.u8[3] = 0xB0;
LEAF_1F2.data.u8[6] = 0x01;
LEAF_1F2.data.u8[7] = 0x80;
break;
case(2):
LEAF_1F2.data.u8[3] = 0xB0;
LEAF_1F2.data.u8[6] = 0x02;
LEAF_1F2.data.u8[7] = 0x81;
break;
case(3):
LEAF_1F2.data.u8[3] = 0xB0;
LEAF_1F2.data.u8[6] = 0x03;
LEAF_1F2.data.u8[7] = 0x82;
break;
case(4):
LEAF_1F2.data.u8[3] = 0xB0;
LEAF_1F2.data.u8[6] = 0x00;
LEAF_1F2.data.u8[7] = 0x8F;
break;
case(5): // Set 2
LEAF_1F2.data.u8[3] = 0xB4;
LEAF_1F2.data.u8[6] = 0x01;
LEAF_1F2.data.u8[7] = 0x84;
break;
case(6):
LEAF_1F2.data.u8[3] = 0xB4;
LEAF_1F2.data.u8[6] = 0x02;
LEAF_1F2.data.u8[7] = 0x85;
break;
case(7):
LEAF_1F2.data.u8[3] = 0xB4;
LEAF_1F2.data.u8[6] = 0x03;
LEAF_1F2.data.u8[7] = 0x86;
break;
case(8):
LEAF_1F2.data.u8[3] = 0xB4;
LEAF_1F2.data.u8[6] = 0x00;
LEAF_1F2.data.u8[7] = 0x83;
break;
case(9):
LEAF_1F2.data.u8[3] = 0xB4;
LEAF_1F2.data.u8[6] = 0x01;
LEAF_1F2.data.u8[7] = 0x84;
break;
case(10): // Set 3
LEAF_1F2.data.u8[3] = 0xB0;
LEAF_1F2.data.u8[6] = 0x02;
LEAF_1F2.data.u8[7] = 0x81;
break;
case(11):
LEAF_1F2.data.u8[3] = 0xB0;
LEAF_1F2.data.u8[6] = 0x03;
LEAF_1F2.data.u8[7] = 0x82;
break;
case(12):
LEAF_1F2.data.u8[3] = 0xB0;
LEAF_1F2.data.u8[6] = 0x00;
LEAF_1F2.data.u8[7] = 0x8F;
break;
case(13):
LEAF_1F2.data.u8[3] = 0xB0;
LEAF_1F2.data.u8[6] = 0x01;
LEAF_1F2.data.u8[7] = 0x80;
break;
case(14):
LEAF_1F2.data.u8[3] = 0xB0;
LEAF_1F2.data.u8[6] = 0x02;
LEAF_1F2.data.u8[7] = 0x81;
break;
case(15): // Set 4
LEAF_1F2.data.u8[3] = 0xB4;
LEAF_1F2.data.u8[6] = 0x03;
LEAF_1F2.data.u8[7] = 0x86;
break;
case(16):
LEAF_1F2.data.u8[3] = 0xB4;
LEAF_1F2.data.u8[6] = 0x00;
LEAF_1F2.data.u8[7] = 0x83;
break;
case(17):
LEAF_1F2.data.u8[3] = 0xB4;
LEAF_1F2.data.u8[6] = 0x01;
LEAF_1F2.data.u8[7] = 0x84;
break;
case(18):
LEAF_1F2.data.u8[3] = 0xB4;
LEAF_1F2.data.u8[6] = 0x02;
LEAF_1F2.data.u8[7] = 0x85;
break;
case(19):
LEAF_1F2.data.u8[3] = 0xB4;
LEAF_1F2.data.u8[6] = 0x03;
LEAF_1F2.data.u8[7] = 0x86;
break;
default:
break;
}
ESP32Can.CANWriteFrame(&LEAF_1F2); //Contains (CHG_STA_RQ == 1 == Normal Charge)
mprun10r++;
if(mprun10r > 19){ // 0x1F2 patter repeats after 20 messages,
mprun10r = 0;
}
}
//Send 10s CAN messages
if (currentMillis - previousMillis10s >= interval10s)
{
previousMillis10s = currentMillis;
//Every 10s, ask diagnostic data from the battery. Don't ask if someone is already polling on the bus (Leafspy?)
if(!stop_battery_query){
if (group == 1){ // Cycle between group 1, 2, and 4 using bit manipulation
group = 2;
}
else if (group == 2){
group = 4;
}
else if (group == 4){
group = 1;
}
LEAF_GROUP_REQUEST.data.u8[2] = group;
ESP32Can.CANWriteFrame(&LEAF_GROUP_REQUEST);
}
if(hold_off_with_polling_10seconds > 0){
hold_off_with_polling_10seconds--;
}
else
{
stop_battery_query = 0;
}
}
}
uint16_t convert2unsignedint16(int16_t signed_value)
{
if(signed_value < 0){
return(65535 + signed_value);
}
else{
return (uint16_t)signed_value;
}
}
bool is_message_corrupt(CAN_frame_t rx_frame)
{
uint8_t crc = 0;
for (uint8_t j = 0; j < 7; j++){
crc = crctable[(crc ^ static_cast<uint8_t>(rx_frame.data.u8[j])) % 256];
}
return crc != rx_frame.data.u8[7];
}
uint16_t Temp_fromRAW_to_F(uint16_t temperature)
{ //This function feels horrible, but apparently works well
if (temperature == 1021) {
return 10;
} else if (temperature >= 589) {
return static_cast<uint16_t>(1620 - temperature * 1.81);
} else if (temperature >= 569) {
return static_cast<uint16_t>(572 + (579 - temperature) * 1.80);
} else if (temperature >= 558) {
return static_cast<uint16_t>(608 + (558 - temperature) * 1.6363636363636364);
} else if (temperature >= 548) {
return static_cast<uint16_t>(626 + (548 - temperature) * 1.80);
} else if (temperature >= 537) {
return static_cast<uint16_t>(644 + (537 - temperature) * 1.6363636363636364);
} else if (temperature >= 447) {
return static_cast<uint16_t>(662 + (527 - temperature) * 1.8);
} else if (temperature >= 438) {
return static_cast<uint16_t>(824 + (438 - temperature) * 2);
} else if (temperature >= 428) {
return static_cast<uint16_t>(842 + (428 - temperature) * 1.80);
} else if (temperature >= 365) {
return static_cast<uint16_t>(860 + (419 - temperature) * 2.0);
} else if (temperature >= 357) {
return static_cast<uint16_t>(986 + (357 - temperature) * 2.25);
} else if (temperature >= 348) {
return static_cast<uint16_t>(1004 + (348 - temperature) * 2);
} else if (temperature >= 316) {
return static_cast<uint16_t>(1022 + (340 - temperature) * 2.25);
}
return static_cast<uint16_t>(1094 + (309 - temperature) * 2.5714285714285715);
}

View file

@ -0,0 +1,47 @@
#ifndef NISSAN_LEAF_BATTERY_H
#define NISSAN_LEAF_BATTERY_H
#include <Arduino.h>
#include "../../ESP32CAN.h"
#include "../../USER_SETTINGS.h"
#define ABSOLUTE_MAX_VOLTAGE 4040 // 404.4V,if battery voltage goes over this, charging is not possible (goes into forced discharge)
#define ABSOLUTE_MIN_VOLTAGE 3100 // 310.0V if battery voltage goes under this, discharging further is disabled
// These parameters need to be mapped for the inverter
extern uint16_t SOC; //SOC%, 0-100.00 (0-10000)
extern uint16_t StateOfHealth; //SOH%, 0-100.00 (0-10000)
extern uint16_t battery_voltage; //V+1, 0-500.0 (0-5000)
extern uint16_t battery_current; //A+1, Goes thru convert2unsignedint16 function (5.0A = 50, -5.0A = 65485)
extern uint16_t capacity_Wh; //Wh, 0-60000
extern uint16_t remaining_capacity_Wh; //Wh, 0-60000
extern uint16_t max_target_discharge_power; //W, 0-60000
extern uint16_t max_target_charge_power; //W, 0-60000
extern uint16_t bms_status; //Enum, 0-5
extern uint16_t bms_char_dis_status; //Enum, 0-2
extern uint16_t stat_batt_power; //W, Goes thru convert2unsignedint16 function (5W = 5, -5W = 65530)
extern uint16_t temperature_min; //C+1, Goes thru convert2unsignedint16 function (15.0C = 150, -15.0C = 65385)
extern uint16_t temperature_max; //C+1, Goes thru convert2unsignedint16 function (15.0C = 150, -15.0C = 65385)
extern uint16_t cell_max_voltage; //mV, 0-4350
extern uint16_t cell_min_voltage; //mV, 0-4350
extern uint8_t batteryAllowsContactorClosing; //Bool, 1=true, 0=false
extern uint8_t LEDcolor; //Enum, 0-2
// Definitions for bms_status
#define STANDBY 0
#define INACTIVE 1
#define DARKSTART 2
#define ACTIVE 3
#define FAULT 4
#define UPDATING 5
// LED colors
#define GREEN 0
#define YELLOW 1
#define RED 2
void update_values_leaf_battery();
void receive_can_leaf_battery(CAN_frame_t rx_frame);
void send_can_leaf_battery();
uint16_t convert2unsignedint16(int16_t signed_value);
uint16_t Temp_fromRAW_to_F(uint16_t temperature);
bool is_message_corrupt(CAN_frame_t rx_frame);
#endif

View file

@ -0,0 +1,167 @@
#include "RENAULT-ZOE-BATTERY.h"
#include "../../ESP32CAN.h"
#include "../../CAN_config.h"
/* Do not change code below unless you are sure what you are doing */
#define LB_MAX_SOC 1000 //BMS never goes over this value. We use this info to rescale SOC% sent to Fronius
#define LB_MIN_SOC 0 //BMS never goes below this value. We use this info to rescale SOC% sent to Fronius
const int rx_queue_size = 10; // Receive Queue size
static uint8_t CANstillAlive = 12; //counter for checking if CAN is still alive
static uint8_t errorCode = 0; //stores if we have an error code active from battery control logic
static int16_t LB_SOC = 0;
static int16_t LB_SOH = 0;
static int16_t LB_MIN_TEMPERATURE = 0;
static int16_t LB_MAX_TEMPERATURE = 0;
static uint16_t LB_Discharge_Power_Limit = 0;
static uint32_t LB_Discharge_Power_Limit_Watts = 0;
static uint16_t LB_Charge_Power_Limit = 0;
static uint32_t LB_Charge_Power_Limit_Watts = 0;
CAN_frame_t ZOE_423 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_std,}},.MsgID = 0x423,.data = {0x33, 0x00, 0xFF, 0xFF, 0x00, 0xE0, 0x00, 0x00}};
static unsigned long previousMillis10 = 0; // will store last time a 10ms CAN Message was sent
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was sent
static const int interval10 = 10; // interval (ms) at which send CAN Messages
static const int interval100 = 100; // interval (ms) at which send CAN Messages
static int BMSPollCounter = 0;
void update_values_zoe_battery()
{ //This function maps all the values fetched via CAN to the correct parameters used for modbus
bms_status = ACTIVE; //Startout in active mode
StateOfHealth = (LB_SOH * 100); //Increase range from 99% -> 99.00%
//Calculate the SOC% value to send to Fronius
LB_SOC = LB_MIN_SOC + (LB_MAX_SOC - LB_MIN_SOC) * (LB_SOC - MINPERCENTAGE) / (MAXPERCENTAGE - MINPERCENTAGE);
if (LB_SOC < 0)
{ //We are in the real SOC% range of 0-20%, always set SOC sent to Fronius as 0%
LB_SOC = 0;
}
if (LB_SOC > 1000)
{ //We are in the real SOC% range of 80-100%, always set SOC sent to Fronius as 100%
LB_SOC = 1000;
}
SOC = (LB_SOC * 10); //increase LB_SOC range from 0-100.0 -> 100.00
battery_voltage;
battery_current;
capacity_Wh = BATTERY_WH_MAX;
remaining_capacity_Wh;
LB_Discharge_Power_Limit_Watts = (LB_Discharge_Power_Limit * 500); //Convert value fetched from battery to watts
/* Define power able to be discharged from battery */
if(LB_Discharge_Power_Limit_Watts > 30000) //if >30kW can be pulled from battery
{
max_target_discharge_power = 30000; //cap value so we don't go over the Fronius limits
}
else
{
max_target_discharge_power = LB_Discharge_Power_Limit_Watts;
}
if(SOC == 0) //Scaled SOC% value is 0.00%, we should not discharge battery further
{
max_target_discharge_power = 0;
}
LB_Charge_Power_Limit_Watts = (LB_Charge_Power_Limit * 500); //Convert value fetched from battery to watts
/* Define power able to be put into the battery */
if(LB_Charge_Power_Limit_Watts > 30000) //if >30kW can be put into the battery
{
max_target_charge_power = 30000; //cap value so we don't go over the Fronius limits
}
if(LB_Charge_Power_Limit_Watts < 0)
{
max_target_charge_power = 0; //cap calue so we dont do under the Fronius limits
}
else
{
max_target_charge_power = LB_Charge_Power_Limit_Watts;
}
if(SOC == 10000) //Scaled SOC% value is 100.00%
{
max_target_charge_power = 0; //No need to charge further, set max power to 0
}
/* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/
if(!CANstillAlive)
{
bms_status = FAULT;
Serial.println("No CAN communication detected for 60s. Shutting down battery control.");
}
else
{
CANstillAlive--;
}
stat_batt_power;
temperature_min;
temperature_max;
#ifdef DEBUG_VIA_USB
Serial.print("BMS Status (3=OK): ");
Serial.println(bms_status);
Serial.print("Max discharge power: ");
Serial.println(max_target_discharge_power);
Serial.print("Max charge power: ");
Serial.println(max_target_charge_power);
Serial.print("SOH%: ");
Serial.println(LB_SOH);
Serial.print("SOH% to Fronius: ");
Serial.println(StateOfHealth);
Serial.print("LB_SOC: ");
Serial.println(LB_SOC);
Serial.print("SOC% to Fronius: ");
Serial.println(SOC);
Serial.print("Temperature Min: ");
Serial.println(temperature_min);
Serial.print("Temperature Max: ");
Serial.println(temperature_max);
#endif
}
void receive_can_zoe_battery(CAN_frame_t rx_frame)
{
switch (rx_frame.MsgID)
{
case 0x155: //BMS1
CANstillAlive = 12; //Indicate that we are still getting CAN messages from the BMS
//LB_Max_Charge_Amps =
//LB_Current = (((rx_frame.data.u8[1] & 0xF8) << 5) | (rx_frame.data.u8[2]));
LB_SOC = ((rx_frame.data.u8[4] << 8) | (rx_frame.data.u8[5]));
break;
case 0x424: //BMS2
LB_Charge_Power_Limit = (rx_frame.data.u8[2]);
LB_Discharge_Power_Limit = (rx_frame.data.u8[3]);
LB_SOH = (rx_frame.data.u8[5]);
LB_MIN_TEMPERATURE = ((rx_frame.data.u8[4] & 0x7F) - 40);
LB_MAX_TEMPERATURE = ((rx_frame.data.u8[7] & 0x7F) - 40);
break;
case 0x425: //BMS3 (could also be 445?)
//LB_kWh_Remaining =
//LB_Cell_Max_Voltage =
//LB_Cell_Min_Voltage =
break;
default:
break;
}
}
void send_can_zoe_battery()
{
unsigned long currentMillis = millis();
// Send 100ms CAN Message
if (currentMillis - previousMillis100 >= interval100)
{
previousMillis100 = currentMillis;
BMSPollCounter++; //GKOE
if (BMSPollCounter < 46) //GKOE
{ //The Kangoo batteries (also Zoe?) dont like getting this message continously, so we pause after 4.6s, and resume after 40s
ESP32Can.CANWriteFrame(&ZOE_423); //Send 0x423 to keep BMS happy
}
if (BMSPollCounter > 400) //GKOE
{
BMSPollCounter = 0;
}
}
}

View file

@ -0,0 +1,38 @@
#ifndef RENAULT_ZOE_BATTERY_H
#define RENAULT_ZOE_BATTERY_H
#include <Arduino.h>
#include "../../ESP32CAN.h"
#include "../../USER_SETTINGS.h"
#define ABSOLUTE_MAX_VOLTAGE 4040 // 404.4V,if battery voltage goes over this, charging is not possible (goes into forced discharge)
#define ABSOLUTE_MIN_VOLTAGE 3100 // 310.0V if battery voltage goes under this, discharging further is disabled
// These parameters need to be mapped for the Gen24
extern uint16_t SOC;
extern uint16_t StateOfHealth;
extern uint16_t battery_voltage;
extern uint16_t battery_current;
extern uint16_t capacity_Wh;
extern uint16_t remaining_capacity_Wh;
extern uint16_t max_target_discharge_power;
extern uint16_t max_target_charge_power;
extern uint16_t bms_status;
extern uint16_t bms_char_dis_status;
extern uint16_t stat_batt_power;
extern uint16_t temperature_min;
extern uint16_t temperature_max;
extern uint16_t CANerror;
// Definitions for BMS status
#define STANDBY 0
#define INACTIVE 1
#define DARKSTART 2
#define ACTIVE 3
#define FAULT 4
#define UPDATING 5
void update_values_zoe_battery();
void receive_can_zoe_battery(CAN_frame_t rx_frame);
void send_can_zoe_battery();
uint16_t convert2unsignedint16(uint16_t signed_value);
#endif

View file

@ -0,0 +1,405 @@
#include "TESLA-MODEL-3-BATTERY.h"
#include "../../ESP32CAN.h"
#include "../../CAN_config.h"
/* Do not change code below unless you are sure what you are doing */
/* Credits: Most of the code comes from Per Carlen's bms_comms_tesla_model3.py (https://gitlab.com/pelle8/batt2gen24/) */
static unsigned long previousMillis30 = 0; // will store last time a 30ms CAN Message was send
static const int interval30 = 30; // interval (ms) at which send CAN Messages
static uint8_t stillAliveCAN = 6; //counter for checking if CAN is still alive
CAN_frame_t TESLA_221_1 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_std,}},.MsgID = 0x221,.data = {0x41, 0x11, 0x01, 0x00, 0x00, 0x00, 0x20, 0x96}}; //Contactor frame 221 - close contactors
CAN_frame_t TESLA_221_2 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_std,}},.MsgID = 0x221,.data = {0x61, 0x15, 0x01, 0x00, 0x00, 0x00, 0x20, 0xBA}}; //Contactor Frame 221 - hv_up_for_drive
static uint32_t temporaryvariable = 0;
static uint32_t total_discharge = 0;
static uint32_t total_charge = 0;
static uint16_t volts = 0; // V
static int16_t amps = 0; // A
static uint16_t raw_amps = 0; // A
static int16_t max_temp = 6; // C*
static int16_t min_temp = 5; // C*
static uint16_t energy_buffer = 0;
static uint16_t energy_to_charge_complete = 0;
static uint16_t expected_energy_remaining = 0;
static uint8_t full_charge_complete = 0;
static uint16_t ideal_energy_remaining = 0;
static uint16_t nominal_energy_remaining = 0;
static uint16_t nominal_full_pack_energy = 0;
static uint16_t battery_charge_time_remaining = 0; // Minutes
static uint16_t regenerative_limit = 0;
static uint16_t discharge_limit = 0;
static uint16_t max_heat_park = 0;
static uint16_t hvac_max_power = 0;
static uint16_t min_voltage = 0;
static uint16_t max_discharge_current = 0;
static uint16_t max_charge_current = 0;
static uint16_t max_voltage = 0;
static uint16_t high_voltage = 0;
static uint16_t low_voltage = 0;
static uint16_t output_current = 0;
static uint16_t soc_min = 0;
static uint16_t soc_max = 0;
static uint16_t soc_vi = 0;
static uint16_t soc_ave = 0;
static uint16_t cell_max_v = 3700;
static uint16_t cell_min_v = 3700;
static uint16_t cell_deviation_mV = 0; //contains the deviation between highest and lowest cell in mV
static uint8_t max_vno = 0;
static uint8_t min_vno = 0;
static uint8_t contactor = 0; //State of contactor
static uint8_t hvil_status = 0;
static uint8_t packContNegativeState = 0;
static uint8_t packContPositiveState = 0;
static uint8_t packContactorSetState = 0;
static uint8_t packCtrsClosingAllowed = 0;
static uint8_t pyroTestInProgress = 0;
static uint8_t send221still = 10;
static const char* contactorText[] = {"UNKNOWN(0)","OPEN","CLOSING","BLOCKED","OPENING","CLOSED","UNKNOWN(6)","WELDED","POS_CL","NEG_CL","UNKNOWN(10)","UNKNOWN(11)","UNKNOWN(12)"};
static const char* contactorState[] = {"SNA","OPEN","PRECHARGE","BLOCKED","PULLED_IN","OPENING","ECONOMIZED","WELDED","UNKNOWN(8)","UNKNOWN(9)","UNKNOWN(10)","UNKNOWN(11)"};
static const char* hvilStatusState[] = {"NOT OK","STATUS_OK","CURRENT_SOURCE_FAULT","INTERNAL_OPEN_FAULT","VEHICLE_OPEN_FAULT","PENTHOUSE_LID_OPEN_FAULT","UNKNOWN_LOCATION_OPEN_FAULT","VEHICLE_NODE_FAULT","NO_12V_SUPPLY","VEHICLE_OR_PENTHOUSE_LID_OPENFAULT","UNKNOWN(10)","UNKNOWN(11)","UNKNOWN(12)","UNKNOWN(13)","UNKNOWN(14)","UNKNOWN(15)"};
#define MAX_SOC 1000 //BMS never goes over this value. We use this info to rescale SOC% sent to inverter
#define MIN_SOC 0 //BMS never goes below this value. We use this info to rescale SOC% sent to inverter
#define MAX_CELL_VOLTAGE 4250 //Battery is put into emergency stop if one cell goes over this value (These values might need tweaking based on chemistry)
#define MIN_CELL_VOLTAGE 2950 //Battery is put into emergency stop if one cell goes below this value (These values might need tweaking based on chemistry)
#define MAX_CELL_DEVIATION 500 //LED turns yellow on the board if mv delta exceeds this value
void print_int_with_units(char *header, int value, char *units) {
Serial.print(header);
Serial.print(value);
Serial.print(units);
}
void print_SOC(char *header, int SOC) {
Serial.print(header);
Serial.print(SOC / 100);
Serial.print(".");
int hundredth = SOC % 100;
if(hundredth < 10)
Serial.print(0);
Serial.print(hundredth);
Serial.println("%");
}
void update_values_tesla_model_3_battery()
{ //This function maps all the values fetched via CAN to the correct parameters used for modbus
//After values are mapped, we perform some safety checks, and do some serial printouts
StateOfHealth = 9900; //Hardcoded to 99%SOH
//Calculate the SOC% value to send to inverter
soc_vi = MIN_SOC + (MAX_SOC - MIN_SOC) * (soc_vi - MINPERCENTAGE) / (MAXPERCENTAGE - MINPERCENTAGE);
if (soc_vi < 0)
{ //We are in the real SOC% range of 0-20%, always set SOC sent to Inverter as 0%
soc_vi = 0;
}
if (soc_vi > 1000)
{ //We are in the real SOC% range of 80-100%, always set SOC sent to Inverter as 100%
soc_vi = 1000;
}
SOC = (soc_vi * 10); //increase SOC range from 0-100.0 -> 100.00
battery_voltage = (volts*10); //One more decimal needed (370 -> 3700)
battery_current = amps; //TODO, this needs verifying if scaling is right
capacity_Wh = (nominal_full_pack_energy * 100); //Scale up 75.2kWh -> 75200Wh
if(capacity_Wh > 60000)
{
capacity_Wh = 60000;
}
remaining_capacity_Wh = (expected_energy_remaining * 100); //Scale up 60.3kWh -> 60300Wh
//Calculate the allowed discharge power, cap it if it gets too large
temporaryvariable = (max_discharge_current * volts);
if(temporaryvariable > 60000){
max_target_discharge_power = 60000;
}
else{
max_target_discharge_power = temporaryvariable;
}
//The allowed charge power behaves strangely. We instead estimate this value
if(SOC == 10000){
max_target_charge_power = 0; //When battery is 100% full, set allowed charge W to 0
}
else{
max_target_charge_power = 15000; //Otherwise we can push 15kW into the pack!
}
stat_batt_power = (volts * amps); //TODO, check if scaling is OK
min_temp = (min_temp * 10);
temperature_min = convert2unsignedInt16(min_temp);
max_temp = (max_temp * 10);
temperature_max = convert2unsignedInt16(max_temp);
cell_max_voltage = cell_max_v;
cell_min_voltage = cell_min_v;
/* Value mapping is completed. Start to check all safeties */
bms_status = ACTIVE; //Startout in active mode before checking if we have any faults
/* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/
if(!stillAliveCAN)
{
bms_status = FAULT;
Serial.println("ERROR: No CAN communication detected for 60s. Shutting down battery control.");
}
else
{
stillAliveCAN--;
}
if (hvil_status == 3){ //INTERNAL_OPEN_FAULT - Someone disconnected a high voltage cable while battery was in use
bms_status = FAULT;
Serial.println("ERROR: High voltage cable removed while battery running. Opening contactors!");
}
if(cell_max_v >= MAX_CELL_VOLTAGE){
bms_status = FAULT;
Serial.println("ERROR: CELL OVERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!");
}
if(cell_min_v <= MIN_CELL_VOLTAGE){
bms_status = FAULT;
Serial.println("ERROR: CELL UNDERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!");
}
cell_deviation_mV = (cell_max_v - cell_min_v);
if(cell_deviation_mV > MAX_CELL_DEVIATION){
LEDcolor = YELLOW;
Serial.println("ERROR: HIGH CELL DEVIATION!!! Inspect battery!");
}
/* Safeties verified. Perform USB serial printout if configured to do so */
#ifdef DEBUG_VIA_USB
if (packCtrsClosingAllowed == 0)
{
Serial.println("ERROR: Check high voltage connectors and interlock circuit! Closing contactor not allowed! Values: ");
}
if (pyroTestInProgress == 1)
{
Serial.println("ERROR: Please wait for Pyro Connection check to finish, HV cables successfully seated!");
}
Serial.print("STATUS: Contactor: ");
Serial.print(contactorText[contactor]); //Display what state the contactor is in
Serial.print(", HVIL: ");
Serial.print(hvilStatusState[hvil_status]);
Serial.print(", NegativeState: ");
Serial.print(contactorState[packContNegativeState]);
Serial.print(", PositiveState: ");
Serial.print(contactorState[packContPositiveState]);
Serial.print(", setState: ");
Serial.print(contactorState[packContactorSetState]);
Serial.print(", close allowed: ");
Serial.print(packCtrsClosingAllowed);
Serial.print(", Pyrotest: ");
Serial.println(pyroTestInProgress);
Serial.print("Battery values: ");
Serial.print(" Vi SOC: ");
Serial.print(soc_vi);
Serial.print(", SOC max: ");
Serial.print(soc_max);
Serial.print(", SOC min: ");
Serial.print(soc_min);
Serial.print(", SOC avg: ");
Serial.print(soc_ave);
print_int_with_units(", Battery voltage: ", volts, "V");
print_int_with_units(", Battery current: ", amps, "A");
Serial.println("");
print_int_with_units("Discharge limit battery: ", discharge_limit, "kW");
Serial.print(", ");
print_int_with_units("Charge limit battery: ", regenerative_limit, "kW");
Serial.print("kW");
Serial.print(", Fully charged?: ");
if(full_charge_complete)
Serial.print("YES, ");
else
Serial.print("NO, ");
print_int_with_units("Min voltage allowed: ", min_voltage, "V");
Serial.print(", ");
print_int_with_units("Max voltage allowed: ", max_voltage, "V");
Serial.println("");
print_int_with_units("Max charge current: ", max_charge_current, "A");
Serial.print(", ");
print_int_with_units("Max discharge current: ", max_discharge_current, "A");
Serial.println("");
Serial.print("Cellstats, Max: ");
Serial.print(cell_max_v);
Serial.print("mV (cell ");
Serial.print(max_vno);
Serial.print("), Min: ");
Serial.print(cell_min_v);
Serial.print("mV (cell ");
Serial.print(min_vno);
Serial.print("), Imbalance: ");
Serial.print(cell_deviation_mV);
Serial.println("mV.");
print_int_with_units("High Voltage Output Pins: ", high_voltage, "V");
Serial.print(", ");
print_int_with_units("Low Voltage: ", low_voltage, "V");
Serial.println("");
print_int_with_units("Current Output: ", output_current, "A");
Serial.println("");
Serial.println("Values passed to the inverter: ");
print_SOC(" SOC: ", SOC);
print_int_with_units(" Max discharge power: ", max_target_discharge_power, "W");
Serial.print(", ");
print_int_with_units(" Max charge power: ", max_target_charge_power, "W");
Serial.println("");
print_int_with_units(" Max temperature: ", temperature_max, "C");
Serial.print(", ");
print_int_with_units(" Min temperature: ", temperature_min, "C");
Serial.println("");
#endif
}
void receive_can_tesla_model_3_battery(CAN_frame_t rx_frame)
{
static int mux = 0;
static int temp = 0;
switch (rx_frame.MsgID)
{
case 0x352:
//SOC
nominal_full_pack_energy = (((rx_frame.data.u8[1] & 0x0F) << 8) | (rx_frame.data.u8[0])); //Example 752 (75.2kWh)
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
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)
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
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
energy_buffer = (((rx_frame.data.u8[7] & 0x7F) << 1) | ((rx_frame.data.u8[6] & 0x80) >> 7)) * 0.1; //Example 1 * 0.1 = 0
full_charge_complete = ((rx_frame.data.u8[7] & 0x80) >> 7);
break;
case 0x20A:
//Contactor state
packContNegativeState = (rx_frame.data.u8[0] & 0x07);
packContPositiveState = (rx_frame.data.u8[0] & 0x38) >> 3;
contactor = (rx_frame.data.u8[1] & 0x0F);
packContactorSetState = (rx_frame.data.u8[1] & 0x0F);
packCtrsClosingAllowed = (rx_frame.data.u8[4] & 0x08) >> 3;
pyroTestInProgress = (rx_frame.data.u8[4] & 0x20) >> 5;
hvil_status = (rx_frame.data.u8[5] & 0x0F);
break;
case 0x252:
//Limits
regenerative_limit = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]) * 0.01; //Example 4715 * 0.01 = 47.15kW
discharge_limit = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]) * 0.013; //Example 2009 * 0.013 = 26.117???
max_heat_park = (((rx_frame.data.u8[5] & 0x03) << 8) | rx_frame.data.u8[4]) * 0.01; //Example 500 * 0.01 = 5kW
hvac_max_power = (((rx_frame.data.u8[7] << 6) | ((rx_frame.data.u8[6] & 0xFC) >> 2))) * 0.02; //Example 1000 * 0.02 = 20kW?
break;
case 0x132:
//battery amps/volts
volts = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]) * 0.01; //Example 37030mv * 0.01 = 370V
amps = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]); //Example 65492 (-4.3A) OR 225 (22.5A)
if (amps > 32768)
{
amps = - (65535 - amps);
}
amps = amps * 0.1;
raw_amps = ((rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4]) * -0.05; //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
if(battery_charge_time_remaining == 4095)
{
battery_charge_time_remaining = 0;
}
break;
case 0x3D2:
// total charge/discharge kwh
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;
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;
break;
case 0x332:
//min/max hist values
mux = (rx_frame.data.u8[0] & 0x03);
if(mux == 1) //Cell voltages
{
temp = ((rx_frame.data.u8[1] << 6) | (rx_frame.data.u8[0] >> 2));
temp = (temp & 0xFFF);
cell_max_v = temp*2;
temp = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]);
temp = (temp & 0xFFF);
cell_min_v = temp*2;
max_vno = 1 + (rx_frame.data.u8[4] & 0x7F); //This cell has highest voltage
min_vno = 1 + (rx_frame.data.u8[5] & 0x7F); //This cell has lowest voltage
}
if(mux == 0)//Temperature sensors
{
temp = rx_frame.data.u8[2];
max_temp = (temp * 0.5) - 40; //in celcius, Example 24
temp = rx_frame.data.u8[3];
min_temp = (temp * 0.5) - 40; //in celcius , Example 24
}
break;
case 0x2d2:
//Min / max limits
min_voltage = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]) * 0.01 * 2; //Example 24148mv * 0.01 = 241.48 V
max_voltage = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]) * 0.01 * 2; //Example 40282mv * 0.01 = 402.82 V
max_charge_current = (((rx_frame.data.u8[5] & 0x3F) << 8) | rx_frame.data.u8[4]) * 0.1; //Example 1301? * 0.1 = 130.1?
max_discharge_current = (((rx_frame.data.u8[7] & 0x3F) << 8) | rx_frame.data.u8[6]) * 0.128; //Example 430? * 0.128 = 55.4?
break;
case 0x2b4:
low_voltage = (((rx_frame.data.u8[1] & 0x03) << 8) | rx_frame.data.u8[0]) * 0.0390625;
high_voltage = (((rx_frame.data.u8[2] << 6) | ((rx_frame.data.u8[1] & 0xFC) >> 2))) * 0.146484;
output_current = (((rx_frame.data.u8[4] & 0x0F) << 8) | rx_frame.data.u8[3]) / 100;
break;
case 0x292:
stillAliveCAN = 12; //We are getting CAN messages from the BMS, set the CAN detect counter
soc_min = (((rx_frame.data.u8[1] & 0x03) << 8) | rx_frame.data.u8[0]);
soc_vi = (((rx_frame.data.u8[2] & 0x0F) << 6) | ((rx_frame.data.u8[1] & 0xFC) >> 2));
soc_max = (((rx_frame.data.u8[3] & 0x3F) << 4) | ((rx_frame.data.u8[2] & 0xF0) >> 4));
soc_ave = ((rx_frame.data.u8[4] << 2) | ((rx_frame.data.u8[3] & 0xC0) >> 6));
break;
default:
break;
}
}
void send_can_tesla_model_3_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
two 221 messages are being continuously transmitted. When I want to shut down, I stop the second message and only send
the first, for a few cycles, then stop all messages which causes the contactor to open. */
unsigned long currentMillis = millis();
//Send 30ms message
if (currentMillis - previousMillis30 >= interval30)
{
previousMillis30 = currentMillis;
if(bms_status == ACTIVE){
send221still = 10;
ESP32Can.CANWriteFrame(&TESLA_221_1);
ESP32Can.CANWriteFrame(&TESLA_221_2);
}
else{ //bms_status == FAULT
if(send221still > 0){
ESP32Can.CANWriteFrame(&TESLA_221_1);
send221still--;
}
}
}
}
uint16_t convert2unsignedInt16(int16_t signed_value)
{
if(signed_value < 0){
return(65535 + signed_value);
}
else{
return (uint16_t)signed_value;
}
}

View file

@ -0,0 +1,45 @@
#ifndef TESLA_MODEL_3_BATTERY_H
#define TESLA_MODEL_3_BATTERY_H
#include <Arduino.h>
#include "../../ESP32CAN.h"
#include "../../USER_SETTINGS.h"
#define ABSOLUTE_MAX_VOLTAGE 4030 // 403.0V,if battery voltage goes over this, charging is not possible (goes into forced discharge)
#define ABSOLUTE_MIN_VOLTAGE 2450 // 245.0V if battery voltage goes under this, discharging further is disabled
// These parameters need to be mapped for the Inverter
extern uint16_t SOC;
extern uint16_t StateOfHealth;
extern uint16_t battery_voltage;
extern uint16_t battery_current;
extern uint16_t capacity_Wh;
extern uint16_t remaining_capacity_Wh;
extern uint16_t max_target_discharge_power;
extern uint16_t max_target_charge_power;
extern uint16_t bms_status;
extern uint16_t bms_char_dis_status;
extern uint16_t stat_batt_power;
extern uint16_t temperature_min;
extern uint16_t temperature_max;
extern uint16_t CANerror;
extern uint16_t cell_max_voltage;
extern uint16_t cell_min_voltage;
extern uint8_t LEDcolor;
// Definitions for BMS status
#define STANDBY 0
#define INACTIVE 1
#define DARKSTART 2
#define ACTIVE 3
#define FAULT 4
#define UPDATING 5
// LED colors
#define GREEN 0
#define YELLOW 1
#define RED 2
void update_values_tesla_model_3_battery();
void receive_can_tesla_model_3_battery(CAN_frame_t rx_frame);
void send_can_tesla_model_3_battery();
uint16_t convert2unsignedInt16(int16_t signed_value);
#endif