mirror of
https://github.com/dalathegreat/Battery-Emulator.git
synced 2025-10-05 10:49:42 +02:00
add inverter folder
This commit is contained in:
parent
a3583b6897
commit
fc0c71975d
16 changed files with 21 additions and 21 deletions
169
Software/src/inverter/BYD-CAN.cpp
Normal file
169
Software/src/inverter/BYD-CAN.cpp
Normal file
|
@ -0,0 +1,169 @@
|
|||
#include "BYD-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
|
||||
|
||||
//Startup messages
|
||||
const CAN_frame_t BYD_250 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_std,}},.MsgID = 0x250,.data = {0x03, 0x16, 0x00, 0x66, (uint8_t)((BATTERY_WH_MAX/100) >> 8), (uint8_t)(BATTERY_WH_MAX/100), 0x02, 0x09}}; //3.16 FW , Capacity kWh byte4&5 (example 24kWh = 240)
|
||||
const CAN_frame_t BYD_290 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_std,}},.MsgID = 0x290,.data = {0x06, 0x37, 0x10, 0xD9, 0x00, 0x00, 0x00, 0x00}};
|
||||
const CAN_frame_t BYD_2D0 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_std,}},.MsgID = 0x2D0,.data = {0x00, 0x42, 0x59, 0x44, 0x00, 0x00, 0x00, 0x00}}; //BYD
|
||||
const CAN_frame_t BYD_3D0_0 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_std,}},.MsgID = 0x3D0,.data = {0x00, 0x42, 0x61, 0x74, 0x74, 0x65, 0x72, 0x79}}; //Battery
|
||||
const CAN_frame_t BYD_3D0_1 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_std,}},.MsgID = 0x3D0,.data = {0x01, 0x2D, 0x42, 0x6F, 0x78, 0x20, 0x50, 0x72}}; //-Box Pr
|
||||
const CAN_frame_t BYD_3D0_2 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_std,}},.MsgID = 0x3D0,.data = {0x02, 0x65, 0x6D, 0x69, 0x75, 0x6D, 0x20, 0x48}}; //emium H
|
||||
const CAN_frame_t BYD_3D0_3 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_std,}},.MsgID = 0x3D0,.data = {0x03, 0x56, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00}}; //VS
|
||||
//Actual content messages
|
||||
CAN_frame_t BYD_110 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_std,}},.MsgID = 0x110,.data = {0x01, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
CAN_frame_t BYD_150 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_std,}},.MsgID = 0x150,.data = {0x00, 0x00, 0x00, 0x00, 0x10, 0x27, 0x00, 0x00}};
|
||||
CAN_frame_t BYD_190 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_std,}},.MsgID = 0x190,.data = {0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
CAN_frame_t BYD_1D0 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_std,}},.MsgID = 0x1D0,.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x08}};
|
||||
CAN_frame_t BYD_210 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_std,}},.MsgID = 0x210,.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
|
||||
static int discharge_current = 0;
|
||||
static int charge_current = 0;
|
||||
static int initialDataSent = 0;
|
||||
static int temperature_average = 0;
|
||||
|
||||
static int inverter_voltage = 0;
|
||||
static int inverter_SOC = 0;
|
||||
static long inverter_timestamp = 0;
|
||||
|
||||
void update_values_can_byd()
|
||||
{ //This function maps all the values fetched from battery CAN to the correct CAN messages
|
||||
//Calculate values
|
||||
charge_current = ((max_target_charge_power*10)/max_volt_byd_can); //Charge power in W , max volt in V+1decimal (P=UI, solve for I)
|
||||
//The above calculation results in (30 000*10)/3700=81A
|
||||
charge_current = (charge_current*10); //Value needs a decimal before getting sent to inverter (81.0A)
|
||||
|
||||
discharge_current = ((max_target_discharge_power*10)/max_volt_byd_can); //Charge power in W , max volt in V+1decimal (P=UI, solve for I)
|
||||
//The above calculation results in (30 000*10)/3700=81A
|
||||
discharge_current = (discharge_current*10); //Value needs a decimal before getting sent to inverter (81.0A)
|
||||
|
||||
temperature_average = ((temperature_max + temperature_min)/2);
|
||||
|
||||
|
||||
//Map values to CAN messages
|
||||
//Maxvoltage (eg 400.0V = 4000 , 16bits long)
|
||||
BYD_110.data.u8[0] = (max_volt_byd_can >> 8);
|
||||
BYD_110.data.u8[1] = (max_volt_byd_can & 0x00FF);
|
||||
//Minvoltage (eg 300.0V = 3000 , 16bits long)
|
||||
BYD_110.data.u8[2] = (min_volt_byd_can >> 8);
|
||||
BYD_110.data.u8[3] = (min_volt_byd_can & 0x00FF);
|
||||
//Maximum discharge power allowed (Unit: A+1)
|
||||
BYD_110.data.u8[4] = (discharge_current >> 8);
|
||||
BYD_110.data.u8[5] = (discharge_current & 0x00FF);
|
||||
//Maximum charge power allowed (Unit: A+1)
|
||||
BYD_110.data.u8[6] = (charge_current >> 8);
|
||||
BYD_110.data.u8[7] = (charge_current & 0x00FF);
|
||||
|
||||
//SOC (100.00%)
|
||||
BYD_150.data.u8[0] = (SOC >> 8);
|
||||
BYD_150.data.u8[1] = (SOC & 0x00FF);
|
||||
//StateOfHealth (100.00%)
|
||||
BYD_150.data.u8[2] = (StateOfHealth >> 8);
|
||||
BYD_150.data.u8[3] = (StateOfHealth & 0x00FF);
|
||||
//Maximum charge power allowed (Unit: A+1)
|
||||
BYD_150.data.u8[4] = (charge_current >> 8);
|
||||
BYD_150.data.u8[5] = (charge_current & 0x00FF);
|
||||
//Maximum discharge power allowed (Unit: A+1)
|
||||
BYD_150.data.u8[6] = (discharge_current >> 8);
|
||||
BYD_150.data.u8[7] = (discharge_current & 0x00FF);
|
||||
|
||||
//Voltage (ex 370.0)
|
||||
BYD_1D0.data.u8[0] = (battery_voltage >> 8);
|
||||
BYD_1D0.data.u8[1] = (battery_voltage & 0x00FF);
|
||||
//Current (ex 81.0A)
|
||||
BYD_1D0.data.u8[2] = (battery_current >> 8);
|
||||
BYD_1D0.data.u8[3] = (battery_current & 0x00FF);
|
||||
//Temperature average
|
||||
BYD_1D0.data.u8[4] = (temperature_average >> 8);
|
||||
BYD_1D0.data.u8[5] = (temperature_average & 0x00FF);
|
||||
|
||||
//Temperature max
|
||||
BYD_210.data.u8[0] = (temperature_max >> 8);
|
||||
BYD_210.data.u8[1] = (temperature_max & 0x00FF);
|
||||
//Temperature min
|
||||
BYD_210.data.u8[2] = (temperature_min >> 8);
|
||||
BYD_210.data.u8[3] = (temperature_min & 0x00FF);
|
||||
}
|
||||
|
||||
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;
|
||||
case 0x091:
|
||||
inverter_voltage = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]) * 0.1;
|
||||
break;
|
||||
case 0x0D1:
|
||||
inverter_SOC = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]) * 0.1;
|
||||
break;
|
||||
case 0x111:
|
||||
inverter_timestamp = ((rx_frame.data.u8[3] << 24) | (rx_frame.data.u8[2] << 16) | (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void send_can_byd()
|
||||
{
|
||||
unsigned long currentMillis = millis();
|
||||
// Send initial CAN data once on bootup
|
||||
if (!initialDataSent)
|
||||
{
|
||||
send_intial_data();
|
||||
initialDataSent = 1;
|
||||
}
|
||||
|
||||
if(bms_status != FAULT)
|
||||
{ // Send CAN messages towards inverter if battery is OK
|
||||
// Send 2s CAN Message
|
||||
if (currentMillis - previousMillis2s >= interval2s)
|
||||
{
|
||||
previousMillis2s = currentMillis;
|
||||
|
||||
ESP32Can.CANWriteFrame(&BYD_110);
|
||||
}
|
||||
// Send 10s CAN Message
|
||||
if (currentMillis - previousMillis10s >= interval10s)
|
||||
{
|
||||
previousMillis10s = currentMillis;
|
||||
|
||||
ESP32Can.CANWriteFrame(&BYD_150);
|
||||
ESP32Can.CANWriteFrame(&BYD_1D0);
|
||||
ESP32Can.CANWriteFrame(&BYD_210);
|
||||
//Serial.println("CAN 10s done");
|
||||
}
|
||||
//Send 60s message
|
||||
if (currentMillis - previousMillis60s >= interval60s)
|
||||
{
|
||||
previousMillis60s = currentMillis;
|
||||
|
||||
ESP32Can.CANWriteFrame(&BYD_190);
|
||||
//Serial.println("CAN 60s done");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void send_intial_data()
|
||||
{
|
||||
ESP32Can.CANWriteFrame(&BYD_250);
|
||||
ESP32Can.CANWriteFrame(&BYD_290);
|
||||
ESP32Can.CANWriteFrame(&BYD_2D0);
|
||||
ESP32Can.CANWriteFrame(&BYD_3D0_0);
|
||||
ESP32Can.CANWriteFrame(&BYD_3D0_1);
|
||||
ESP32Can.CANWriteFrame(&BYD_3D0_2);
|
||||
ESP32Can.CANWriteFrame(&BYD_3D0_3);
|
||||
}
|
36
Software/src/inverter/BYD-CAN.h
Normal file
36
Software/src/inverter/BYD-CAN.h
Normal file
|
@ -0,0 +1,36 @@
|
|||
#ifndef BYD_CAN_H
|
||||
#define BYD_CAN_H
|
||||
#include <Arduino.h>
|
||||
#include "../../ESP32CAN.h"
|
||||
#include "../../USER_SETTINGS.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_byd();
|
||||
void receive_can_byd(CAN_frame_t rx_frame);
|
||||
void send_intial_data();
|
||||
|
||||
#endif
|
32
Software/src/inverter/INVERTERS.h
Normal file
32
Software/src/inverter/INVERTERS.h
Normal file
|
@ -0,0 +1,32 @@
|
|||
#ifndef INVERTERS_H
|
||||
#define INVERTERS_H
|
||||
|
||||
#ifdef SOLAX_CAN
|
||||
#include "SOLAX-CAN.h"
|
||||
#endif
|
||||
|
||||
#ifdef CAN_BYD
|
||||
#include "BYD-CAN.h"
|
||||
#endif
|
||||
|
||||
#ifdef SMA_CAN
|
||||
#include "SMA-CAN.h"
|
||||
#endif
|
||||
|
||||
#ifdef SOFAR_CAN
|
||||
#include "SOFAR-CAN.h"
|
||||
#endif
|
||||
|
||||
#ifdef PYLON_CAN
|
||||
#include "PYLON-CAN.h"
|
||||
#endif
|
||||
|
||||
#ifdef MODBUS_BYD
|
||||
#include "MODBUS-BYD.h"
|
||||
#endif
|
||||
|
||||
#ifdef MODBUS_LUNA2000
|
||||
#include "MODBUS-LUNA2000.h"
|
||||
#endif
|
||||
|
||||
#endif
|
89
Software/src/inverter/MODBUS-BYD.cpp
Normal file
89
Software/src/inverter/MODBUS-BYD.cpp
Normal file
|
@ -0,0 +1,89 @@
|
|||
#include "MODBUS-BYD.h"
|
||||
|
||||
void update_modbus_registers_byd() {
|
||||
//Updata for ModbusRTU Server for BYD
|
||||
handle_update_data_modbusp201_byd();
|
||||
handle_update_data_modbusp301_byd();
|
||||
}
|
||||
|
||||
void handle_static_data_modbus_byd() {
|
||||
// Store the data into the array
|
||||
static uint16_t si_data[] = { 21321, 1 };
|
||||
static uint16_t byd_data[] = { 16985, 17408, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||
static uint16_t battery_data[] = { 16985, 17440, 16993, 29812, 25970, 31021, 17007, 30752, 20594, 25965, 26997, 27936, 18518, 0, 0, 0 };
|
||||
static uint16_t volt_data[] = { 13614, 12288, 0, 0, 0, 0, 0, 0, 13102, 12598, 0, 0, 0, 0, 0, 0 };
|
||||
static uint16_t serial_data[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||
static uint16_t static_data[] = { 1, 0 };
|
||||
static uint16_t* data_array_pointers[] = { si_data, byd_data, battery_data, volt_data, serial_data, static_data };
|
||||
static uint16_t data_sizes[] = { sizeof(si_data), sizeof(byd_data), sizeof(battery_data), sizeof(volt_data), sizeof(serial_data), sizeof(static_data) };
|
||||
static uint16_t i = 100;
|
||||
for (uint8_t arr_idx = 0; arr_idx < sizeof(data_array_pointers) / sizeof(uint16_t*); arr_idx++) {
|
||||
uint16_t data_size = data_sizes[arr_idx];
|
||||
memcpy(&mbPV[i], data_array_pointers[arr_idx], data_size);
|
||||
i += data_size / sizeof(uint16_t);
|
||||
}
|
||||
}
|
||||
|
||||
void handle_update_data_modbusp201_byd() {
|
||||
// Store the data into the array
|
||||
static uint16_t system_data[13];
|
||||
system_data[0] = 0; // Id.: p201 Value.: 0 Scaled value.: 0 Comment.: Always 0
|
||||
system_data[1] = 0; // Id.: p202 Value.: 0 Scaled value.: 0 Comment.: Always 0
|
||||
system_data[2] = (capacity_Wh_startup); // Id.: p203 Value.: 32000 Scaled value.: 32kWh Comment.: Capacity rated, maximum value is 60000 (60kWh)
|
||||
system_data[3] = (max_power); // Id.: p204 Value.: 32000 Scaled value.: 32kWh Comment.: Nominal capacity
|
||||
system_data[4] = (max_power); // Id.: p205 Value.: 14500 Scaled value.: 30,42kW Comment.: Max Charge/Discharge Power=10.24kW (lowest value of 204 and 205 will be enforced by Gen24)
|
||||
system_data[5] = (max_volt_modbus_byd); // Id.: p206 Value.: 3667 Scaled value.: 362,7VDC Comment.: Max Voltage, if higher charging is not possible (goes into forced discharge)
|
||||
system_data[6] = (min_volt_modbus_byd); // Id.: p207 Value.: 2776 Scaled value.: 277,6VDC Comment.: Min Voltage, if lower Gen24 disables battery
|
||||
system_data[7] = 53248; // Id.: p208 Value.: 53248 Scaled value.: 53248 Comment.: Always 53248 for this BYD, Peak Charge power?
|
||||
system_data[8] = 10; // Id.: p209 Value.: 10 Scaled value.: 10 Comment.: Always 10
|
||||
system_data[9] = 53248; // Id.: p210 Value.: 53248 Scaled value.: 53248 Comment.: Always 53248 for this BYD, Peak Discharge power?
|
||||
system_data[10] = 10; // Id.: p211 Value.: 10 Scaled value.: 10 Comment.: Always 10
|
||||
system_data[11] = 0; // Id.: p212 Value.: 0 Scaled value.: 0 Comment.: Always 0
|
||||
system_data[12] = 0; // Id.: p213 Value.: 0 Scaled value.: 0 Comment.: Always 0
|
||||
static uint16_t i = 200;
|
||||
memcpy(&mbPV[i], system_data, sizeof(system_data));
|
||||
}
|
||||
|
||||
void handle_update_data_modbusp301_byd() {
|
||||
// Store the data into the array
|
||||
static uint16_t battery_data[24];
|
||||
if (battery_current > 0) { //Positive value = Charging
|
||||
bms_char_dis_status = 2; //Charging
|
||||
} else if (battery_current < 0) { //Negative value = Discharging
|
||||
bms_char_dis_status = 1; //Discharging
|
||||
} else { //battery_current == 0
|
||||
bms_char_dis_status = 0; //idle
|
||||
}
|
||||
|
||||
if (bms_status == ACTIVE) {
|
||||
battery_data[8] = battery_voltage; // Id.: p309 Value.: 3161 Scaled value.: 316,1VDC Comment.: Batt Voltage outer (0 if status !=3, maybe a contactor closes when active): 173.4V
|
||||
} else {
|
||||
battery_data[8] = 0;
|
||||
}
|
||||
battery_data[0] = bms_status; // Id.: p301 Value.: 3 Scaled value.: 3 Comment.: status(*): ACTIVE - [0..5]<>[STANDBY,INACTIVE,DARKSTART,ACTIVE,FAULT,UPDATING]
|
||||
battery_data[1] = 0; // Id.: p302 Value.: 0 Scaled value.: 0 Comment.: always 0
|
||||
battery_data[2] = 128 + bms_char_dis_status; // Id.: p303 Value.: 130 Scaled value.: 130 Comment.: mode(*): normal
|
||||
battery_data[3] = SOC; // Id.: p304 Value.: 1700 Scaled value.: 50% Comment.: SOC: (50% would equal 5000)
|
||||
battery_data[4] = capacity_Wh; // Id.: p305 Value.: 32000 Scaled value.: 32kWh Comment.: tot cap:
|
||||
battery_data[5] = remaining_capacity_Wh; // Id.: p306 Value.: 13260 Scaled value.: 13,26kWh Comment.: remaining cap: 7.68kWh
|
||||
battery_data[6] = max_target_discharge_power; // Id.: p307 Value.: 25604 Scaled value.: 25,604kW Comment.: max/target discharge power: 0W (0W > restricts to no discharge)
|
||||
battery_data[7] = max_target_charge_power; // Id.: p308 Value.: 25604 Scaled value.: 25,604kW Comment.: max/target charge power: 4.3kW (during charge), both 307&308 can be set (>0) at the same time
|
||||
//Battery_data[8] set previously in function // Id.: p309 Value.: 3161 Scaled value.: 316,1VDC Comment.: Batt Voltage outer (0 if status !=3, maybe a contactor closes when active): 173.4V
|
||||
battery_data[9] = 2000; // Id.: p310 Value.: 64121 Scaled value.: 6412,1W Comment.: Current Power to API: if>32768... -(65535-61760)=3775W
|
||||
battery_data[10] = battery_voltage; // Id.: p311 Value.: 3161 Scaled value.: 316,1VDC Comment.: Batt Voltage inner: 173.2V (LEAF voltage is in whole volts, need to add a decimal)
|
||||
battery_data[11] = 2000; // Id.: p312 Value.: 64121 Scaled value.: 6412,1W Comment.: p310
|
||||
battery_data[12] = temperature_min; // Id.: p313 Value.: 75 Scaled value.: 7,5 Comment.: temp min: 7 degrees (if below 0....65535-t)
|
||||
battery_data[13] = temperature_max; // Id.: p314 Value.: 95 Scaled value.: 9,5 Comment.: temp max: 9 degrees (if below 0....65535-t)
|
||||
battery_data[14] = 0; // Id.: p315 Value.: 0 Scaled value.: 0 Comment.: always 0
|
||||
battery_data[15] = 0; // Id.: p316 Value.: 0 Scaled value.: 0 Comment.: always 0
|
||||
battery_data[16] = 16; // Id.: p317 Value.: 0 Scaled value.: 0 Comment.: counter charge hi
|
||||
battery_data[17] = 22741; // Id.: p318 Value.: 0 Scaled value.: 0 Comment.: counter charge lo....65536*101+9912 = 6629048 Wh?
|
||||
battery_data[18] = 0; // Id.: p319 Value.: 0 Scaled value.: 0 Comment.: always 0
|
||||
battery_data[19] = 0; // Id.: p320 Value.: 0 Scaled value.: 0 Comment.: always 0
|
||||
battery_data[20] = 13; // Id.: p321 Value.: 0 Scaled value.: 0 Comment.: counter discharge hi
|
||||
battery_data[21] = 52064; // Id.: p322 Value.: 0 Scaled value.: 0 Comment.: counter discharge lo....65536*92+7448 = 6036760 Wh?
|
||||
battery_data[22] = 230; // Id.: p323 Value.: 0 Scaled value.: 0 Comment.: device temperature (23 degrees)
|
||||
battery_data[23] = StateOfHealth; // Id.: p324 Value.: 9900 Scaled value.: 99% Comment.: SOH
|
||||
static uint16_t i = 300;
|
||||
memcpy(&mbPV[i], battery_data, sizeof(battery_data));
|
||||
}
|
38
Software/src/inverter/MODBUS-BYD.h
Normal file
38
Software/src/inverter/MODBUS-BYD.h
Normal file
|
@ -0,0 +1,38 @@
|
|||
#ifndef MODBUS_BYD_H
|
||||
#define MODBUS_BYD_H
|
||||
#include <Arduino.h>
|
||||
|
||||
#define MB_RTU_NUM_VALUES 30000
|
||||
// Definitions for BMS status
|
||||
#define STANDBY 0
|
||||
#define INACTIVE 1
|
||||
#define DARKSTART 2
|
||||
#define ACTIVE 3
|
||||
#define FAULT 4
|
||||
#define UPDATING 5
|
||||
|
||||
extern uint16_t mbPV[MB_RTU_NUM_VALUES];
|
||||
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 capacity_Wh_startup;
|
||||
extern uint16_t max_power;
|
||||
extern uint16_t max_volt_modbus_byd;
|
||||
extern uint16_t min_volt_modbus_byd;
|
||||
|
||||
void handle_static_data_modbus_byd();
|
||||
void handle_update_data_modbusp201_byd();
|
||||
void handle_update_data_modbusp301_byd();
|
||||
void update_modbus_registers_byd();
|
||||
#endif
|
58
Software/src/inverter/MODBUS-LUNA2000.cpp
Normal file
58
Software/src/inverter/MODBUS-LUNA2000.cpp
Normal file
|
@ -0,0 +1,58 @@
|
|||
#include "MODBUS-LUNA2000.h"
|
||||
|
||||
void update_modbus_registers_luna2000()
|
||||
{
|
||||
//Updata for ModbusRTU Server for Luna2000
|
||||
handle_update_data_modbus32051();
|
||||
handle_update_data_modbus39500();
|
||||
}
|
||||
|
||||
void handle_update_data_modbus32051() {
|
||||
// Store the data into the array
|
||||
static uint16_t system_data[9];
|
||||
system_data[0] = 1;
|
||||
system_data[1] = 534; //Goes between 534-498 depending on SOC
|
||||
system_data[2] = 110; //Goes between 110- -107 [NOTE, SIGNED VALUE]
|
||||
system_data[3] = 0; //Goes between 0 and -1 [NOTE, SIGNED VALUE]
|
||||
system_data[4] = 616; //Goes between 616- -520 [NOTE, SIGNED VALUE]
|
||||
system_data[5] = temperature_max; //Temperature max?
|
||||
system_data[6] = temperature_min; //Temperature min?
|
||||
system_data[7] = (SOC/100); //SOC 0-100%, no decimals
|
||||
system_data[8] = 98; //Always 98 in logs
|
||||
static uint16_t i = 2051;
|
||||
memcpy(&mbPV[i], system_data, sizeof(system_data));
|
||||
}
|
||||
|
||||
void handle_update_data_modbus39500() {
|
||||
// Store the data into the array
|
||||
static uint16_t system_data[26];
|
||||
system_data[0] = 0;
|
||||
system_data[1] = capacity_Wh_startup; //Capacity? 5000 with 5kWh battery
|
||||
system_data[2] = 0;
|
||||
system_data[3] = capacity_Wh_startup; //Capacity? 5000 with 5kWh battery
|
||||
system_data[4] = 0;
|
||||
system_data[5] = 2500; //???
|
||||
system_data[6] = 0;
|
||||
system_data[7] = 2500; //???
|
||||
system_data[8] = (SOC/100); //SOC 0-100%, no decimals
|
||||
system_data[9] = 1; //Running status, equiv to register 37762, 0 = Offline, 1 = Standby,2 = Running, 3 = Fault, 4 = sleep mode
|
||||
system_data[10] = battery_voltage; //Battery bus voltage (766.5V = 7665)
|
||||
system_data[11] = 9; //TODO, GOES LOWER WITH LOW SOC
|
||||
system_data[12] = 0;
|
||||
system_data[13] = 699; //TODO, GOES LOWER WITH LOW SOC
|
||||
system_data[14] = 1; //Always 1 in logs
|
||||
system_data[15] = 18; //Always 18 in logs
|
||||
system_data[16] = 8066; //TODO, GOES HIGHER WITH LOW SOC (max allowed charge W?)
|
||||
system_data[17] = 17;
|
||||
system_data[18] = 44027; //TODO, GOES LOWER WITH LOW SOC
|
||||
system_data[19] = 0;
|
||||
system_data[20] = 435; //Always 435 in logs
|
||||
system_data[21] = 0;
|
||||
system_data[22] = 0;
|
||||
system_data[23] = 0;
|
||||
system_data[24] = (SOC/10); //SOC 0-100.0%, 1x decimal
|
||||
system_data[25] = 0;
|
||||
system_data[26] = 1; //Always 1 in logs
|
||||
static uint16_t i = 9500;
|
||||
memcpy(&mbPV[i], system_data, sizeof(system_data));
|
||||
}
|
37
Software/src/inverter/MODBUS-LUNA2000.h
Normal file
37
Software/src/inverter/MODBUS-LUNA2000.h
Normal file
|
@ -0,0 +1,37 @@
|
|||
#ifndef MODBUS_LUNA2000_H
|
||||
#define MODBUS_LUNA2000_H
|
||||
#include <Arduino.h>
|
||||
|
||||
#define MB_RTU_NUM_VALUES 50000
|
||||
// Definitions for BMS status
|
||||
#define STANDBY 0
|
||||
#define INACTIVE 1
|
||||
#define DARKSTART 2
|
||||
#define ACTIVE 3
|
||||
#define FAULT 4
|
||||
#define UPDATING 5
|
||||
|
||||
extern uint16_t mbPV[MB_RTU_NUM_VALUES];
|
||||
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 capacity_Wh_startup;
|
||||
extern uint16_t max_power;
|
||||
extern uint16_t max_volt_modbus_byd;
|
||||
extern uint16_t min_volt_modbus_byd;
|
||||
|
||||
void update_modbus_registers_luna2000();
|
||||
void handle_update_data_modbus32051();
|
||||
void handle_update_data_modbus39500();
|
||||
#endif
|
154
Software/src/inverter/PYLON-CAN.cpp
Normal file
154
Software/src/inverter/PYLON-CAN.cpp
Normal file
|
@ -0,0 +1,154 @@
|
|||
#include "PYLON-CAN.h"
|
||||
#include "../../ESP32CAN.h"
|
||||
#include "../../CAN_config.h"
|
||||
|
||||
#define SEND_0 //If defined, the messages will have ID ending with 0 (useful for some inverters)
|
||||
//#define SEND_1 //If defined, the messages will have ID ending with 1 (useful for some inverters)
|
||||
|
||||
/* Do not change code below unless you are sure what you are doing */
|
||||
//Actual content messages
|
||||
CAN_frame_t PYLON_7310 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x7310,.data = {0x01, 0x00, 0x02, 0x01, 0x01, 0x02, 0x00, 0x00}};
|
||||
CAN_frame_t PYLON_7320 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x7320,.data = {0x4B, 0x00, 0x05, 0x0F, 0x2D, 0x00, 0x56, 0x00}};
|
||||
|
||||
CAN_frame_t PYLON_4210 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x4210,.data = {0xA5, 0x09, 0x30, 0x75, 0x9D, 0x04, 0x2E, 0x64}};
|
||||
CAN_frame_t PYLON_4220 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x4220,.data = {0x8C, 0x0A, 0xE9, 0x07, 0x4A, 0x79, 0x4A, 0x79}};
|
||||
CAN_frame_t PYLON_4230 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x4230,.data = {0xDF, 0x0C, 0xDA, 0x0C, 0x03, 0x00, 0x06, 0x00}};
|
||||
CAN_frame_t PYLON_4240 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x4240,.data = {0x7E, 0x04, 0x62, 0x04, 0x11, 0x00, 0x03, 0x00}};
|
||||
CAN_frame_t PYLON_4250 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x4250,.data = {0x03, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
CAN_frame_t PYLON_4260 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x4260,.data = {0xAC, 0xC7, 0x74, 0x27, 0x03, 0x00, 0x02, 0x00}};
|
||||
CAN_frame_t PYLON_4270 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x4270,.data = {0x7E, 0x04, 0x62, 0x04, 0x05, 0x00, 0x01, 0x00}};
|
||||
CAN_frame_t PYLON_4280 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x4280,.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
CAN_frame_t PYLON_4290 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x4290,.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
|
||||
CAN_frame_t PYLON_7311 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x7311,.data = {0x01, 0x00, 0x02, 0x01, 0x01, 0x02, 0x00, 0x00}};
|
||||
CAN_frame_t PYLON_7321 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x7321,.data = {0x4B, 0x00, 0x05, 0x0F, 0x2D, 0x00, 0x56, 0x00}};
|
||||
|
||||
CAN_frame_t PYLON_4211 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x4211,.data = {0xA5, 0x09, 0x30, 0x75, 0x9D, 0x04, 0x2E, 0x64}};
|
||||
CAN_frame_t PYLON_4221 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x4221,.data = {0x8C, 0x0A, 0xE9, 0x07, 0x4A, 0x79, 0x4A, 0x79}};
|
||||
CAN_frame_t PYLON_4231 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x4231,.data = {0xDF, 0x0C, 0xDA, 0x0C, 0x03, 0x00, 0x06, 0x00}};
|
||||
CAN_frame_t PYLON_4241 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x4241,.data = {0x7E, 0x04, 0x62, 0x04, 0x11, 0x00, 0x03, 0x00}};
|
||||
CAN_frame_t PYLON_4251 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x4251,.data = {0x03, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
CAN_frame_t PYLON_4261 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x4261,.data = {0xAC, 0xC7, 0x74, 0x27, 0x03, 0x00, 0x02, 0x00}};
|
||||
CAN_frame_t PYLON_4271 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x4271,.data = {0x7E, 0x04, 0x62, 0x04, 0x05, 0x00, 0x01, 0x00}};
|
||||
CAN_frame_t PYLON_4281 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x4281,.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
CAN_frame_t PYLON_4291 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x4291,.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
|
||||
|
||||
void update_values_can_pylon()
|
||||
{ //This function maps all the values fetched from battery CAN to the correct CAN messages
|
||||
//There are more mappings that could be added, but this should be enough to use as a starting point
|
||||
// Note we map both 0 and 1 messages
|
||||
|
||||
//Charge / Discharge allowed
|
||||
PYLON_4280.data.u8[0] = 0;
|
||||
PYLON_4280.data.u8[1] = 0;
|
||||
PYLON_4280.data.u8[2] = 0;
|
||||
PYLON_4280.data.u8[3] = 0;
|
||||
PYLON_4281.data.u8[0] = 0;
|
||||
PYLON_4281.data.u8[1] = 0;
|
||||
PYLON_4281.data.u8[2] = 0;
|
||||
PYLON_4281.data.u8[3] = 0;
|
||||
|
||||
//Voltage (370.0)
|
||||
PYLON_4210.data.u8[0] = (battery_voltage >> 8);
|
||||
PYLON_4210.data.u8[1] = (battery_voltage & 0x00FF);
|
||||
PYLON_4211.data.u8[0] = (battery_voltage >> 8);
|
||||
PYLON_4211.data.u8[1] = (battery_voltage & 0x00FF);
|
||||
|
||||
//Current (TODO, SIGNED? Or looks like it could be just offset, in that case the below line wont work)
|
||||
PYLON_4210.data.u8[2] = (battery_current >> 8);
|
||||
PYLON_4210.data.u8[3] = (battery_current & 0x00FF);
|
||||
PYLON_4211.data.u8[2] = (battery_current >> 8);
|
||||
PYLON_4211.data.u8[3] = (battery_current & 0x00FF);
|
||||
|
||||
//SOC (100.00%)
|
||||
PYLON_4210.data.u8[6] = (SOC*0.01); //Remove decimals
|
||||
PYLON_4211.data.u8[6] = (SOC*0.01); //Remove decimals
|
||||
|
||||
//StateOfHealth (100.00%)
|
||||
PYLON_4210.data.u8[7] = (StateOfHealth*0.01);
|
||||
PYLON_4211.data.u8[7] = (StateOfHealth*0.01);
|
||||
|
||||
//Minvoltage (eg 300.0V = 3000 , 16bits long) Charge Cutoff Voltage
|
||||
PYLON_4220.data.u8[0] = (min_volt_pylon_can >> 8);
|
||||
PYLON_4220.data.u8[1] = (min_volt_pylon_can & 0x00FF);
|
||||
PYLON_4221.data.u8[0] = (min_volt_pylon_can >> 8);
|
||||
PYLON_4221.data.u8[1] = (min_volt_pylon_can & 0x00FF);
|
||||
|
||||
//Maxvoltage (eg 400.0V = 4000 , 16bits long) Discharge Cutoff Voltage
|
||||
PYLON_4220.data.u8[2] = (max_volt_pylon_can >> 8);
|
||||
PYLON_4220.data.u8[3] = (max_volt_pylon_can & 0x00FF);
|
||||
PYLON_4221.data.u8[2] = (max_volt_pylon_can >> 8);
|
||||
PYLON_4221.data.u8[3] = (max_volt_pylon_can & 0x00FF);
|
||||
|
||||
//In case we run into any errors/faults, we can set charge / discharge forbidden
|
||||
if(bms_status == FAULT)
|
||||
{
|
||||
PYLON_4280.data.u8[0] = 0xAA;
|
||||
PYLON_4280.data.u8[1] = 0xAA;
|
||||
PYLON_4280.data.u8[2] = 0xAA;
|
||||
PYLON_4280.data.u8[3] = 0xAA;
|
||||
PYLON_4281.data.u8[0] = 0xAA;
|
||||
PYLON_4281.data.u8[1] = 0xAA;
|
||||
PYLON_4281.data.u8[2] = 0xAA;
|
||||
PYLON_4281.data.u8[3] = 0xAA;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void receive_can_pylon(CAN_frame_t rx_frame)
|
||||
{
|
||||
switch (rx_frame.MsgID)
|
||||
{
|
||||
case 0x4200: //Message originating from inverter. Depending on which data is required, act accordingly
|
||||
if(rx_frame.data.u8[0] == 0x02)
|
||||
{
|
||||
send_setup_info();
|
||||
}
|
||||
if(rx_frame.data.u8[0] == 0x00)
|
||||
{
|
||||
send_system_data();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void send_setup_info()
|
||||
{ //Ensemble information
|
||||
#ifdef SEND_0
|
||||
ESP32Can.CANWriteFrame(&PYLON_7310);
|
||||
ESP32Can.CANWriteFrame(&PYLON_7320);
|
||||
#endif
|
||||
#ifdef SEND_1
|
||||
ESP32Can.CANWriteFrame(&PYLON_7311);
|
||||
ESP32Can.CANWriteFrame(&PYLON_7321);
|
||||
#endif
|
||||
}
|
||||
|
||||
void send_system_data()
|
||||
{ //System equipment information
|
||||
#ifdef SEND_0
|
||||
ESP32Can.CANWriteFrame(&PYLON_4210);
|
||||
ESP32Can.CANWriteFrame(&PYLON_4220);
|
||||
ESP32Can.CANWriteFrame(&PYLON_4230);
|
||||
ESP32Can.CANWriteFrame(&PYLON_4240);
|
||||
ESP32Can.CANWriteFrame(&PYLON_4250);
|
||||
ESP32Can.CANWriteFrame(&PYLON_4260);
|
||||
ESP32Can.CANWriteFrame(&PYLON_4270);
|
||||
ESP32Can.CANWriteFrame(&PYLON_4280);
|
||||
ESP32Can.CANWriteFrame(&PYLON_4290);
|
||||
#endif
|
||||
#ifdef SEND_1
|
||||
ESP32Can.CANWriteFrame(&PYLON_4211);
|
||||
ESP32Can.CANWriteFrame(&PYLON_4221);
|
||||
ESP32Can.CANWriteFrame(&PYLON_4231);
|
||||
ESP32Can.CANWriteFrame(&PYLON_4241);
|
||||
ESP32Can.CANWriteFrame(&PYLON_4251);
|
||||
ESP32Can.CANWriteFrame(&PYLON_4261);
|
||||
ESP32Can.CANWriteFrame(&PYLON_4271);
|
||||
ESP32Can.CANWriteFrame(&PYLON_4281);
|
||||
ESP32Can.CANWriteFrame(&PYLON_4291);
|
||||
#endif
|
||||
}
|
36
Software/src/inverter/PYLON-CAN.h
Normal file
36
Software/src/inverter/PYLON-CAN.h
Normal file
|
@ -0,0 +1,36 @@
|
|||
#ifndef PYLON_CAN_H
|
||||
#define PYLON_CAN_H
|
||||
#include <Arduino.h>
|
||||
#include "../../ESP32CAN.h"
|
||||
#include "../../USER_SETTINGS.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_pylon_can;
|
||||
extern uint16_t max_volt_pylon_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_pylon();
|
||||
void receive_can_pylon(CAN_frame_t rx_frame);
|
||||
void send_system_data();
|
||||
void send_setup_info();
|
||||
|
||||
#endif
|
199
Software/src/inverter/SMA-CAN.cpp
Normal file
199
Software/src/inverter/SMA-CAN.cpp
Normal file
|
@ -0,0 +1,199 @@
|
|||
#include "SMA-CAN.h"
|
||||
#include "../../ESP32CAN.h"
|
||||
#include "../../CAN_config.h"
|
||||
|
||||
//TODO, change CAN sending routine once confirmed that 500ms interval is OK for this battery type
|
||||
|
||||
/* Do not change code below unless you are sure what you are doing */
|
||||
static unsigned long previousMillis1s = 0; // will store last time a Xs CAN Message was send
|
||||
static unsigned long previousMillis2s = 0; // will store last time a Xs CAN Message was send
|
||||
static unsigned long previousMillis3s = 0; // will store last time a Xs CAN Message was send
|
||||
static unsigned long previousMillis4s = 0; // will store last time a Xs CAN Message was send
|
||||
static unsigned long previousMillis5s = 0; // will store last time a Xs CAN Message was send
|
||||
static unsigned long previousMillis6s = 0; // will store last time a Xs CAN Message was send
|
||||
static unsigned long previousMillis7s = 0; // will store last time a Xs CAN Message was send
|
||||
static unsigned long previousMillis8s = 0; // will store last time a Xs CAN Message was send
|
||||
static unsigned long previousMillis9s = 0; // will store last time a Xs CAN Message was send
|
||||
static unsigned long previousMillis10s = 0; // will store last time a Xs CAN Message was send
|
||||
static unsigned long previousMillis11s = 0; // will store last time a Xs CAN Message was send
|
||||
static unsigned long previousMillis12s = 0; // will store last time a Xs CAN Message was send
|
||||
static const int interval1s = 100; // interval (ms) at which send CAN Messages
|
||||
static const int interval2s = 102; // interval (ms) at which send CAN Messages
|
||||
static const int interval3s = 104; // interval (ms) at which send CAN Messages
|
||||
static const int interval4s = 106; // interval (ms) at which send CAN Messages
|
||||
static const int interval5s = 108; // interval (ms) at which send CAN Messages
|
||||
static const int interval6s = 110; // interval (ms) at which send CAN Messages
|
||||
static const int interval7s = 112; // interval (ms) at which send CAN Messages
|
||||
static const int interval8s = 114; // interval (ms) at which send CAN Messages
|
||||
static const int interval9s = 116; // interval (ms) at which send CAN Messages
|
||||
static const int interval10s = 118; // interval (ms) at which send CAN Messages
|
||||
static const int interval11s = 120; // interval (ms) at which send CAN Messages
|
||||
static const int interval12s = 122; // interval (ms) at which send CAN Messages
|
||||
|
||||
//Actual content messages
|
||||
static const CAN_frame_t SMA_558 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_std,}},.MsgID = 0x558,.data = {0x03, 0x12, 0x00, 0x04, 0x00, 0x59, 0x07, 0x07}}; //7x BYD modules, Vendor ID 7 BYD
|
||||
static const CAN_frame_t SMA_598 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_std,}},.MsgID = 0x598,.data = {0x00, 0x00, 0x12, 0x34, 0x5A, 0xDE, 0x07, 0x4F}}; //B0-4 Serial, rest unknown
|
||||
static const CAN_frame_t SMA_5D8 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_std,}},.MsgID = 0x5D8,.data = {0x00, 0x42, 0x59, 0x44, 0x00, 0x00, 0x00, 0x00}}; //B Y D
|
||||
static const CAN_frame_t SMA_618_1 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_std,}},.MsgID = 0x618,.data = {0x00, 0x42, 0x61, 0x74, 0x74, 0x65, 0x72, 0x79}}; //0 B A T T E R Y
|
||||
static const CAN_frame_t SMA_618_2 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_std,}},.MsgID = 0x618,.data = {0x01, 0x2D, 0x42, 0x6F, 0x78, 0x20, 0x48, 0x39}}; //1 - B O X H
|
||||
static const CAN_frame_t SMA_618_3 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_std,}},.MsgID = 0x618,.data = {0x02, 0x2E, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00}}; //2 - 0
|
||||
CAN_frame_t SMA_358 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_std,}},.MsgID = 0x358,.data = {0x0F, 0x6C, 0x06, 0x20, 0x00, 0x00, 0x00, 0x00}};
|
||||
CAN_frame_t SMA_3D8 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_std,}},.MsgID = 0x3D8,.data = {0x04, 0x10, 0x27, 0x10, 0x00, 0x18, 0xF9, 0x00}};
|
||||
CAN_frame_t SMA_458 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_std,}},.MsgID = 0x458,.data = {0x00, 0x00, 0x06, 0x75, 0x00, 0x00, 0x05, 0xD6}};
|
||||
CAN_frame_t SMA_518 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_std,}},.MsgID = 0x518,.data = {0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF}};
|
||||
CAN_frame_t SMA_4D8 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_std,}},.MsgID = 0x4D8,.data = {0x09, 0xFD, 0x00, 0x00, 0x00, 0xA8, 0x02, 0x08}};
|
||||
CAN_frame_t SMA_158 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_std,}},.MsgID = 0x158,.data = {0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x6A, 0xAA, 0xAA}};
|
||||
|
||||
static int discharge_current = 0;
|
||||
static int charge_current = 0;
|
||||
static int temperature_average = 0;
|
||||
static int ampere_hours_remaining = 0;
|
||||
|
||||
void update_values_can_sma()
|
||||
{ //This function maps all the values fetched from battery CAN to the correct CAN messages
|
||||
//Calculate values
|
||||
charge_current = ((max_target_charge_power*10)/max_volt_sma_can); //Charge power in W , max volt in V+1decimal (P=UI, solve for I)
|
||||
//The above calculation results in (30 000*10)/3700=81A
|
||||
charge_current = (charge_current*10); //Value needs a decimal before getting sent to inverter (81.0A)
|
||||
|
||||
discharge_current = ((max_target_discharge_power*10)/max_volt_sma_can); //Charge power in W , max volt in V+1decimal (P=UI, solve for I)
|
||||
//The above calculation results in (30 000*10)/3700=81A
|
||||
discharge_current = (discharge_current*10); //Value needs a decimal before getting sent to inverter (81.0A)
|
||||
|
||||
temperature_average = ((temperature_max + temperature_min)/2);
|
||||
|
||||
ampere_hours_remaining = ((remaining_capacity_Wh/battery_voltage)*100); //(WH[10000] * V+1[3600])*100 = 270 (27.0Ah)
|
||||
|
||||
//Map values to CAN messages
|
||||
//Maxvoltage (eg 400.0V = 4000 , 16bits long)
|
||||
SMA_358.data.u8[0] = (max_volt_sma_can >> 8);
|
||||
SMA_358.data.u8[1] = (max_volt_sma_can & 0x00FF);
|
||||
//Minvoltage (eg 300.0V = 3000 , 16bits long)
|
||||
SMA_358.data.u8[2] = (min_volt_sma_can >> 8); //Minvoltage behaves strange on SMA, cuts out at 56% of the set value?
|
||||
SMA_358.data.u8[3] = (min_volt_sma_can & 0x00FF);
|
||||
//Discharge limited current, 500 = 50A, (0.1, A)
|
||||
SMA_358.data.u8[4] = (discharge_current >> 8);
|
||||
SMA_358.data.u8[5] = (discharge_current & 0x00FF);
|
||||
//Charge limited current, 125 =12.5A (0.1, A)
|
||||
SMA_358.data.u8[6] = (charge_current >> 8);
|
||||
SMA_358.data.u8[7] = (charge_current & 0x00FF);
|
||||
|
||||
//SOC (100.00%)
|
||||
SMA_3D8.data.u8[0] = (SOC >> 8);
|
||||
SMA_3D8.data.u8[1] = (SOC & 0x00FF);
|
||||
//StateOfHealth (100.00%)
|
||||
SMA_3D8.data.u8[2] = (StateOfHealth >> 8);
|
||||
SMA_3D8.data.u8[3] = (StateOfHealth & 0x00FF);
|
||||
//State of charge (AH, 0.1)
|
||||
SMA_3D8.data.u8[4] = (ampere_hours_remaining >> 8);
|
||||
SMA_3D8.data.u8[5] = (ampere_hours_remaining & 0x00FF);
|
||||
|
||||
//Voltage (370.0)
|
||||
SMA_4D8.data.u8[0] = (battery_voltage >> 8);
|
||||
SMA_4D8.data.u8[1] = (battery_voltage & 0x00FF);
|
||||
//Current (TODO, signed OK?)
|
||||
SMA_4D8.data.u8[2] = (battery_current >> 8);
|
||||
SMA_4D8.data.u8[3] = (battery_current & 0x00FF);
|
||||
//Temperature average
|
||||
SMA_4D8.data.u8[4] = (temperature_average >> 8);
|
||||
SMA_4D8.data.u8[5] = (temperature_average & 0x00FF);
|
||||
|
||||
//Error bits
|
||||
//SMA_158.data.u8[0] = //bit12 Fault high temperature, bit34Battery cellundervoltage, bit56 Battery cell overvoltage, bit78 batterysystemdefect
|
||||
//TODO, add all error bits
|
||||
}
|
||||
|
||||
void receive_can_sma(CAN_frame_t rx_frame)
|
||||
{
|
||||
switch (rx_frame.MsgID)
|
||||
{
|
||||
case 0x660: //Message originating from SMA inverter
|
||||
break;
|
||||
case 0x5E0: //Message originating from SMA inverter
|
||||
break;
|
||||
case 0x560: //Message originating from SMA inverter
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void send_can_sma()
|
||||
{
|
||||
unsigned long currentMillis = millis();
|
||||
|
||||
// Send CAN Message every X ms, 1000 for testing
|
||||
if (currentMillis - previousMillis1s >= interval1s)
|
||||
{
|
||||
previousMillis1s = currentMillis;
|
||||
|
||||
ESP32Can.CANWriteFrame(&SMA_558);
|
||||
}
|
||||
if (currentMillis - previousMillis2s >= interval2s)
|
||||
{
|
||||
previousMillis2s = currentMillis;
|
||||
|
||||
ESP32Can.CANWriteFrame(&SMA_598);
|
||||
}
|
||||
if (currentMillis - previousMillis3s >= interval3s)
|
||||
{
|
||||
previousMillis3s = currentMillis;
|
||||
|
||||
ESP32Can.CANWriteFrame(&SMA_5D8);
|
||||
}
|
||||
if (currentMillis - previousMillis4s >= interval4s)
|
||||
{
|
||||
previousMillis4s = currentMillis;
|
||||
|
||||
ESP32Can.CANWriteFrame(&SMA_618_1);
|
||||
}
|
||||
if (currentMillis - previousMillis5s >= interval5s)
|
||||
{
|
||||
previousMillis5s = currentMillis;
|
||||
|
||||
ESP32Can.CANWriteFrame(&SMA_618_2);
|
||||
}
|
||||
if (currentMillis - previousMillis6s >= interval6s)
|
||||
{
|
||||
previousMillis6s = currentMillis;
|
||||
|
||||
ESP32Can.CANWriteFrame(&SMA_618_3);
|
||||
}
|
||||
if (currentMillis - previousMillis7s >= interval7s)
|
||||
{
|
||||
previousMillis7s = currentMillis;
|
||||
|
||||
ESP32Can.CANWriteFrame(&SMA_358);
|
||||
}
|
||||
if (currentMillis - previousMillis8s >= interval8s)
|
||||
{
|
||||
previousMillis8s = currentMillis;
|
||||
|
||||
ESP32Can.CANWriteFrame(&SMA_3D8);
|
||||
}
|
||||
if (currentMillis - previousMillis9s >= interval9s)
|
||||
{
|
||||
previousMillis9s = currentMillis;
|
||||
|
||||
ESP32Can.CANWriteFrame(&SMA_458);
|
||||
}
|
||||
if (currentMillis - previousMillis10s >= interval10s)
|
||||
{
|
||||
previousMillis10s = currentMillis;
|
||||
|
||||
ESP32Can.CANWriteFrame(&SMA_518);
|
||||
}
|
||||
if (currentMillis - previousMillis11s >= interval11s)
|
||||
{
|
||||
previousMillis11s = currentMillis;
|
||||
|
||||
ESP32Can.CANWriteFrame(&SMA_4D8);
|
||||
}
|
||||
if (currentMillis - previousMillis12s >= interval12s)
|
||||
{
|
||||
previousMillis12s = currentMillis;
|
||||
|
||||
ESP32Can.CANWriteFrame(&SMA_158);
|
||||
}
|
||||
|
||||
}
|
37
Software/src/inverter/SMA-CAN.h
Normal file
37
Software/src/inverter/SMA-CAN.h
Normal file
|
@ -0,0 +1,37 @@
|
|||
#ifndef SMA_CAN_H
|
||||
#define SMA_CAN_H
|
||||
#include <Arduino.h>
|
||||
#include "../../ESP32CAN.h"
|
||||
#include "../../USER_SETTINGS.h"
|
||||
|
||||
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 uint16_t min_volt_sma_can;
|
||||
extern uint16_t max_volt_sma_can;
|
||||
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
|
||||
|
||||
void update_values_can_sma();
|
||||
void send_can_sma();
|
||||
void receive_can_sma(CAN_frame_t rx_frame);
|
||||
|
||||
#endif
|
117
Software/src/inverter/SOFAR-CAN.cpp
Normal file
117
Software/src/inverter/SOFAR-CAN.cpp
Normal file
|
@ -0,0 +1,117 @@
|
|||
#include "SOFAR-CAN.h"
|
||||
#include "../../ESP32CAN.h"
|
||||
#include "../../CAN_config.h"
|
||||
|
||||
/* This implementation of the SOFAR can protocol is halfway done. What's missing is implementing the inverter replies, all the CAN messages are listed, but the can sending is missing. */
|
||||
|
||||
/* 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
|
||||
|
||||
//Actual content messages
|
||||
//Note that these are technically extended frames. If more batteries are put in parallel,the first battery sends 0x351 the next battery sends 0x1351 etc. 16 batteries in parallel supported
|
||||
CAN_frame_t SOFAR_351 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x351,.data = {0xC6, 0x08, 0xFA, 0x00, 0xFA, 0x00, 0x80, 0x07}};
|
||||
CAN_frame_t SOFAR_355 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x355,.data = {0x31, 0x00, 0x64, 0x00, 0xFF, 0xFF, 0xF6, 0x00}};
|
||||
CAN_frame_t SOFAR_356 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x356,.data = {0x36, 0x08, 0x10, 0x00, 0xD0, 0x00, 0x01, 0x00}};
|
||||
CAN_frame_t SOFAR_30F = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x30F,.data = {0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
CAN_frame_t SOFAR_359 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x359,.data = {0x00, 0x00, 0x00, 0x00, 0x04, 0x10, 0x27, 0x10}};
|
||||
CAN_frame_t SOFAR_35E = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x35E,.data = {0x41, 0x4D, 0x41, 0x53, 0x53, 0x00, 0x00, 0x00}};
|
||||
CAN_frame_t SOFAR_35F = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x35F,.data = {0x00, 0x00, 0x24, 0x4E, 0x32, 0x00, 0x00, 0x00}};
|
||||
CAN_frame_t SOFAR_35A = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x35A,.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
|
||||
CAN_frame_t SOFAR_670 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x670,.data = {0x00, 0x8A, 0x33, 0x11, 0x59, 0x1A, 0x00, 0x00}};
|
||||
CAN_frame_t SOFAR_671 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x671,.data = {0x00, 0x42, 0x48, 0x55, 0x35, 0x31, 0x32, 0x30}};
|
||||
CAN_frame_t SOFAR_672 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x672,.data = {0x00, 0x32, 0x35, 0x45, 0x50, 0x43, 0x32, 0x31}};
|
||||
CAN_frame_t SOFAR_673 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x673,.data = {0x00, 0x34, 0x32, 0x36, 0x31, 0x36, 0x32, 0x00}};
|
||||
CAN_frame_t SOFAR_680 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x680,.data = {0x00, 0xB7, 0x0C, 0xB3, 0x0C, 0xB4, 0x0C, 0x00}};
|
||||
CAN_frame_t SOFAR_681 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x681,.data = {0x00, 0xB6, 0x0C, 0xB3, 0x0C, 0xB4, 0x0C, 0x00}};
|
||||
CAN_frame_t SOFAR_682 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x682,.data = {0x00, 0xB6, 0x0C, 0xB3, 0x0C, 0xB4, 0x0C, 0x00}};
|
||||
CAN_frame_t SOFAR_683 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x683,.data = {0x00, 0xB6, 0x0C, 0xB3, 0x0C, 0xB4, 0x0C, 0x00}};
|
||||
CAN_frame_t SOFAR_684 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x684,.data = {0x00, 0xB6, 0x0C, 0xB3, 0x0C, 0xB4, 0x0C, 0x00}};
|
||||
CAN_frame_t SOFAR_685 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x685,.data = {0x00, 0xB3, 0x0C, 0xBB, 0x0C, 0xB3, 0x0C, 0x00}};
|
||||
CAN_frame_t SOFAR_690 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x690,.data = {0x00, 0xD7, 0x00, 0xD4, 0x00, 0x00, 0x00, 0x00}};
|
||||
CAN_frame_t SOFAR_691 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x691,.data = {0x00, 0xD4, 0x00, 0xD1, 0x00, 0x00, 0x00, 0x00}};
|
||||
CAN_frame_t SOFAR_6A0 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x6A0,.data = {0x00, 0xFA, 0x00, 0xDD, 0x00, 0x00, 0x00, 0x00}};
|
||||
CAN_frame_t SOFAR_6B0 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x6B0,.data = {0x00, 0xF6, 0x00, 0x06, 0x02, 0x01, 0x00, 0x00}};
|
||||
CAN_frame_t SOFAR_6C0 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x6C0,.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
CAN_frame_t SOFAR_770 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x770,.data = {0x00, 0x56, 0x0B, 0xF0, 0x58, 0x00, 0x00, 0x00}};
|
||||
CAN_frame_t SOFAR_771 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x771,.data = {0x00, 0x42, 0x48, 0x55, 0x35, 0x31, 0x32, 0x30}};
|
||||
CAN_frame_t SOFAR_772 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x772,.data = {0x00, 0x32, 0x35, 0x45, 0x50, 0x43, 0x32, 0x31}};
|
||||
CAN_frame_t SOFAR_773 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x773,.data = {0x00, 0x34, 0x32, 0x36, 0x31, 0x36, 0x32, 0x00}};
|
||||
CAN_frame_t SOFAR_780 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x780,.data = {0x00, 0xEB, 0x0C, 0xED, 0x0C, 0xED, 0x0C, 0x00}};
|
||||
CAN_frame_t SOFAR_781 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x781,.data = {0x00, 0xEB, 0x0C, 0xED, 0x0C, 0xED, 0x0C, 0x00}};
|
||||
CAN_frame_t SOFAR_782 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x782,.data = {0x00, 0xEB, 0x0C, 0xED, 0x0C, 0xED, 0x0C, 0x00}};
|
||||
CAN_frame_t SOFAR_783 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x783,.data = {0x00, 0xEB, 0x0C, 0xED, 0x0C, 0xED, 0x0C, 0x00}};
|
||||
CAN_frame_t SOFAR_784 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x784,.data = {0x00, 0xEB, 0x0C, 0xED, 0x0C, 0xED, 0x0C, 0x00}};
|
||||
CAN_frame_t SOFAR_785 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x785,.data = {0x00, 0xEB, 0x0C, 0xED, 0x0C, 0xED, 0x0C, 0x00}};
|
||||
CAN_frame_t SOFAR_790 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x790,.data = {0x00, 0xCD, 0x00, 0xCF, 0x00, 0x00, 0x00, 0x00}};
|
||||
CAN_frame_t SOFAR_791 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x791,.data = {0x00, 0xCD, 0x00, 0xCF, 0x00, 0x00, 0x00, 0x00}};
|
||||
CAN_frame_t SOFAR_7A0 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x7A0,.data = {0x00, 0xFA, 0x00, 0xE1, 0x00, 0x00, 0x00, 0x00}};
|
||||
CAN_frame_t SOFAR_7B0 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x7B0,.data = {0x00, 0xF9, 0x00, 0x06, 0x02, 0xE9, 0x5D, 0x00}};
|
||||
CAN_frame_t SOFAR_7C0 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x7C0,.data = {0x00, 0x00, 0x00, 0x04, 0x00, 0x04, 0x80, 0x00}};
|
||||
|
||||
void update_values_can_sofar()
|
||||
{ //This function maps all the values fetched from battery CAN to the correct CAN messages
|
||||
|
||||
//Maxvoltage (eg 400.0V = 4000 , 16bits long) Charge Cutoff Voltage
|
||||
SOFAR_351.data.u8[0] = (max_volt_sofar_can >> 8);
|
||||
SOFAR_351.data.u8[1] = (max_volt_sofar_can & 0x00FF);
|
||||
//SOFAR_351.data.u8[2] = DC charge current limitation (Default 25.0A)
|
||||
//SOFAR_351.data.u8[3] = DC charge current limitation
|
||||
//SOFAR_351.data.u8[4] = DC discharge current limitation (Default 25.0A)
|
||||
//SOFAR_351.data.u8[5] = DC discharge current limitation
|
||||
//Minvoltage (eg 300.0V = 3000 , 16bits long) Discharge Cutoff Voltage
|
||||
SOFAR_351.data.u8[6] = (min_volt_sofar_can >> 8);
|
||||
SOFAR_351.data.u8[7] = (min_volt_sofar_can & 0x00FF);
|
||||
|
||||
//SOC
|
||||
SOFAR_355.data.u8[0] = (SOC/100);
|
||||
SOFAR_355.data.u8[2] = (StateOfHealth/100);
|
||||
//SOFAR_355.data.u8[6] = (AH_remaining >> 8);
|
||||
//SOFAR_355.data.u8[7] = (AH_remaining & 0x00FF);
|
||||
|
||||
//Voltage (370.0)
|
||||
SOFAR_356.data.u8[0] = (battery_voltage >> 8);
|
||||
SOFAR_356.data.u8[1] = (battery_voltage & 0x00FF);
|
||||
SOFAR_356.data.u8[2] = (battery_current >> 8);
|
||||
SOFAR_356.data.u8[3] = (battery_current & 0x00FF);
|
||||
SOFAR_356.data.u8[2] = (temperature_max >> 8);
|
||||
SOFAR_356.data.u8[3] = (temperature_max & 0x00FF);
|
||||
|
||||
}
|
||||
|
||||
void receive_can_sofar(CAN_frame_t rx_frame)
|
||||
{
|
||||
switch (rx_frame.MsgID)
|
||||
{ //In here we need to respond to the inverter. TODO make logic
|
||||
case 0x605:
|
||||
//frame1_605 = rx_frame.data.u8[1];
|
||||
//frame3_605 = rx_frame.data.u8[3];
|
||||
break;
|
||||
case 0x705:
|
||||
//frame1_705 = rx_frame.data.u8[1];
|
||||
//frame3_705 = rx_frame.data.u8[3];
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void send_can_sofar()
|
||||
{
|
||||
unsigned long currentMillis = millis();
|
||||
// Send 100ms CAN Message
|
||||
if (currentMillis - previousMillis100 >= interval100)
|
||||
{
|
||||
previousMillis100 = currentMillis;
|
||||
//Frames actively reported by BMS
|
||||
ESP32Can.CANWriteFrame(&SOFAR_351);
|
||||
ESP32Can.CANWriteFrame(&SOFAR_355);
|
||||
ESP32Can.CANWriteFrame(&SOFAR_356);
|
||||
ESP32Can.CANWriteFrame(&SOFAR_30F);
|
||||
ESP32Can.CANWriteFrame(&SOFAR_359);
|
||||
ESP32Can.CANWriteFrame(&SOFAR_35E);
|
||||
ESP32Can.CANWriteFrame(&SOFAR_35F);
|
||||
ESP32Can.CANWriteFrame(&SOFAR_35A);
|
||||
}
|
||||
}
|
39
Software/src/inverter/SOFAR-CAN.h
Normal file
39
Software/src/inverter/SOFAR-CAN.h
Normal file
|
@ -0,0 +1,39 @@
|
|||
#ifndef SOFAR_CAN_H
|
||||
#define SOFAR_CAN_H
|
||||
#include <Arduino.h>
|
||||
#include "../../ESP32CAN.h"
|
||||
#include "../../USER_SETTINGS.h"
|
||||
|
||||
// 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
|
||||
extern uint16_t min_volt_sofar_can;
|
||||
extern uint16_t max_volt_sofar_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_sofar();
|
||||
void send_can_sofar();
|
||||
void receive_can_sofar(CAN_frame_t rx_frame);
|
||||
|
||||
#endif
|
222
Software/src/inverter/SOLAX-CAN.cpp
Normal file
222
Software/src/inverter/SOLAX-CAN.cpp
Normal file
|
@ -0,0 +1,222 @@
|
|||
#include "SOLAX-CAN.h"
|
||||
|
||||
/* Do not change code below unless you are sure what you are doing */
|
||||
static uint16_t max_charge_rate_amp = 0;
|
||||
static uint16_t max_discharge_rate_amp = 0;
|
||||
static uint16_t temperature_average = 0;
|
||||
static int STATE = BATTERY_ANNOUNCE;
|
||||
static unsigned long LastFrameTime = 0;
|
||||
static int number_of_batteries = 1;
|
||||
|
||||
//CAN message translations from this amazing repository: https://github.com/rand12345/solax_can_bus
|
||||
|
||||
CAN_frame_t SOLAX_1801 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x1801,.data = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}};
|
||||
CAN_frame_t SOLAX_1872 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x1872,.data = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}; //BMS_Limits
|
||||
CAN_frame_t SOLAX_1873 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x1873,.data = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}; //BMS_PackData
|
||||
CAN_frame_t SOLAX_1874 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x1874,.data = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}; //BMS_CellData
|
||||
CAN_frame_t SOLAX_1875 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x1875,.data = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}; //BMS_Status
|
||||
CAN_frame_t SOLAX_1876 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x1876,.data = {0x0, 0x0, 0xE2, 0x0C, 0x0, 0x0, 0xD7, 0x0C}}; //BMS_PackTemps
|
||||
CAN_frame_t SOLAX_1877 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x1877,.data = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}};
|
||||
CAN_frame_t SOLAX_1878 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x1878,.data = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}; //BMS_PackStats
|
||||
CAN_frame_t SOLAX_1879 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x1879,.data = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}};
|
||||
CAN_frame_t SOLAX_1881 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x1881,.data = {0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}; // E.g.: 0 6 S B M S F A
|
||||
CAN_frame_t SOLAX_1882 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x1882,.data = {0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}; // E.g.: 0 2 3 A B 0 5 2
|
||||
CAN_frame_t SOLAX_100A001 = {.FIR = {.B = {.DLC = 0,.FF = CAN_frame_ext,}},.MsgID = 0x100A001,.data = {}};
|
||||
|
||||
// __builtin_bswap64 needed to convert to ESP32 little endian format
|
||||
// Byte[4] defines the requested contactor state: 1 = Closed , 0 = Open
|
||||
#define Contactor_Open_Payload __builtin_bswap64(0x0200010000000000)
|
||||
#define Contactor_Close_Payload __builtin_bswap64(0x0200010001000000)
|
||||
|
||||
void CAN_WriteFrame(CAN_frame_t* tx_frame)
|
||||
{
|
||||
#ifdef DUAL_CAN
|
||||
CANMessage MCP2515Frame; //Struct with ACAN2515 library format, needed to use the MCP2515 library
|
||||
MCP2515Frame.id = tx_frame->MsgID;
|
||||
MCP2515Frame.ext = tx_frame->FIR.B.FF;
|
||||
MCP2515Frame.len = tx_frame->FIR.B.DLC;
|
||||
for (uint8_t i=0 ; i<MCP2515Frame.len ; i++) {
|
||||
MCP2515Frame.data[i] = tx_frame->data.u8[i];
|
||||
}
|
||||
can.tryToSend(MCP2515Frame);
|
||||
#else
|
||||
ESP32Can.CANWriteFrame(tx_frame);
|
||||
#endif
|
||||
}
|
||||
|
||||
void update_values_can_solax()
|
||||
{ //This function maps all the values fetched from battery CAN to the correct CAN messages
|
||||
// If not receiveing any communication from the inverter, open contactors and return to battery announce state
|
||||
if (millis() - LastFrameTime >= SolaxTimeout)
|
||||
{
|
||||
inverterAllowsContactorClosing = 0;
|
||||
STATE = BATTERY_ANNOUNCE;
|
||||
}
|
||||
//Calculate the required values
|
||||
temperature_average = ((temperature_max + temperature_min)/2);
|
||||
|
||||
//max_target_charge_power (30000W max)
|
||||
if(SOC > 9999) //99.99%
|
||||
{ //Additional safety incase SOC% is 100, then do not charge battery further
|
||||
max_charge_rate_amp = 0;
|
||||
}
|
||||
else
|
||||
{ //We can pass on the battery charge rate (in W) to the inverter (that takes A)
|
||||
if(max_target_charge_power >= 30000)
|
||||
{
|
||||
max_charge_rate_amp = 75; //Incase battery can take over 30kW, cap value to 75A
|
||||
}
|
||||
else
|
||||
{ //Calculate the W value into A
|
||||
max_charge_rate_amp = (max_target_charge_power/(battery_voltage*0.1)); // P/U = I
|
||||
}
|
||||
}
|
||||
|
||||
//max_target_discharge_power (30000W max)
|
||||
if(SOC < 100) //1.00%
|
||||
{ //Additional safety incase SOC% is below 1, then do not charge battery further
|
||||
max_discharge_rate_amp = 0;
|
||||
}
|
||||
else
|
||||
{ //We can pass on the battery discharge rate to the inverter
|
||||
if(max_target_discharge_power >= 30000)
|
||||
{
|
||||
max_discharge_rate_amp = 75; //Incase battery can be charged with over 30kW, cap value to 75A
|
||||
}
|
||||
else
|
||||
{ //Calculate the W value into A
|
||||
max_discharge_rate_amp = (max_target_discharge_power/(battery_voltage*0.1)); // P/U = I
|
||||
}
|
||||
}
|
||||
|
||||
//Put the values into the CAN messages
|
||||
//BMS_Limits
|
||||
SOLAX_1872.data.u8[0] = (uint8_t) max_volt_solax_can; //Todo, scaling OK?
|
||||
SOLAX_1872.data.u8[1] = (max_volt_solax_can >> 8);
|
||||
SOLAX_1872.data.u8[2] = (uint8_t) min_volt_solax_can; //Todo, scaling OK?
|
||||
SOLAX_1872.data.u8[3] = (min_volt_solax_can >> 8);
|
||||
SOLAX_1872.data.u8[4] = (uint8_t) (max_charge_rate_amp*10); //Todo, scaling OK?
|
||||
SOLAX_1872.data.u8[5] = ((max_charge_rate_amp*10) >> 8);
|
||||
SOLAX_1872.data.u8[6] = (uint8_t) (max_discharge_rate_amp*10); //Todo, scaling OK?
|
||||
SOLAX_1872.data.u8[7] = ((max_discharge_rate_amp*10) >> 8);
|
||||
|
||||
//BMS_PackData
|
||||
SOLAX_1873.data.u8[0] = (uint8_t) battery_voltage; // OK
|
||||
SOLAX_1873.data.u8[1] = (battery_voltage >> 8);
|
||||
SOLAX_1873.data.u8[2] = (int8_t) battery_current; // OK, Signed (Active current in Amps x 10)
|
||||
SOLAX_1873.data.u8[3] = (battery_current >> 8);
|
||||
SOLAX_1873.data.u8[4] = (uint8_t) (SOC/100); //SOC (100.00%)
|
||||
//SOLAX_1873.data.u8[5] = //Seems like this is not required? Or shall we put SOC decimals here?
|
||||
SOLAX_1873.data.u8[6] = (uint8_t) (remaining_capacity_Wh/100); //Todo, scaling OK?
|
||||
SOLAX_1873.data.u8[7] = ((remaining_capacity_Wh/100) >> 8);
|
||||
|
||||
//BMS_CellData
|
||||
SOLAX_1874.data.u8[0] = (uint8_t) temperature_max;
|
||||
SOLAX_1874.data.u8[1] = (temperature_max >> 8);
|
||||
SOLAX_1874.data.u8[2] = (uint8_t) temperature_min;
|
||||
SOLAX_1874.data.u8[3] = (temperature_min >> 8);
|
||||
SOLAX_1874.data.u8[4] = (uint8_t) (cell_max_voltage); //Todo, scaling OK? Supposed to be alarm trigger absolute cell max?
|
||||
SOLAX_1874.data.u8[5] = (cell_max_voltage >> 8);
|
||||
SOLAX_1874.data.u8[6] = (uint8_t) (cell_min_voltage); //Todo, scaling OK? Supposed to be alarm trigger absolute cell min?
|
||||
SOLAX_1874.data.u8[7] = (cell_min_voltage >> 8);
|
||||
|
||||
//BMS_Status
|
||||
SOLAX_1875.data.u8[0] = (uint8_t) temperature_average;
|
||||
SOLAX_1875.data.u8[1] = (temperature_average >> 8);
|
||||
SOLAX_1875.data.u8[2] = (uint8_t) 0; // Number of slave batteries
|
||||
SOLAX_1875.data.u8[4] = (uint8_t) 0; // Contactor Status 0=off, 1=on.
|
||||
|
||||
//BMS_PackTemps (strange name, since it has voltages?)
|
||||
SOLAX_1876.data.u8[2] = (uint8_t) cell_max_voltage; //Todo, scaling OK?
|
||||
SOLAX_1876.data.u8[3] = (cell_min_voltage >> 8);
|
||||
|
||||
SOLAX_1876.data.u8[6] = (uint8_t) cell_min_voltage; //Todo, scaling OK?
|
||||
SOLAX_1876.data.u8[7] = (cell_min_voltage >> 8);
|
||||
|
||||
//Unknown
|
||||
SOLAX_1877.data.u8[4] = (uint8_t) 0x50; // Battery type
|
||||
SOLAX_1877.data.u8[6] = (uint8_t) 0x22; // Firmware version?
|
||||
SOLAX_1877.data.u8[7] = (uint8_t) 0x02; // The above firmware version applies to:02 = Master BMS, 10 = S1, 20 = S2, 30 = S3, 40 = S4
|
||||
|
||||
//BMS_PackStats
|
||||
SOLAX_1878.data.u8[0] = (uint8_t) (battery_voltage); //TODO, should this be max or current voltage?
|
||||
SOLAX_1878.data.u8[1] = ((battery_voltage) >> 8);
|
||||
|
||||
SOLAX_1878.data.u8[4] = (uint8_t) capacity_Wh; //TODO, scaling OK?
|
||||
SOLAX_1878.data.u8[5] = (capacity_Wh >> 8);
|
||||
|
||||
// BMS_Answer
|
||||
SOLAX_1801.data.u8[0] = 2;
|
||||
SOLAX_1801.data.u8[2] = 1;
|
||||
SOLAX_1801.data.u8[4] = 1;
|
||||
|
||||
}
|
||||
|
||||
void receive_can_solax(CAN_frame_t rx_frame)
|
||||
{
|
||||
if (rx_frame.MsgID == 0x1871 && rx_frame.data.u8[0] == (0x01) || rx_frame.MsgID == 0x1871 && rx_frame.data.u8[0] == (0x02)) {
|
||||
LastFrameTime = millis();
|
||||
switch(STATE)
|
||||
{
|
||||
case(BATTERY_ANNOUNCE):
|
||||
Serial.println("Solax Battery State: Announce");
|
||||
inverterAllowsContactorClosing = 0;
|
||||
SOLAX_1875.data.u8[4] = (0x00); // Inform Inverter: Contactor 0=off, 1=on.
|
||||
for (int i=0; i <= number_of_batteries; i++) {
|
||||
CAN_WriteFrame(&SOLAX_1872);
|
||||
CAN_WriteFrame(&SOLAX_1873);
|
||||
CAN_WriteFrame(&SOLAX_1874);
|
||||
CAN_WriteFrame(&SOLAX_1875);
|
||||
CAN_WriteFrame(&SOLAX_1876);
|
||||
CAN_WriteFrame(&SOLAX_1877);
|
||||
CAN_WriteFrame(&SOLAX_1878);
|
||||
}
|
||||
CAN_WriteFrame(&SOLAX_100A001); //BMS Announce
|
||||
// Message from the inverter to proceed to contactor closing
|
||||
// Byte 4 changes from 0 to 1
|
||||
if (rx_frame.data.u64 == Contactor_Close_Payload)
|
||||
STATE = WAITING_FOR_CONTACTOR;
|
||||
break;
|
||||
|
||||
case(WAITING_FOR_CONTACTOR):
|
||||
SOLAX_1875.data.u8[4] = (0x00); // Inform Inverter: Contactor 0=off, 1=on.
|
||||
CAN_WriteFrame(&SOLAX_1872);
|
||||
CAN_WriteFrame(&SOLAX_1873);
|
||||
CAN_WriteFrame(&SOLAX_1874);
|
||||
CAN_WriteFrame(&SOLAX_1875);
|
||||
CAN_WriteFrame(&SOLAX_1876);
|
||||
CAN_WriteFrame(&SOLAX_1877);
|
||||
CAN_WriteFrame(&SOLAX_1878);
|
||||
CAN_WriteFrame(&SOLAX_1801); // Announce that the battery will be connected
|
||||
STATE = CONTACTOR_CLOSED; // Jump to Contactor Closed State
|
||||
Serial.println("Solax Battery State: Contactor Closed");
|
||||
break;
|
||||
|
||||
case(CONTACTOR_CLOSED):
|
||||
inverterAllowsContactorClosing = 1;
|
||||
SOLAX_1875.data.u8[4] = (0x01); // Inform Inverter: Contactor 0=off, 1=on.
|
||||
CAN_WriteFrame(&SOLAX_1872);
|
||||
CAN_WriteFrame(&SOLAX_1873);
|
||||
CAN_WriteFrame(&SOLAX_1874);
|
||||
CAN_WriteFrame(&SOLAX_1875);
|
||||
CAN_WriteFrame(&SOLAX_1876);
|
||||
CAN_WriteFrame(&SOLAX_1877);
|
||||
CAN_WriteFrame(&SOLAX_1878);
|
||||
// Message from the inverter to open contactor
|
||||
// Byte 4 changes from 1 to 0
|
||||
if (rx_frame.data.u64 == Contactor_Open_Payload)
|
||||
STATE = BATTERY_ANNOUNCE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (rx_frame.MsgID == 0x1871 && rx_frame.data.u64 == __builtin_bswap64(0x0500010000000000)) {
|
||||
CAN_WriteFrame(&SOLAX_1881);
|
||||
CAN_WriteFrame(&SOLAX_1882);
|
||||
Serial.println("1871 05-frame received from inverter");
|
||||
}
|
||||
if (rx_frame.MsgID == 0x1871 && rx_frame.data.u8[0] == (0x03)) {
|
||||
Serial.println("1871 03-frame received from inverter");
|
||||
}
|
||||
|
||||
}
|
43
Software/src/inverter/SOLAX-CAN.h
Normal file
43
Software/src/inverter/SOLAX-CAN.h
Normal file
|
@ -0,0 +1,43 @@
|
|||
#ifndef SOLAX_CAN_H
|
||||
#define SOLAX_CAN_H
|
||||
#include <Arduino.h>
|
||||
#include "../../ESP32CAN.h"
|
||||
#include "../../config.h"
|
||||
#include "../../USER_SETTINGS.h"
|
||||
|
||||
#include "../../ACAN2515.h"
|
||||
extern ACAN2515 can;
|
||||
|
||||
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_solax_can;
|
||||
extern uint16_t max_volt_solax_can;
|
||||
extern uint16_t cell_max_voltage;
|
||||
extern uint16_t cell_min_voltage;
|
||||
extern uint8_t inverterAllowsContactorClosing;
|
||||
|
||||
// Timeout in milliseconds
|
||||
#define SolaxTimeout 2000
|
||||
|
||||
//SOLAX BMS States Definition
|
||||
#define BATTERY_ANNOUNCE 0
|
||||
#define WAITING_FOR_CONTACTOR 1
|
||||
#define CONTACTOR_CLOSED 2
|
||||
#define FAULT_SOLAX 3
|
||||
#define UPDATING_FW 4
|
||||
|
||||
void update_values_can_solax();
|
||||
void receive_can_solax(CAN_frame_t rx_frame);
|
||||
#endif
|
Loading…
Add table
Add a link
Reference in a new issue