mirror of
https://github.com/dalathegreat/Battery-Emulator.git
synced 2025-10-05 02:39:57 +02:00
Refactor according to 7.8.dev
This commit is contained in:
parent
504c6b3d7a
commit
a005533ef2
4 changed files with 61 additions and 84 deletions
|
@ -170,7 +170,7 @@ void setup() {
|
|||
init_rs485();
|
||||
|
||||
init_serialDataLink();
|
||||
#if defined(CAN_INVERTER_SELECTED) || defined(MODBUS_INVERTER_SELECTED)
|
||||
#if defined(CAN_INVERTER_SELECTED) || defined(MODBUS_INVERTER_SELECTED) || defined(RS485_INVERTER_SELECTED)
|
||||
setup_inverter();
|
||||
#endif
|
||||
setup_battery();
|
||||
|
|
|
@ -72,6 +72,7 @@ void update_modbus_registers_inverter();
|
|||
#ifdef RS485_INVERTER_SELECTED
|
||||
void receive_RS485();
|
||||
void update_RS485_registers_inverter();
|
||||
void setup_inverter();
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
|
|
@ -10,8 +10,6 @@
|
|||
static const uint8_t KOSTAL_FRAMEHEADER[5] = {0x62, 0xFF, 0x02, 0xFF, 0x29};
|
||||
static const uint8_t KOSTAL_FRAMEHEADER2[5] = {0x63, 0xFF, 0x02, 0xFF, 0x29};
|
||||
static uint16_t nominal_voltage_dV = 0;
|
||||
static uint16_t discharge_current_dA = 0;
|
||||
static uint16_t charge_current_dA = 0;
|
||||
static int16_t average_temperature_dC = 0;
|
||||
static uint8_t incoming_message_counter = RS485_HEALTHY;
|
||||
static int8_t f2_startup_count = 0;
|
||||
|
@ -29,31 +27,33 @@ union f32b {
|
|||
byte b[4];
|
||||
};
|
||||
|
||||
uint8_t BATTERY_INFO[40] = {0x00, 0xE2, 0xFF, 0x02, 0xFF, 0x29, // Frame header
|
||||
0x00, 0x00, 0x80, 0x43, // 256.063 Nominal voltage / 5*51.2=256 first byte 0x01 or 0x04
|
||||
0xE4, 0x70, 0x8A, 0x5C, // Manufacture date (Epoch time) (BYD: GetBatteryInfo this[0x10ac])
|
||||
0xB5, 0x00, 0xD3, 0x00, // Battery Serial number? Modbus register 527 - 0x10b0
|
||||
0x00, 0x00, 0xC8, 0x41, // 0x10b4
|
||||
0xC2, 0x18, // Battery Firmware, modbus register 586 (0x10b8)
|
||||
0x00, // Static (BYD: GetBatteryInfo this[0x10ba])
|
||||
0x00, // ?
|
||||
0x59, 0x42, // Static (BYD: GetBatteryInfo this[0x10bc])
|
||||
0x00, 0x00, // Static (BYD: GetBatteryInfo this[0x10be])
|
||||
0x00, 0x00,
|
||||
0x05, 0x00, 0xA0, 0x00, 0x00, 0x00,
|
||||
0x4D, // CRC
|
||||
0x00}; //
|
||||
uint8_t BATTERY_INFO[40] = {
|
||||
0x00, 0xE2, 0xFF, 0x02, 0xFF, 0x29, // Frame header
|
||||
0x00, 0x00, 0x80, 0x43, // 256.063 Nominal voltage / 5*51.2=256 first byte 0x01 or 0x04
|
||||
0xE4, 0x70, 0x8A, 0x5C, // Manufacture date (Epoch time) (BYD: GetBatteryInfo this[0x10ac])
|
||||
0xB5, 0x00, 0xD3, 0x00, // Battery Serial number? Modbus register 527 - 0x10b0
|
||||
0x00, 0x00, 0xC8, 0x41, // 0x10b4
|
||||
0xC2, 0x18, // Battery Firmware, modbus register 586 (0x10b8)
|
||||
0x00, // Static (BYD: GetBatteryInfo this[0x10ba])
|
||||
0x00, // ?
|
||||
0x59, 0x42, // Static (BYD: GetBatteryInfo this[0x10bc])
|
||||
0x00, 0x00, // Static (BYD: GetBatteryInfo this[0x10be])
|
||||
0x00, 0x00, 0x05, 0x00, 0xA0, 0x00, 0x00, 0x00,
|
||||
0x4D, // CRC
|
||||
0x00}; //
|
||||
|
||||
// values in CyclicData will be overwritten at update_modbus_registers_inverter()
|
||||
|
||||
uint8_t CyclicData[64] = {
|
||||
0x00, // First zero byte pointer
|
||||
0xE2, 0xFF, 0x02, 0xFF, 0x29, // frame Header
|
||||
0x1D, 0x5A, 0x85, 0x43, // Current Voltage (float) Modbus register 216, Bytes 6-9
|
||||
0x00, 0x00, 0x8D, 0x43, // Max Voltage (2 byte float), Bytes 12-13
|
||||
0x00, 0x00, 0xAC, 0x41, // BAttery Temperature (2 byte float) Modbus register 214, Bytes 16-17
|
||||
0x00, 0x00, 0x00, 0x00, // Peak Current (1s period?), Bytes 18-21 - Communication fault seen with some values (>10A?)
|
||||
0x00, 0x00, 0x00, 0x00, // Avg current (1s period?), Bytes 22-25 - Communication fault seen with some values (>10A?)
|
||||
0x1D, 0x5A, 0x85, 0x43, // Current Voltage (float) Modbus register 216, Bytes 6-9
|
||||
0x00, 0x00, 0x8D, 0x43, // Max Voltage (2 byte float), Bytes 12-13
|
||||
0x00, 0x00, 0xAC, 0x41, // BAttery Temperature (2 byte float) Modbus register 214, Bytes 16-17
|
||||
0x00, 0x00, 0x00,
|
||||
0x00, // Peak Current (1s period?), Bytes 18-21 - Communication fault seen with some values (>10A?)
|
||||
0x00, 0x00, 0x00,
|
||||
0x00, // Avg current (1s period?), Bytes 22-25 - Communication fault seen with some values (>10A?)
|
||||
0x00, 0x00, 0x48, 0x42, // Max discharge current (2 byte float), Bit 26-29,
|
||||
// Sunspec: ADisChaMax
|
||||
0x00, 0x00, 0xC8, 0x41, // Battery gross capacity, Ah (2 byte float) , Bytes 30-33, Modbus 512
|
||||
|
@ -64,14 +64,14 @@ uint8_t CyclicData[64] = {
|
|||
0xA4, 0x70, 0x55, 0x40, // MaxCellVolt (float), Bit 46-49
|
||||
0x7D, 0x3F, 0x55, 0x40, // MinCellVolt (float), Bit 50-53
|
||||
|
||||
0xFE, 0x04, // Cycle count,
|
||||
0x00, // Byte 56
|
||||
0x00, // When SOC=100 Byte57=0x40, at startup 0x03 (about 7 times), otherwise 0x02
|
||||
0x64, // SOC , Bit 58
|
||||
0x00, // Unknown,
|
||||
0x00, // Unknown,
|
||||
0x00, // Unknown,
|
||||
0x00, // CRC (inverted sum of bytes 1-62 + 0xC0), Bit 62
|
||||
0xFE, 0x04, // Cycle count,
|
||||
0x00, // Byte 56
|
||||
0x00, // When SOC=100 Byte57=0x40, at startup 0x03 (about 7 times), otherwise 0x02
|
||||
0x64, // SOC , Bit 58
|
||||
0x00, // Unknown,
|
||||
0x00, // Unknown,
|
||||
0x00, // Unknown,
|
||||
0x00, // CRC (inverted sum of bytes 1-62 + 0xC0), Bit 62
|
||||
0x00};
|
||||
|
||||
// FE 04 01 40 xx 01 01 02 yy (fully charged)
|
||||
|
@ -84,7 +84,7 @@ uint8_t frame3[9] = {
|
|||
0x00 //endbyte
|
||||
};
|
||||
|
||||
uint8_t frame4[8] = {0x07, 0xE3, 0xFF, 0x02, 0xFF, 0x29, 0xF4, 0x00};
|
||||
uint8_t frame4[8] = {0x07, 0xE3, 0xFF, 0x02, 0xFF, 0x29, 0xF4, 0x00};
|
||||
uint8_t frameB1[10] = {0x07, 0x63, 0xFF, 0x02, 0xFF, 0x29, 0x5E, 0x02, 0x16, 0x00};
|
||||
|
||||
uint8_t RS485_RXFRAME[10];
|
||||
|
@ -115,25 +115,25 @@ void send_kostal(byte* arr, int alen) {
|
|||
Serial2.write(arr, alen);
|
||||
}
|
||||
|
||||
void scramble_null_bytes(byte *lfc, int len) {
|
||||
void scramble_null_bytes(byte* lfc, int len) {
|
||||
int last_null_byte = 0;
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (lfc[i] == '\0') {
|
||||
lfc[last_null_byte] = (byte) (i - last_null_byte);
|
||||
lfc[last_null_byte] = (byte)(i - last_null_byte);
|
||||
last_null_byte = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
byte calculate_kostal_crc(byte *lfc, int len) {
|
||||
byte calculate_kostal_crc(byte* lfc, int len) {
|
||||
unsigned int sum = 0;
|
||||
if (lfc[0] != 0) {
|
||||
printf("WARNING: first byte should be 0, but is 0x%02x\n", lfc[0]);
|
||||
printf("WARNING: first byte should be 0, but is 0x%02x\n", lfc[0]);
|
||||
}
|
||||
for (int i = 1; i < len; i++) {
|
||||
sum += lfc[i];
|
||||
sum += lfc[i];
|
||||
}
|
||||
return (byte) (-sum & 0xff);
|
||||
return (byte)(-sum & 0xff);
|
||||
}
|
||||
|
||||
byte calculate_frame1_crc(byte* lfc, int lastbyte) {
|
||||
|
@ -158,39 +158,14 @@ bool check_kostal_frame_crc() {
|
|||
|
||||
void update_RS485_registers_inverter() {
|
||||
|
||||
if (datalayer.battery.status.voltage_dV > 10) { // Only update value when we have voltage available to avoid div0
|
||||
charge_current_dA =
|
||||
((datalayer.battery.status.max_charge_power_W * 10) /
|
||||
datalayer.battery.status.voltage_dV); //Charge power in W , max volt in V+1decimal (P=UI, solve for I)
|
||||
//The above calculation results in (30 000*10)/3700=81A
|
||||
charge_current_dA = (charge_current_dA * 10); //Value needs a decimal before getting sent to inverter (81.0A)
|
||||
|
||||
discharge_current_dA =
|
||||
((datalayer.battery.status.max_discharge_power_W * 10) /
|
||||
datalayer.battery.status.voltage_dV); //Charge power in W , max volt in V+1decimal (P=UI, solve for I)
|
||||
//The above calculation results in (30 000*10)/3700=81A
|
||||
discharge_current_dA = (discharge_current_dA * 10); //Value needs a decimal before getting sent to inverter (81.0A)
|
||||
}
|
||||
|
||||
if (charge_current_dA > datalayer.battery.info.max_charge_amp_dA) {
|
||||
charge_current_dA =
|
||||
datalayer.battery.info
|
||||
.max_charge_amp_dA; //Cap the value to the max allowed Amp. Some inverters cannot handle large values.
|
||||
}
|
||||
|
||||
if (discharge_current_dA > datalayer.battery.info.max_discharge_amp_dA) {
|
||||
discharge_current_dA =
|
||||
datalayer.battery.info
|
||||
.max_discharge_amp_dA; //Cap the value to the max allowed Amp. Some inverters cannot handle large values.
|
||||
}
|
||||
|
||||
average_temperature_dC =
|
||||
((datalayer.battery.status.temperature_max_dC + datalayer.battery.status.temperature_min_dC) / 2);
|
||||
if (datalayer.battery.status.temperature_min_dC < 0) {
|
||||
average_temperature_dC = 0;
|
||||
}
|
||||
|
||||
if (datalayer.system.status.battery_allows_contactor_closing & datalayer.system.status.inverter_allows_contactor_closing ) {
|
||||
if (datalayer.system.status.battery_allows_contactor_closing &
|
||||
datalayer.system.status.inverter_allows_contactor_closing) {
|
||||
float2frame(CyclicData, (float)datalayer.battery.status.voltage_dV / 10, 6); // Confirmed OK mapping
|
||||
} else {
|
||||
float2frame(CyclicData, 0.0, 6);
|
||||
|
@ -206,17 +181,18 @@ void update_RS485_registers_inverter() {
|
|||
float2frame(CyclicData, (float)average_temperature_dC / 10, 14);
|
||||
|
||||
// Some current values causes communication error, must be resolved, why.
|
||||
float2frame(CyclicData, (float)datalayer.battery.status.current_dA / 10, 18); // Peak discharge? current (2 byte float)
|
||||
float2frame(CyclicData, (float)datalayer.battery.status.current_dA / 10,
|
||||
18); // Peak discharge? current (2 byte float)
|
||||
float2frame(CyclicData, (float)datalayer.battery.status.current_dA / 10, 22);
|
||||
|
||||
float2frame(CyclicData, (float)discharge_current_dA / 10, 26); // BAttery capacity Ah
|
||||
float2frame(CyclicData, (float)datalayer.battery.status.max_discharge_current_dA / 10, 26); // BAttery capacity Ah
|
||||
|
||||
float2frame(CyclicData, (float)discharge_current_dA / 10, 30);
|
||||
float2frame(CyclicData, (float)datalayer.battery.status.max_discharge_current_dA / 10, 30);
|
||||
|
||||
// When SOC = 100%, drop down allowed charge current down.
|
||||
|
||||
if ((datalayer.battery.status.reported_soc / 100) < 100) {
|
||||
float2frame(CyclicData, (float)charge_current_dA / 10, 34);
|
||||
float2frame(CyclicData, (float)datalayer.battery.status.max_charge_current_dA / 10, 34);
|
||||
} else {
|
||||
float2frame(CyclicData, 0.0, 34);
|
||||
}
|
||||
|
@ -231,7 +207,6 @@ void update_RS485_registers_inverter() {
|
|||
|
||||
register_content_ok = true;
|
||||
|
||||
|
||||
if (incoming_message_counter > 0) {
|
||||
incoming_message_counter--;
|
||||
}
|
||||
|
@ -258,7 +233,7 @@ void receive_RS485() // Runs as fast as possible to handle the serial stream
|
|||
|
||||
if (startupMillis) {
|
||||
if (((currentMillis - startupMillis) >= INTERVAL_2_S & currentMillis - startupMillis <= 7000) &
|
||||
datalayer.system.status.inverter_allows_contactor_closing) {
|
||||
datalayer.system.status.inverter_allows_contactor_closing) {
|
||||
// Disconnect allowed only, when curren zero
|
||||
if (datalayer.battery.status.current_dA == 0) {
|
||||
datalayer.system.status.inverter_allows_contactor_closing = false;
|
||||
|
@ -294,29 +269,25 @@ void receive_RS485() // Runs as fast as possible to handle the serial stream
|
|||
|
||||
if (RS485_RXFRAME[1] == 'c') {
|
||||
if (RS485_RXFRAME[6] == 0x47) {
|
||||
// Set time function - Do nothing.
|
||||
send_kostal(frame4, 8); // ACK
|
||||
// Set time function - Do nothing.
|
||||
send_kostal(frame4, 8); // ACK
|
||||
}
|
||||
if (RS485_RXFRAME[6] == 0x5E) {
|
||||
// Set State function
|
||||
if (RS485_RXFRAME[7] == 0x01) {
|
||||
// State X
|
||||
}
|
||||
else if (RS485_RXFRAME[7] == 0x04) {
|
||||
} else if (RS485_RXFRAME[7] == 0x04) {
|
||||
// INVALID
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// State Y
|
||||
}
|
||||
send_kostal(frame4, 8); // ACK
|
||||
send_kostal(frame4, 8); // ACK
|
||||
}
|
||||
}
|
||||
else if (RS485_RXFRAME[1] == 'b') {
|
||||
} else if (RS485_RXFRAME[1] == 'b') {
|
||||
if (RS485_RXFRAME[6] == 0x50) {
|
||||
//Reverse polarity, do nothing
|
||||
}
|
||||
else {
|
||||
int code=RS485_RXFRAME[6] + RS485_RXFRAME[7]*0x100;
|
||||
} else {
|
||||
int code = RS485_RXFRAME[6] + RS485_RXFRAME[7] * 0x100;
|
||||
if (code == 0x44a) {
|
||||
//Send cyclic data
|
||||
update_values_battery();
|
||||
|
@ -327,7 +298,7 @@ void receive_RS485() // Runs as fast as possible to handle the serial stream
|
|||
byte tmpframe[64]; //copy values to prevent data manipulation during rewrite/crc calculation
|
||||
memcpy(tmpframe, CyclicData, 64);
|
||||
tmpframe[62] = calculate_kostal_crc(tmpframe, 62);
|
||||
scramble_null_bytes(tmpframe,64);
|
||||
scramble_null_bytes(tmpframe, 64);
|
||||
send_kostal(tmpframe, 64);
|
||||
}
|
||||
if (code == 0x84a) {
|
||||
|
@ -358,4 +329,9 @@ void receive_RS485() // Runs as fast as possible to handle the serial stream
|
|||
}
|
||||
}
|
||||
|
||||
void setup_inverter(void) { // Performs one time setup at startup
|
||||
strncpy(datalayer.system.info.inverter_protocol, "BYD battery via Kostal RS485", 63);
|
||||
datalayer.system.info.inverter_protocol[63] = '\0';
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -4,6 +4,6 @@
|
|||
#include "../include.h"
|
||||
|
||||
#define RS485_INVERTER_SELECTED
|
||||
#define DEBUG_KOSTAL_RS485_DATA // Enable this line to get TX / RX printed out via serial
|
||||
//#define DEBUG_KOSTAL_RS485_DATA // Enable this line to get TX / RX printed out via serial
|
||||
|
||||
#endif
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue