Rewrite CAN handler. Add Solax

This commit is contained in:
Daniel 2023-07-04 11:54:09 +03:00
parent 0e3af999f0
commit deb664d097
12 changed files with 400 additions and 307 deletions

View file

@ -3,13 +3,12 @@
#include "CAN_config.h" #include "CAN_config.h"
/* Do not change code below unless you are sure what you are doing */ /* Do not change code below unless you are sure what you are doing */
unsigned long previousMillis2s = 0; // will store last time a 2s CAN Message was send static unsigned long previousMillis2s = 0; // will store last time a 2s CAN Message was send
unsigned long previousMillis10s = 0; // will store last time a 10s CAN Message was send static unsigned long previousMillis10s = 0; // will store last time a 10s CAN Message was send
unsigned long previousMillis60s = 0; // will store last time a 60s CAN Message was send static unsigned long previousMillis60s = 0; // will store last time a 60s CAN Message was send
const int interval2s = 2000; // interval (ms) at which send CAN Messages static const int interval2s = 2000; // interval (ms) at which send CAN Messages
const int interval10s = 10000; // interval (ms) at which send CAN Messages static const int interval10s = 10000; // interval (ms) at which send CAN Messages
const int interval60s = 60000; // interval (ms) at which send CAN Messages static const int interval60s = 60000; // interval (ms) at which send CAN Messages
const int rx_queue_size = 10; // Receive Queue size
//Constant startup messages //Constant startup messages
const CAN_frame_t BYD_250 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_std,}},.MsgID = 0x250,.data = {0x03, 0x16, 0x00, 0x66, 0x00, 0x33, 0x02, 0x09}}; const CAN_frame_t BYD_250 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_std,}},.MsgID = 0x250,.data = {0x03, 0x16, 0x00, 0x66, 0x00, 0x33, 0x02, 0x09}};
@ -74,7 +73,22 @@ void update_values_can_byd()
BYD_210.data.u8[3] = (temperature_min & 0x00FF); BYD_210.data.u8[3] = (temperature_min & 0x00FF);
} }
void handle_can_byd() void receive_can_byd(CAN_frame_t rx_frame)
{
switch (rx_frame.MsgID)
{
case 0x151: //Message originating from BYD HVS compatible inverter. Reply with CAN identifier!
if(rx_frame.data.u8[0] & 0x01)
{
send_intial_data();
}
break;
default:
break;
}
}
void send_can_byd()
{ {
unsigned long currentMillis = millis(); unsigned long currentMillis = millis();
// Send 2s CAN Message // Send 2s CAN Message

View file

@ -28,7 +28,8 @@ extern uint16_t max_volt_byd_can;
#define UPDATING 5 #define UPDATING 5
void update_values_can_byd(); void update_values_can_byd();
void handle_can_byd(); void send_can_byd();
void receive_can_byd(CAN_frame_t rx_frame);
void send_intial_data(); void send_intial_data();
#endif #endif

12
Software/INVERTERS.h Normal file
View file

@ -0,0 +1,12 @@
#ifndef INVERTERS_H
#define INVERTERS_H
#ifdef SOLAX_CAN
#include "SOLAX-CAN.h"
#endif
#ifdef CAN_BYD
#include "BYD-CAN.h"
#endif
#endif

View file

@ -274,140 +274,121 @@ void update_values_leaf_battery()
} }
} }
void handle_can_leaf_battery() void receive_can_leaf_battery(CAN_frame_t rx_frame)
{ {
CAN_frame_t rx_frame; switch (rx_frame.MsgID)
static unsigned long currentMillis = millis();
// Receive next CAN frame from queue
if (xQueueReceive(CAN_cfg.rx_queue, &rx_frame, 3 * portTICK_PERIOD_MS) == pdTRUE)
{ {
if (rx_frame.FIR.B.FF == CAN_frame_std) case 0x1DB:
if(is_message_corrupt(rx_frame))
{ {
//printf("New standard frame"); CANerror++;
switch (rx_frame.MsgID) break; //Message content malformed, abort reading data from it
{ }
case 0x1DB: LB_Current = (rx_frame.data.u8[0] << 3) | (rx_frame.data.u8[1] & 0xe0) >> 5;
if(is_message_corrupt(rx_frame)) if (LB_Current & 0x0400)
{ {
CANerror++; // negative so extend the sign bit
break; //Message content malformed, abort reading data from it LB_Current |= 0xf800;
} }
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;
}
LB_Total_Voltage = ((rx_frame.data.u8[2] << 2) | (rx_frame.data.u8[3] & 0xc0) >> 6) / 2; LB_Total_Voltage = ((rx_frame.data.u8[2] << 2) | (rx_frame.data.u8[3] & 0xc0) >> 6) / 2;
//Collect various data from the BMS //Collect various data from the BMS
LB_Relay_Cut_Request = ((rx_frame.data.u8[1] & 0x18) >> 3); LB_Relay_Cut_Request = ((rx_frame.data.u8[1] & 0x18) >> 3);
LB_Failsafe_Status = (rx_frame.data.u8[1] & 0x07); LB_Failsafe_Status = (rx_frame.data.u8[1] & 0x07);
LB_MainRelayOn_flag = (byte) ((rx_frame.data.u8[3] & 0x20) >> 5); LB_MainRelayOn_flag = (byte) ((rx_frame.data.u8[3] & 0x20) >> 5);
LB_Full_CHARGE_flag = (byte) ((rx_frame.data.u8[3] & 0x10) >> 4); LB_Full_CHARGE_flag = (byte) ((rx_frame.data.u8[3] & 0x10) >> 4);
LB_Interlock = (byte) ((rx_frame.data.u8[3] & 0x08) >> 3); LB_Interlock = (byte) ((rx_frame.data.u8[3] & 0x08) >> 3);
break; break;
case 0x1DC: case 0x1DC:
if(is_message_corrupt(rx_frame)) if(is_message_corrupt(rx_frame))
{ {
CANerror++; CANerror++;
break; //Message content malformed, abort reading data from it 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_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) << 2 | rx_frame.data.u8[2] >> 4) / 4.0); LB_Charge_Power_Limit = (((rx_frame.data.u8[1] & 0x3F) << 2 | 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); LB_MAX_POWER_FOR_CHARGER = ((((rx_frame.data.u8[2] & 0x0F) << 6 | rx_frame.data.u8[3] >> 2) / 10.0) - 10);
break; break;
case 0x55B: case 0x55B:
if(is_message_corrupt(rx_frame)) if(is_message_corrupt(rx_frame))
{ {
CANerror++; CANerror++;
break; //Message content malformed, abort reading data from it break; //Message content malformed, abort reading data from it
} }
LB_TEMP = (rx_frame.data.u8[0] << 2 | rx_frame.data.u8[1] >> 6); LB_TEMP = (rx_frame.data.u8[0] << 2 | rx_frame.data.u8[1] >> 6);
if (LB_TEMP != 0x3ff) //3FF is unavailable value if (LB_TEMP != 0x3ff) //3FF is unavailable value
{ {
LB_SOC = LB_TEMP; LB_SOC = LB_TEMP;
} }
break; break;
case 0x5BC: case 0x5BC:
CANstillAlive = 12; //Indicate that we are still getting CAN messages from the BMS CANstillAlive = 12; //Indicate that we are still getting CAN messages from the BMS
LB_MAX = ((rx_frame.data.u8[5] & 0x10) >> 4); LB_MAX = ((rx_frame.data.u8[5] & 0x10) >> 4);
if (LB_MAX) if (LB_MAX)
{ {
LB_Max_GIDS = (rx_frame.data.u8[0] << 2) | ((rx_frame.data.u8[1] & 0xC0) >> 6); LB_Max_GIDS = (rx_frame.data.u8[0] << 2) | ((rx_frame.data.u8[1] & 0xC0) >> 6);
//Max gids active, do nothing //Max gids active, do nothing
//Only the 30/40/62kWh packs have this mux //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);
}
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);
}
}
if(LEAF_Battery_Type == ZE1_BATTERY)
{ //note different mux location in first frame
if ((rx_frame.data.u8[0] & 0x0F) == 1)
{
LB_HistData_Temperature_MAX = ((rx_frame.data.u8[2] / 2) - 40);
}
if ((rx_frame.data.u8[0] & 0x0F) == 3)
{
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;
#ifdef CAN_BYD
case 0x151: //Message originating from BYD HVS compatible inverter. Send CAN identifier!
if(rx_frame.data.u8[0] & 0x01)
{
send_intial_data();
}
break;
#endif
default:
break;
}
} }
else else
{ {
//printf("New extended frame"); //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);
} }
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);
}
}
if(LEAF_Battery_Type == ZE1_BATTERY)
{ //note different mux location in first frame
if ((rx_frame.data.u8[0] & 0x0F) == 1)
{
LB_HistData_Temperature_MAX = ((rx_frame.data.u8[2] / 2) - 40);
}
if ((rx_frame.data.u8[0] & 0x0F) == 3)
{
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;
default:
break;
} }
}
void send_can_leaf_battery()
{
static unsigned long currentMillis = millis();
// Send 100ms CAN Message // Send 100ms CAN Message
if (currentMillis - previousMillis100 >= interval100) if (currentMillis - previousMillis100 >= interval100)
{ {

View file

@ -35,7 +35,8 @@ extern uint16_t CANerror;
#define UPDATING 5 #define UPDATING 5
void update_values_leaf_battery(); void update_values_leaf_battery();
void handle_can_leaf_battery(); void receive_can_leaf_battery(CAN_frame_t rx_frame);
void send_can_leaf_battery();
uint16_t convert2unsignedint16(uint16_t signed_value); uint16_t convert2unsignedint16(uint16_t signed_value);
bool is_message_corrupt(CAN_frame_t rx_frame); bool is_message_corrupt(CAN_frame_t rx_frame);

View file

@ -120,54 +120,35 @@ void update_values_zoe_battery()
} }
} }
void handle_can_zoe_battery() void receive_can_zoe_battery(CAN_frame_t rx_frame)
{ {
CAN_frame_t rx_frame; switch (rx_frame.MsgID)
static unsigned long currentMillis = millis();
// Receive next CAN frame from queue
if (xQueueReceive(CAN_cfg.rx_queue, &rx_frame, 3 * portTICK_PERIOD_MS) == pdTRUE)
{ {
if (rx_frame.FIR.B.FF == CAN_frame_std) case 0x155: //BMS1
{ CANstillAlive = 12; //Indicate that we are still getting CAN messages from the BMS
//printf("New standard frame"); //LB_Max_Charge_Amps =
switch (rx_frame.MsgID) //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]));
case 0x155: //BMS1 break;
CANstillAlive = 12; //Indicate that we are still getting CAN messages from the BMS case 0x424: //BMS2
//LB_Max_Charge_Amps = LB_Charge_Power_Limit = (rx_frame.data.u8[2]);
//LB_Current = (((rx_frame.data.u8[1] & 0xF8) << 5) | (rx_frame.data.u8[2])); LB_Discharge_Power_Limit = (rx_frame.data.u8[3]);
LB_SOC = ((rx_frame.data.u8[4] << 8) | (rx_frame.data.u8[5])); LB_SOH = (rx_frame.data.u8[5]);
break; LB_MIN_TEMPERATURE = ((rx_frame.data.u8[4] & 0x7F) - 40);
case 0x424: //BMS2 LB_MAX_TEMPERATURE = ((rx_frame.data.u8[7] & 0x7F) - 40);
LB_Charge_Power_Limit = (rx_frame.data.u8[2]); break;
LB_Discharge_Power_Limit = (rx_frame.data.u8[3]); case 0x425: //BMS3 (could also be 445?)
LB_SOH = (rx_frame.data.u8[5]); //LB_kWh_Remaining =
LB_MIN_TEMPERATURE = ((rx_frame.data.u8[4] & 0x7F) - 40); //LB_Cell_Max_Voltage =
LB_MAX_TEMPERATURE = ((rx_frame.data.u8[7] & 0x7F) - 40); //LB_Cell_Min_Voltage =
break; break;
case 0x425: //BMS3 (could also be 445?) default:
//LB_kWh_Remaining = break;
//LB_Cell_Max_Voltage =
//LB_Cell_Min_Voltage =
break;
#ifdef CAN_BYD
case 0x151: //Message originating from BYD HVS compatible inverter. Send CAN identifier!
if(rx_frame.data.u8[0] & 0x01)
{
send_intial_data();
}
#endif
break;
default:
break;
}
}
else
{
//printf("New extended frame");
}
} }
}
void send_can_zoe_battery()
{
static unsigned long currentMillis = millis();
// Send 100ms CAN Message // Send 100ms CAN Message
if (currentMillis - previousMillis100 >= interval100) if (currentMillis - previousMillis100 >= interval100)
{ {

View file

@ -34,7 +34,8 @@ extern uint16_t CANerror;
#define UPDATING 5 #define UPDATING 5
void update_values_zoe_battery(); void update_values_zoe_battery();
void handle_can_zoe_battery(); void receive_can_zoe_battery(CAN_frame_t rx_frame);
void send_can_zoe_battery();
uint16_t convert2unsignedint16(uint16_t signed_value); uint16_t convert2unsignedint16(uint16_t signed_value);
#endif #endif

49
Software/SOLAX-CAN.cpp Normal file
View file

@ -0,0 +1,49 @@
#include "SOLAX-CAN.h"
#include "ESP32CAN.h"
#include "CAN_config.h"
/* Do not change code below unless you are sure what you are doing */
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 const int interval2s = 2000; // interval (ms) at which send CAN Messages
static const int interval10s = 10000; // interval (ms) at which send CAN Messages
static const int interval60s = 60000; // interval (ms) at which send CAN Messages
CAN_frame_t SOLAX_1872 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x1872,.data = {0x03, 0x16, 0x00, 0x66, 0x00, 0x33, 0x02, 0x09}};
void update_values_can_solax()
{ //This function maps all the values fetched from battery CAN to the correct CAN messages
}
void send_can_solax()
{
unsigned long currentMillis = millis();
// Send 2s CAN Message
if (currentMillis - previousMillis2s >= interval2s)
{
previousMillis2s = currentMillis;
}
// Send 10s CAN Message
if (currentMillis - previousMillis10s >= interval10s)
{
previousMillis10s = currentMillis;
//Serial.println("CAN 10s done");
}
//Send 60s message
if (currentMillis - previousMillis60s >= interval60s)
{
previousMillis60s = currentMillis;
//ESP32Can.CANWriteFrame(&BYD_190);
//Serial.println("CAN 60s done");
}
}
void receive_can_solax(CAN_frame_t rx_frame)
{
Serial.println("Inverter sending CAN message");
}

33
Software/SOLAX-CAN.h Normal file
View file

@ -0,0 +1,33 @@
#ifndef SOLAX_CAN_H
#define SOLAX_CAN_H
#include <Arduino.h>
#include "ESP32CAN.h"
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 min_volt_byd_can;
extern uint16_t max_volt_byd_can;
// 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_can_byd();
void send_can_solax();
void receive_can_solax(CAN_frame_t rx_frame);
#endif

View file

@ -1,11 +1,12 @@
/* Select battery used */ /* Select battery used */
//#define BATTERY_TYPE_LEAF // See NISSAN-LEAF-BATTERY.h for more LEAF battery settings #define BATTERY_TYPE_LEAF // See NISSAN-LEAF-BATTERY.h for more LEAF battery settings
//#define TESLA_MODEL_3_BATTERY // See TESLA-MODEL-3-BATTERY.h for more Tesla battery settings //#define TESLA_MODEL_3_BATTERY // See TESLA-MODEL-3-BATTERY.h for more Tesla battery settings
#define RENAULT_ZOE_BATTERY // See RENAULT-ZOE-BATTERY.h for more Zoe battery settings //#define RENAULT_ZOE_BATTERY // See RENAULT-ZOE-BATTERY.h for more Zoe battery settings
/* Select inverter communication protocol. See Wiki for which to use with your inverter: https://github.com/dalathegreat/BYD-Battery-Emulator-For-Gen24/wiki */ /* Select inverter communication protocol. See Wiki for which to use with your inverter: https://github.com/dalathegreat/BYD-Battery-Emulator-For-Gen24/wiki */
#define MODBUS_BYD //Enable this line to emulate a "BYD 11kWh HVM battery" over Modbus RTU //#define MODBUS_BYD //Enable this line to emulate a "BYD 11kWh HVM battery" over Modbus RTU
//#define CAN_BYD //Enable this line to emulate a "BYD Battery-Box Premium HVS" over CAN Bus //#define CAN_BYD //Enable this line to emulate a "BYD Battery-Box Premium HVS" over CAN Bus
#define SOLAX_CAN //Enable this line to emulate a "SolaX Triple Power LFP" over CAN bus
/* Do not change any code below this line unless you are sure what you are doing */ /* Do not change any code below this line unless you are sure what you are doing */
/* Only change battery specific settings and limits in their respective .h files */ /* Only change battery specific settings and limits in their respective .h files */
@ -13,14 +14,14 @@
#include <Arduino.h> #include <Arduino.h>
#include "HardwareSerial.h" #include "HardwareSerial.h"
#include "config.h" #include "config.h"
#include "logging.h" #include "Logging.h"
#include "mbServerFCs.h" #include "mbServerFCs.h"
#include "ModbusServerRTU.h" #include "ModbusServerRTU.h"
#include "ESP32CAN.h" #include "ESP32CAN.h"
#include "CAN_config.h" #include "CAN_config.h"
#include "Adafruit_NeoPixel.h" #include "Adafruit_NeoPixel.h"
#include "BATTERIES.h" #include "BATTERIES.h"
#include "BYD-CAN.h" #include "INVERTERS.h"
//CAN parameters //CAN parameters
#define MAX_CAN_FAILURES 5000 //Amount of malformed CAN messages to allow before raising a warning #define MAX_CAN_FAILURES 5000 //Amount of malformed CAN messages to allow before raising a warning
CAN_device_t CAN_cfg; // CAN Config CAN_device_t CAN_cfg; // CAN Config
@ -48,6 +49,8 @@ const uint16_t max_voltage = ABSOLUTE_MAX_VOLTAGE; //if higher charging is not p
const uint16_t min_voltage = ABSOLUTE_MIN_VOLTAGE; //if lower Gen24 disables battery const uint16_t min_voltage = ABSOLUTE_MIN_VOLTAGE; //if lower Gen24 disables battery
uint16_t min_volt_byd_can = min_voltage; uint16_t min_volt_byd_can = min_voltage;
uint16_t max_volt_byd_can = max_voltage; uint16_t max_volt_byd_can = max_voltage;
uint16_t min_volt_solax_can = min_voltage;
uint16_t max_volt_solax_can = max_voltage;
uint16_t battery_voltage = 3700; uint16_t battery_voltage = 3700;
uint16_t battery_current = 0; uint16_t battery_current = 0;
uint16_t SOC = 5000; //SOC 0-100.00% //Updates later on from CAN uint16_t SOC = 5000; //SOC 0-100.00% //Updates later on from CAN
@ -175,18 +178,52 @@ void loop()
} }
void handle_can() void handle_can()
{ //Depending on which parts are used, handle their respective CAN routines { //This section checks if we have a complete CAN message incoming
//Depending on which battery/inverter is selected, we forward this to their respective CAN routines
CAN_frame_t rx_frame;
if (xQueueReceive(CAN_cfg.rx_queue, &rx_frame, 3 * portTICK_PERIOD_MS) == pdTRUE)
{
if (rx_frame.FIR.B.FF == CAN_frame_std)
{
//printf("New standard frame");
#ifdef BATTERY_TYPE_LEAF
receive_can_leaf_battery(rx_frame);
#endif
#ifdef TESLA_MODEL_3_BATTERY
receive_can_tesla_model_3_battery(rx_frame);
#endif
#ifdef RENAULT_ZOE_BATTERY
receive_can_zoe_battery(rx_frame);
#endif
#ifdef CAN_BYD
receive_can_byd(rx_frame);
#endif
}
else
{
//printf("New extended frame");
#ifdef SOLAX_CAN
receive_can_solax(rx_frame);
#endif
}
}
//When we are done checking if a CAN message has arrived, we can focus on sending CAN messages
//Inverter sending
#ifdef CAN_BYD #ifdef CAN_BYD
handle_can_byd(); send_can_byd();
#endif #endif
#ifdef SOLAX_CAN
send_can_solax();
#endif
//Battery sending
#ifdef BATTERY_TYPE_LEAF #ifdef BATTERY_TYPE_LEAF
handle_can_leaf_battery(); send_can_leaf_battery();
#endif #endif
#ifdef TESLA_MODEL_3_BATTERY #ifdef TESLA_MODEL_3_BATTERY
handle_can_tesla_model_3_battery(); send_can_tesla_model_3_battery();
#endif #endif
#ifdef RENAULT_ZOE_BATTERY #ifdef RENAULT_ZOE_BATTERY
handle_can_zoe_battery(); send_can_zoe_battery();
#endif #endif
} }

View file

@ -177,127 +177,108 @@ void update_values_tesla_model_3_battery()
} }
} }
void handle_can_tesla_model_3_battery() void receive_can_tesla_model_3_battery(CAN_frame_t rx_frame)
{ {
CAN_frame_t rx_frame;
static unsigned long currentMillis = millis();
static int mux = 0; static int mux = 0;
static int temp = 0; static int temp = 0;
// Receive next CAN frame from queue switch (rx_frame.MsgID)
if (xQueueReceive(CAN_cfg.rx_queue, &rx_frame, 3 * portTICK_PERIOD_MS) == pdTRUE) {
{ case 0x352:
if (rx_frame.FIR.B.FF == CAN_frame_std) //SOC
{ nominal_full_pack_energy = (((rx_frame.data.u8[1] & 0x0F) << 8) | (rx_frame.data.u8[0])); //Example 752 (75.2kWh)
stillAliveCAN = 6; //We got CAN-messages flowing in! 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
//printf("New standard frame"); 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)
switch (rx_frame.MsgID) 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
case 0x352: energy_buffer = (((rx_frame.data.u8[7] & 0x7F) << 1) | ((rx_frame.data.u8[6] & 0x80) >> 7)) * 0.1; //Example 1 * 0.1 = 0
//SOC full_charge_complete = (rx_frame.data.u8[7] & 0x80);
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);
if(nominal_full_pack_energy > 0) if(nominal_full_pack_energy > 0)
{ //Avoid division by 0 { //Avoid division by 0
calculated_soc_float = ((float)expected_energy_remaining / nominal_full_pack_energy) * 10000; calculated_soc_float = ((float)expected_energy_remaining / nominal_full_pack_energy) * 10000;
calculated_soc = calculated_soc_float; calculated_soc = calculated_soc_float;
} }
else else
{ {
calculated_soc = 0; calculated_soc = 0;
} }
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
break;
case 0x332:
//min/max hist values
mux = rx_frame.data.u8[0];
mux = mux & 0x03;
if(mux == 1) //Cell voltages
{
//todo handle cell voltages
//not required by the Gen24, but nice stats located here!
}
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;
#ifdef CAN_BYD
case 0x151: //Message originating from BYD HVS compatible inverter. Send CAN identifier!
if(rx_frame.data.u8[0] & 0x01)
{
send_intial_data();
}
break; break;
#endif case 0x20A:
default: //Contactor state
break; packContNegativeState = (rx_frame.data.u8[0] & 0x07);
} packContPositiveState = (rx_frame.data.u8[0] & 0x38) >> 3;
} contactor = (rx_frame.data.u8[1] & 0x0F);
else packContactorSetState = (rx_frame.data.u8[1] & 0x0F);
{ packCtrsClosingAllowed = (rx_frame.data.u8[4] & 0x08) >> 3;
//printf("New extended frame"); 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
break;
case 0x332:
//min/max hist values
mux = rx_frame.data.u8[0];
mux = mux & 0x03;
if(mux == 1) //Cell voltages
{
//todo handle cell voltages
//not required by the Gen24, but nice stats located here!
}
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;
default:
break;
}
}
void send_can_tesla_model_3_battery()
{
static unsigned long currentMillis = millis();
// Send 100ms CAN Message // Send 100ms CAN Message
if (currentMillis - previousMillis100 >= interval100) if (currentMillis - previousMillis100 >= interval100)
{ {

View file

@ -1,6 +1,7 @@
#ifndef TESLA_MODEL_3_BATTERY_H #ifndef TESLA_MODEL_3_BATTERY_H
#define TESLA_MODEL_3_BATTERY_H #define TESLA_MODEL_3_BATTERY_H
#include <Arduino.h> #include <Arduino.h>
#include "ESP32CAN.h"
/* User definable settings for the Tesla Model 3 battery */ /* User definable settings for the Tesla Model 3 battery */
#define BATTERY_WH_MAX 60000 //Battery size in Wh (Maximum value Fronius accepts is 60000 [60kWh] you can use larger 65/75/90 batteries but do set value over 60000! #define BATTERY_WH_MAX 60000 //Battery size in Wh (Maximum value Fronius accepts is 60000 [60kWh] you can use larger 65/75/90 batteries but do set value over 60000!
@ -34,7 +35,8 @@ extern uint16_t CANerror;
#define UPDATING 5 #define UPDATING 5
void update_values_tesla_model_3_battery(); void update_values_tesla_model_3_battery();
void handle_can_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(uint16_t signed_value); uint16_t convert2unsignedint16(uint16_t signed_value);
#endif #endif