Merge branch 'main' into SIMPBMS

This commit is contained in:
Jamie Jones 2025-02-25 09:40:59 +00:00 committed by GitHub
commit 10b85f36f0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
80 changed files with 4692 additions and 2346 deletions

View file

@ -42,6 +42,10 @@ void setup_can_shunt();
#include "FOXESS-BATTERY.h"
#endif
#ifdef ORION_BMS
#include "ORION-BMS.h"
#endif
#ifdef SONO_BATTERY
#include "SONO-BATTERY.h"
#endif
@ -86,6 +90,10 @@ void setup_can_shunt();
#include "PYLON-BATTERY.h"
#endif
#ifdef DALY_BMS
#include "DALY-BMS.h"
#endif
#ifdef RJXZS_BMS
#include "RJXZS-BMS.h"
#endif
@ -131,14 +139,24 @@ void setup_can_shunt();
#include "VOLVO-SPA-BATTERY.h"
#endif
#ifdef VOLVO_SPA_HYBRID_BATTERY
#include "VOLVO-SPA-HYBRID-BATTERY.h"
#endif
#ifdef SERIAL_LINK_RECEIVER
#include "SERIAL-LINK-RECEIVER-FROM-BATTERY.h"
#endif
void handle_incoming_can_frame_battery(CAN_frame rx_frame);
void update_values_battery();
void transmit_can_battery();
void setup_battery(void);
void update_values_battery();
#ifdef RS485_BATTERY_SELECTED
void transmit_rs485();
void receive_RS485();
#else
void handle_incoming_can_frame_battery(CAN_frame rx_frame);
void transmit_can_battery();
#endif
#ifdef DOUBLE_BATTERY
void update_values_battery2();

View file

@ -790,6 +790,9 @@ void setup_battery(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, "BMW iX and i4-7 platform", 63);
datalayer.system.info.battery_protocol[63] = '\0';
//Reset Battery at bootup
transmit_can_frame(&BMWiX_6F4_REQUEST_HARD_RESET, can_config.battery);
//Before we have started up and detected which battery is in use, use 108S values
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;

View file

@ -15,7 +15,7 @@
#define MAX_CHARGE_POWER_WHEN_TOPBALANCING_W 500
#define RAMPDOWN_SOC 9000 // (90.00) SOC% to start ramping down from max charge power towards 0 at 100.00%
#define STALE_PERIOD_CONFIG \
300000; //Number of milliseconds before critical values are classed as stale/stuck 300000 = 300 seconds
400000; //Number of milliseconds before critical values are classed as stale/stuck 300000 = 400 seconds
void setup_battery(void);
void transmit_can_frame(CAN_frame* tx_frame, int interface);

View file

@ -7,7 +7,7 @@
#define MAX_PACK_VOLTAGE_DV 4150 //5000 = 500.0V
#define MIN_PACK_VOLTAGE_DV 2500
#define MAX_CELL_DEVIATION_MV 500
#define MAX_CELL_DEVIATION_MV 150
#define MAX_CELL_VOLTAGE_MV 4250 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value

View file

@ -36,6 +36,9 @@ static int16_t BMS_highest_cell_temperature = 0;
static int16_t BMS_average_cell_temperature = 0;
static uint16_t BMS_lowest_cell_voltage_mV = 3300;
static uint16_t BMS_highest_cell_voltage_mV = 3300;
static uint8_t battery_frame_index = 0;
#define NOF_CELLS 126
static uint16_t battery_cellvoltages[NOF_CELLS] = {0};
#ifdef DOUBLE_BATTERY
static int16_t battery2_temperature_ambient = 0;
static int16_t battery2_daughterboard_temperatures[10];
@ -52,6 +55,8 @@ static int16_t BMS2_highest_cell_temperature = 0;
static int16_t BMS2_average_cell_temperature = 0;
static uint16_t BMS2_lowest_cell_voltage_mV = 3300;
static uint16_t BMS2_highest_cell_voltage_mV = 3300;
static uint8_t battery2_frame_index = 0;
static uint16_t battery2_cellvoltages[NOF_CELLS] = {0};
#endif //DOUBLE_BATTERY
#define POLL_FOR_BATTERY_SOC 0x05
#define POLL_FOR_BATTERY_VOLTAGE 0x08
@ -135,9 +140,38 @@ void update_values_battery() { //This function maps all the values fetched via
datalayer.battery.status.cell_min_voltage_mV = BMS_lowest_cell_voltage_mV;
datalayer.battery.status.temperature_min_dC = BMS_lowest_cell_temperature * 10; // Add decimals
//Map all cell voltages to the global array
memcpy(datalayer.battery.status.cell_voltages_mV, battery_cellvoltages, NOF_CELLS * sizeof(uint16_t));
#ifdef SKIP_TEMPERATURE_SENSOR_NUMBER
// Initialize min and max variables for temperature calculation
battery_calc_min_temperature = battery_daughterboard_temperatures[0];
battery_calc_max_temperature = battery_daughterboard_temperatures[0];
// Loop through the array of 10x daughterboard temps to find the smallest and largest values
// Note, it is possible for user to skip using a faulty sensor in the .h file
if (SKIP_TEMPERATURE_SENSOR_NUMBER == 1) { //If sensor 1 is skipped, init minmax to sensor 2
battery_calc_min_temperature = battery_daughterboard_temperatures[1];
battery_calc_max_temperature = battery_daughterboard_temperatures[1];
}
for (int i = 1; i < 10; i++) {
if (i == (SKIP_TEMPERATURE_SENSOR_NUMBER - 1)) {
i++;
}
if (battery_daughterboard_temperatures[i] < battery_calc_min_temperature) {
battery_calc_min_temperature = battery_daughterboard_temperatures[i];
}
if (battery_daughterboard_temperatures[i] > battery_calc_max_temperature) {
battery_calc_max_temperature = battery_daughterboard_temperatures[i];
}
}
//Write the result to datalayer
datalayer.battery.status.temperature_min_dC = battery_calc_min_temperature * 10;
datalayer.battery.status.temperature_max_dC = battery_calc_max_temperature * 10;
#else //User does not need filtering out a broken sensor, just use the min-max the BMS sends
datalayer.battery.status.temperature_min_dC = BMS_lowest_cell_temperature * 10;
datalayer.battery.status.temperature_max_dC = BMS_highest_cell_temperature * 10;
#endif //!SKIP_TEMPERATURE_SENSOR_NUMBER
// Update webserver datalayer
datalayer_extended.bydAtto3.SOC_method = SOC_method;
@ -147,6 +181,16 @@ void update_values_battery() { //This function maps all the values fetched via
datalayer_extended.bydAtto3.SOC_polled = BMS_SOC;
datalayer_extended.bydAtto3.voltage_periodic = battery_voltage;
datalayer_extended.bydAtto3.voltage_polled = BMS_voltage;
datalayer_extended.bydAtto3.battery_temperatures[0] = battery_daughterboard_temperatures[0];
datalayer_extended.bydAtto3.battery_temperatures[1] = battery_daughterboard_temperatures[1];
datalayer_extended.bydAtto3.battery_temperatures[2] = battery_daughterboard_temperatures[2];
datalayer_extended.bydAtto3.battery_temperatures[3] = battery_daughterboard_temperatures[3];
datalayer_extended.bydAtto3.battery_temperatures[4] = battery_daughterboard_temperatures[4];
datalayer_extended.bydAtto3.battery_temperatures[5] = battery_daughterboard_temperatures[5];
datalayer_extended.bydAtto3.battery_temperatures[6] = battery_daughterboard_temperatures[6];
datalayer_extended.bydAtto3.battery_temperatures[7] = battery_daughterboard_temperatures[7];
datalayer_extended.bydAtto3.battery_temperatures[8] = battery_daughterboard_temperatures[8];
datalayer_extended.bydAtto3.battery_temperatures[9] = battery_daughterboard_temperatures[9];
}
void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
@ -217,6 +261,15 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
break;
case 0x43D:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
battery_frame_index = rx_frame.data.u8[0];
if (battery_frame_index < (NOF_CELLS / 3)) {
uint8_t base_index = battery_frame_index * 3;
for (uint8_t i = 0; i < 3; i++) {
battery_cellvoltages[base_index + i] =
(((rx_frame.data.u8[2 * (i + 1)] & 0x0F) << 8) | rx_frame.data.u8[2 * i + 1]);
}
}
break;
case 0x444:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
@ -443,6 +496,9 @@ void update_values_battery2() { //This function maps all the values fetched via
datalayer.battery2.status.temperature_min_dC = BMS2_lowest_cell_temperature * 10; // Add decimals
datalayer.battery2.status.temperature_max_dC = BMS2_highest_cell_temperature * 10;
//Map all cell voltages to the global array
memcpy(datalayer.battery2.status.cell_voltages_mV, battery2_cellvoltages, NOF_CELLS * sizeof(uint16_t));
}
void handle_incoming_can_frame_battery2(CAN_frame rx_frame) {
@ -511,6 +567,14 @@ void handle_incoming_can_frame_battery2(CAN_frame rx_frame) {
break;
case 0x43D:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
battery2_frame_index = rx_frame.data.u8[0];
if (battery2_frame_index < (NOF_CELLS / 3)) {
uint8_t base2_index = battery2_frame_index * 3;
for (uint8_t i = 0; i < 3; i++) {
battery2_cellvoltages[base2_index + i] =
(((rx_frame.data.u8[2 * (i + 1)] & 0x0F) << 8) | rx_frame.data.u8[2 * i + 1]);
}
}
break;
case 0x444:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;

View file

@ -8,6 +8,10 @@
#define MAXPOWER_CHARGE_W 10000
#define MAXPOWER_DISCHARGE_W 10000
//Uncomment and configure this line, if you want to filter out a broken temperature sensor (1-10)
//Make sure you understand risks associated with disabling. Values can be read via "More Battery info"
//#define SKIP_TEMPERATURE_SENSOR_NUMBER 1
/* Do not modify the rows below */
#define BATTERY_SELECTED
#define MAX_PACK_VOLTAGE_DV 4410 //5000 = 500.0V

View file

@ -0,0 +1,206 @@
#include "DALY-BMS.h"
#include <cstdint>
#include "../include.h"
#include "RJXZS-BMS.h"
#ifdef DALY_BMS
#include "../datalayer/datalayer.h"
#include "../devboard/utils/events.h"
#include "RENAULT-TWIZY.h"
/* Do not change code below unless you are sure what you are doing */
static uint32_t lastPacket = 0;
static int16_t temperature_min_dC = 0;
static int16_t temperature_max_dC = 0;
static int16_t current_dA = 0;
static uint16_t voltage_dV = 0;
static uint32_t remaining_capacity_mAh = 0;
static uint16_t cellvoltages_mV[48] = {0};
static uint16_t cellvoltage_min_mV = 0;
static uint16_t cellvoltage_max_mV = 0;
static uint16_t SOC = 0;
static bool has_fault = false;
void update_values_battery() {
datalayer.battery.status.real_soc = SOC;
datalayer.battery.status.voltage_dV = voltage_dV; //value is *10 (3700 = 370.0)
datalayer.battery.status.current_dA = current_dA; //value is *10 (150 = 15.0)
datalayer.battery.status.remaining_capacity_Wh = (remaining_capacity_mAh * (uint32_t)voltage_dV) / 10000;
datalayer.battery.status.max_charge_power_W = (BATTERY_MAX_CHARGE_AMP * voltage_dV) / 100;
datalayer.battery.status.max_discharge_power_W = (BATTERY_MAX_DISCHARGE_AMP * voltage_dV) / 100;
// limit power when SoC is low or high
uint32_t adaptive_power_limit = 999999;
if (SOC < 2000)
adaptive_power_limit = ((uint32_t)SOC * POWER_PER_PERCENT) / 100;
else if (SOC > 8000)
adaptive_power_limit = ((10000 - (uint32_t)SOC) * POWER_PER_PERCENT) / 100;
if (adaptive_power_limit < datalayer.battery.status.max_charge_power_W)
datalayer.battery.status.max_charge_power_W = adaptive_power_limit;
if (SOC < 2000 && adaptive_power_limit < datalayer.battery.status.max_discharge_power_W)
datalayer.battery.status.max_discharge_power_W = adaptive_power_limit;
// always allow to charge at least a little bit
if (datalayer.battery.status.max_charge_power_W < POWER_PER_PERCENT)
datalayer.battery.status.max_charge_power_W = POWER_PER_PERCENT;
memcpy(datalayer.battery.status.cell_voltages_mV, cellvoltages_mV, sizeof(cellvoltages_mV));
datalayer.battery.status.cell_min_voltage_mV = cellvoltage_min_mV;
datalayer.battery.status.cell_max_voltage_mV = cellvoltage_max_mV;
datalayer.battery.status.temperature_min_dC = temperature_min_dC;
datalayer.battery.status.temperature_max_dC = temperature_max_dC;
datalayer.battery.status.real_bms_status = has_fault ? BMS_FAULT : BMS_ACTIVE;
}
void setup_battery(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, "DALY RS485", 63);
datalayer.system.info.battery_protocol[63] = '\0';
datalayer.battery.info.number_of_cells = CELL_COUNT;
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
datalayer.battery.info.total_capacity_Wh = BATTERY_WH_MAX;
}
uint8_t calculate_checksum(uint8_t buff[12]) {
uint8_t check = 0;
for (uint8_t i = 0; i < 12; i++) {
check += buff[i];
}
return check;
}
uint16_t decode_uint16be(uint8_t data[8], uint8_t offset) {
uint16_t upper = data[offset];
uint16_t lower = data[offset + 1];
return (upper << 8) | lower;
}
int16_t decode_int16be(uint8_t data[8], uint8_t offset) {
int16_t upper = data[offset];
int16_t lower = data[offset + 1];
return (upper << 8) | lower;
}
uint32_t decode_uint32be(uint8_t data[8], uint8_t offset) {
return (((uint32_t)data[offset]) << 24) | (((uint32_t)data[offset + 1]) << 16) | (((uint32_t)data[offset + 2]) << 8) |
((uint32_t)data[offset + 3]);
}
#ifdef DEBUG_VIA_USB
void dump_buff(const char* msg, uint8_t* buff, uint8_t len) {
Serial.print("[DALY-BMS] ");
Serial.print(msg);
for (int i = 0; i < len; i++) {
Serial.print(buff[i] >> 4, HEX);
Serial.print(buff[i] & 0xf, HEX);
Serial.print(" ");
}
Serial.println();
}
#endif
void decode_packet(uint8_t command, uint8_t data[8]) {
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
switch (command) {
case 0x90:
voltage_dV = decode_uint16be(data, 0);
current_dA = decode_int16be(data, 4) - 30000;
SOC = decode_uint16be(data, 6) * 10;
break;
case 0x91:
cellvoltage_max_mV = decode_uint16be(data, 0);
cellvoltage_min_mV = decode_uint16be(data, 3);
break;
case 0x92:
temperature_max_dC = (data[0] - 40) * 10;
temperature_min_dC = (data[2] - 40) * 10;
break;
case 0x93:
remaining_capacity_mAh = decode_uint32be(data, 4);
break;
case 0x94:
break;
case 0x95:
if (data[0] > 0 && data[0] <= 16) {
uint8_t frame_index = (data[0] - 1) * 3;
cellvoltages_mV[frame_index + 0] = decode_uint16be(data, 1);
cellvoltages_mV[frame_index + 1] = decode_uint16be(data, 3);
cellvoltages_mV[frame_index + 2] = decode_uint16be(data, 5);
}
break;
case 0x96:
break;
case 0x97:
break;
case 0x98:
// for now we do not handle individual faults. All of them are 0 when ok, and 1 when a fault occurs
has_fault = false;
for (int i = 0; i < 8; i++) {
if (data[i] != 0x00) {
has_fault = true;
}
}
break;
}
}
void transmit_rs485() {
static uint8_t nextCommand = 0x90;
if (millis() - lastPacket > 60) {
uint8_t tx_buff[13] = {0};
tx_buff[0] = 0xA5;
tx_buff[1] = 0x40;
tx_buff[2] = nextCommand;
tx_buff[3] = 8;
tx_buff[12] = calculate_checksum(tx_buff);
#ifdef DEBUG_VIA_USB
dump_buff("transmitting: ", tx_buff, 13);
#endif
Serial2.write(tx_buff, 13);
lastPacket = millis();
nextCommand++;
if (nextCommand > 0x98)
nextCommand = 0x90;
}
}
void receive_RS485() {
static uint8_t recv_buff[13] = {0};
static uint8_t recv_len = 0;
while (Serial2.available()) {
recv_buff[recv_len] = Serial2.read();
recv_len++;
if (recv_len > 0 && recv_buff[0] != 0xA5 || recv_len > 1 && recv_buff[1] != 0x01 ||
recv_len > 2 && (recv_buff[2] < 0x90 || recv_buff[2] > 0x98) || recv_len > 3 && recv_buff[3] != 8 ||
recv_len > 12 && recv_buff[12] != calculate_checksum(recv_buff)) {
#ifdef DEBUG_VIA_USB
dump_buff("dropping partial rx: ", recv_buff, recv_len);
#endif
recv_len = 0;
}
if (recv_len > 12) {
#ifdef DEBUG_VIA_USB
dump_buff("decoding successfull rx: ", recv_buff, recv_len);
#endif
decode_packet(recv_buff[2], &recv_buff[4]);
recv_len = 0;
lastPacket = millis();
}
}
}
#endif

View file

@ -0,0 +1,17 @@
#ifndef DALY_BMS_H
#define DALY_BMS_H
/* Tweak these according to your battery build */
#define CELL_COUNT 14
#define MAX_PACK_VOLTAGE_DV 588 //588 = 58.8V
#define MIN_PACK_VOLTAGE_DV 518 //518 = 51.8V
#define MAX_CELL_VOLTAGE_MV 4250 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value
#define POWER_PER_PERCENT 50 // below 20% and above 80% limit power to 50W * SOC (i.e. 150W at 3%, 500W at 10%, ...)
/* Do not modify any rows below*/
#define BATTERY_SELECTED
#define RS485_BATTERY_SELECTED
#define RS485_BAUDRATE 9600
#endif

View file

@ -6,7 +6,7 @@
#define BATTERY_SELECTED
#define MAX_PACK_VOLTAGE_DV 3696 //5000 = 500.0V
#define MIN_PACK_VOLTAGE_DV 3160
#define MAX_CELL_DEVIATION_MV 500
#define MAX_CELL_DEVIATION_MV 250
#define MAX_CELL_VOLTAGE_MV 4150 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_MV 2750 //Battery is put into emergency stop if one cell goes below this value

View file

@ -5,7 +5,7 @@
#define BATTERY_SELECTED
#define MAX_PACK_VOLTAGE_DV 4546 //5000 = 500.0V
#define MIN_PACK_VOLTAGE_DV 3370
#define MAX_CELL_DEVIATION_MV 500
#define MAX_CELL_DEVIATION_MV 250
#define MAX_CELL_VOLTAGE_MV 4250 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value

View file

@ -40,6 +40,7 @@ static int8_t powerRelayTemperature = 0;
static bool startedUp = false;
#ifdef DOUBLE_BATTERY
static uint8_t counter_200_2 = 0;
static uint16_t battery2_soc_calculated = 0;
static uint16_t battery2_SOC_BMS = 0;
static uint16_t battery2_SOC_Display = 0;
@ -69,23 +70,28 @@ static int8_t battery2_temperature_water_inlet = 0;
static int8_t battery2_heatertemp = 0;
static int8_t battery2_powerRelayTemperature = 0;
static bool battery2_startedUp = false;
#endif //DOUBLE_BATTERY
CAN_frame KIA_HYUNDAI_200_2 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x200,
.data = {0x00, 0x80, 0xD8, 0x04, 0x00, 0x17, 0xD0, 0x00}}; //2nd battery
#endif //DOUBLE_BATTERY
CAN_frame KIA_HYUNDAI_200 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x200,
.data = {0x00, 0x80, 0xD8, 0x04, 0x00, 0x17, 0xD0, 0x00}}; //Mid log value
.data = {0x00, 0x80, 0xD8, 0x04, 0x00, 0x17, 0xD0, 0x00}};
CAN_frame KIA_HYUNDAI_523 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x523,
.data = {0x08, 0x38, 0x36, 0x36, 0x33, 0x34, 0x00, 0x01}}; //Mid log value
.data = {0x08, 0x38, 0x36, 0x36, 0x33, 0x34, 0x00, 0x01}};
CAN_frame KIA_HYUNDAI_524 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x524,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; //Initial value
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
//553 Needed frame 200ms
CAN_frame KIA64_553 = {.FD = false,
.ext_ID = false,
@ -724,7 +730,7 @@ void handle_incoming_can_frame_battery2(CAN_frame rx_frame) {
case 0x10: //"PID Header"
if (rx_frame.data.u8[4] == battery2_poll_data_pid) {
transmit_can_frame(&KIA64_7E4_ack,
can_config.battery); //Send ack to BMS if the same frame is sent as polled
can_config.battery_double); //Send ack to BMS if the same frame is sent as polled
}
break;
case 0x21: //First frame in PID group
@ -997,8 +1003,49 @@ void transmit_can_battery() {
transmit_can_frame(&KIA_HYUNDAI_524, can_config.battery);
#ifdef DOUBLE_BATTERY
if (battery2_startedUp && datalayer.system.status.battery2_allows_contactor_closing) {
transmit_can_frame(&KIA_HYUNDAI_200, can_config.battery_double);
switch (counter_200_2) {
case 0:
KIA_HYUNDAI_200_2.data.u8[5] = 0x17;
++counter_200_2;
break;
case 1:
KIA_HYUNDAI_200_2.data.u8[5] = 0x57;
++counter_200_2;
break;
case 2:
KIA_HYUNDAI_200_2.data.u8[5] = 0x97;
++counter_200_2;
break;
case 3:
KIA_HYUNDAI_200_2.data.u8[5] = 0xD7;
++counter_200_2;
break;
case 4:
KIA_HYUNDAI_200_2.data.u8[3] = 0x10;
KIA_HYUNDAI_200_2.data.u8[5] = 0xFF;
++counter_200_2;
break;
case 5:
KIA_HYUNDAI_200_2.data.u8[5] = 0x3B;
++counter_200_2;
break;
case 6:
KIA_HYUNDAI_200_2.data.u8[5] = 0x7B;
++counter_200_2;
break;
case 7:
KIA_HYUNDAI_200_2.data.u8[5] = 0xBB;
++counter_200_2;
break;
case 8:
KIA_HYUNDAI_200_2.data.u8[5] = 0xFB;
counter_200_2 = 5;
break;
}
transmit_can_frame(&KIA_HYUNDAI_200_2, can_config.battery_double);
transmit_can_frame(&KIA_HYUNDAI_523, can_config.battery_double);

View file

@ -51,11 +51,6 @@ static uint16_t battery_allowed_charge_power = 0;
static uint16_t battery_allowed_discharge_power = 0;
static uint16_t cellvoltages_polled[108];
static uint16_t tempval = 0;
static uint8_t BMS_5A2_CRC = 0;
static uint8_t BMS_5CA_CRC = 0;
static uint8_t BMS_0CF_CRC = 0;
static uint8_t BMS_578_CRC = 0;
static uint8_t BMS_0C0_CRC = 0;
static uint8_t BMS_16A954A6_CRC = 0;
static uint8_t BMS_5A2_counter = 0;
static uint8_t BMS_5CA_counter = 0;
@ -386,7 +381,7 @@ uint32_t can_msg_received = 0;
* @see https://www.autosar.org/fileadmin/user_upload/standards/classic/4-3/AUTOSAR_SWS_CRCLibrary.pdf
* @see https://web.archive.org/web/20221105210302/https://www.autosar.org/fileadmin/user_upload/standards/classic/4-3/AUTOSAR_SWS_CRCLibrary.pdf
*/
uint8_t vw_crc_calc(uint8_t* inputBytes, uint8_t length, uint16_t address) {
uint8_t vw_crc_calc(uint8_t* inputBytes, uint8_t length, uint32_t address) {
const uint8_t poly = 0x2F;
const uint8_t xor_output = 0xFF;
@ -395,6 +390,8 @@ uint8_t vw_crc_calc(uint8_t* inputBytes, uint8_t length, uint16_t address) {
0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40};
const uint8_t MB00C0[16] = {0x2f, 0x44, 0x72, 0xd3, 0x07, 0xf2, 0x39, 0x09,
0x8d, 0x6f, 0x57, 0x20, 0x37, 0xf9, 0x9b, 0xfa};
const uint8_t MB00CF[16] = {0xee, 0x80, 0x6e, 0x4e, 0x29, 0xc6, 0x92, 0xc0,
0x65, 0xaa, 0x3a, 0xa1, 0x8f, 0xcd, 0xe6, 0x90};
const uint8_t MB00FC[16] = {0x77, 0x5c, 0xa0, 0x89, 0x4b, 0x7c, 0xbb, 0xd6,
0x1f, 0x6c, 0x4f, 0xf6, 0x20, 0x2b, 0x43, 0xdd};
const uint8_t MB00FD[16] = {0xb4, 0xef, 0xf8, 0x49, 0x1e, 0xe5, 0xc2, 0xc0,
@ -423,6 +420,8 @@ uint8_t vw_crc_calc(uint8_t* inputBytes, uint8_t length, uint16_t address) {
0x1e, 0x0d, 0x24, 0xcd, 0x8c, 0xa6, 0x2f, 0x41};
const uint8_t MB0578[16] = {0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48,
0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48};
const uint8_t MB05A2[16] = {0xeb, 0x4c, 0x44, 0xaf, 0x21, 0x8d, 0x01, 0x58,
0xfa, 0x93, 0xdb, 0x89, 0x15, 0x10, 0x4a, 0x61};
const uint8_t MB05CA[16] = {0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43,
0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43};
const uint8_t MB0641[16] = {0x47, 0x47, 0x47, 0x47, 0x47, 0x47, 0x47, 0x47,
@ -445,6 +444,9 @@ uint8_t vw_crc_calc(uint8_t* inputBytes, uint8_t length, uint16_t address) {
case 0x00C0: //
magicByte = MB00C0[counter];
break;
case 0x00CF: //BMS
magicByte = MB00CF[counter];
break;
case 0x00FC:
magicByte = MB00FC[counter];
break;
@ -487,6 +489,9 @@ uint8_t vw_crc_calc(uint8_t* inputBytes, uint8_t length, uint16_t address) {
case 0x0578: // BMS DC
magicByte = MB0578[counter];
break;
case 0x05A2: // BMS
magicByte = MB05A2[counter];
break;
case 0x05CA: // BMS
magicByte = MB05CA[counter];
break;
@ -503,7 +508,9 @@ uint8_t vw_crc_calc(uint8_t* inputBytes, uint8_t length, uint16_t address) {
magicByte = MB16A954A6[counter];
break;
default: // this won't lead to correct CRC checksums
logging.println("Checksum request uknown");
#ifdef DEBUG_LOG
logging.println("Checksum request unknown");
#endif
magicByte = 0x00;
break;
}
@ -625,6 +632,26 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
#endif
first_can_msg = last_can_msg_timestamp;
}
/* CRC check on messages with CRC */
switch (rx_frame.ID) {
case 0x0CF:
case 0x578:
case 0x5A2:
case 0x5CA:
case 0x16A954A6:
if (rx_frame.data.u8[0] !=
vw_crc_calc(rx_frame.data.u8, rx_frame.DLC, rx_frame.ID)) { //If CRC does not match calc
datalayer.battery.status.CAN_error_counter++;
#ifdef DEBUG_LOG
logging.printf("MEB: Msg 0x%04X CRC error\n", rx_frame.ID);
#endif
return;
}
default:
break;
}
switch (rx_frame.ID) {
case 0x17F0007B: // BMS 500ms
can_msg_received |= RX_0x17F0007B;
@ -707,7 +734,6 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
break;
case 0x16A954A6: // BMS
can_msg_received |= RX_0x16A954A6;
BMS_16A954A6_CRC = rx_frame.data.u8[0]; // Can be used to check CAN signal integrity later on
BMS_16A954A6_counter = (rx_frame.data.u8[1] & 0x0F); // Can be used to check CAN signal integrity later on
isolation_fault = (rx_frame.data.u8[2] & 0xE0) >> 5;
isolation_status = (rx_frame.data.u8[2] & 0x1E) >> 1;
@ -944,7 +970,6 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
energy_extracted_from_battery = ((rx_frame.data.u8[7] & 0x7F) << 8) | rx_frame.data.u8[6];
break;
case 0x578: // BMS 100ms
BMS_578_CRC = rx_frame.data.u8[0]; // Can be used to check CAN signal integrity later on
BMS_578_counter = (rx_frame.data.u8[1] & 0x0F); // Can be used to check CAN signal integrity later on
BMS_Status_DCLS = ((rx_frame.data.u8[1] & 0x30) >> 4);
DC_voltage_DCLS = (rx_frame.data.u8[2] << 6) | (rx_frame.data.u8[1] >> 6);
@ -953,7 +978,6 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
break;
case 0x5A2: // BMS 500ms normal, 100ms fast
can_msg_received |= RX_0x5A2;
BMS_5A2_CRC = rx_frame.data.u8[0]; // Can be used to check CAN signal integrity later on
BMS_5A2_counter = (rx_frame.data.u8[1] & 0x0F); // Can be used to check CAN signal integrity later on
service_disconnect_switch_missing = (rx_frame.data.u8[1] & 0x20) >> 5;
pilotline_open = (rx_frame.data.u8[1] & 0x10) >> 4;
@ -969,10 +993,9 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
break;
case 0x5CA: // BMS 500ms
can_msg_received |= RX_0x5CA;
BMS_5CA_CRC = rx_frame.data.u8[0]; // Can be used to check CAN signal integrity later on
BMS_5CA_counter = (rx_frame.data.u8[1] & 0x0F); // Can be used to check CAN signal integrity later on
balancing_request = (rx_frame.data.u8[5] & 0x08) >>
3; // BMS requests a low current end charge to support balancing, maybe unused.
BMS_5CA_counter = (rx_frame.data.u8[1] & 0x0F);
balancing_request = (rx_frame.data.u8[5] & 0x08) >> 3;
// balancing_request: BMS requests a low current end charge to support balancing, maybe unused.
battery_diagnostic = (rx_frame.data.u8[3] & 0x07);
battery_Wh_left =
(rx_frame.data.u8[2] << 4) | (rx_frame.data.u8[1] >> 4); //*50 ! Not usable, seems to always contain 0x7F0
@ -985,8 +1008,7 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
break;
case 0x0CF: //BMS 10ms
can_msg_received |= RX_0x0CF;
BMS_0CF_CRC = rx_frame.data.u8[0]; // Can be used to check CAN signal integrity later on
BMS_0CF_counter = (rx_frame.data.u8[1] & 0x0F); // Can be used to check CAN signal integrity later on
BMS_0CF_counter = (rx_frame.data.u8[1] & 0x0F);
BMS_welded_contactors_status = (rx_frame.data.u8[1] & 0x60) >> 5;
BMS_ext_limits_active = (rx_frame.data.u8[1] & 0x80) >> 7;
BMS_mode = (rx_frame.data.u8[2] & 0x07);

View file

@ -202,7 +202,11 @@ void update_values_battery() { /* This function maps all the values fetched via
datalayer.battery.status.current_dA =
(battery_Current2 * 5); //0.5A/bit, multiply by 5 to get Amp+1decimal (5,5A = 11)
datalayer.battery.info.total_capacity_Wh = (battery_Max_GIDS * WH_PER_GID);
if (battery_Max_GIDS == 273) { //battery_Max_GIDS is stuck at 273 on ZE0
datalayer.battery.info.total_capacity_Wh = ((battery_Max_GIDS * WH_PER_GID * battery_StateOfHealth) / 100);
} else { //battery_Max_GIDS updates on newer generations, making for a total_capacity_Wh value that makes sense
datalayer.battery.info.total_capacity_Wh = (battery_Max_GIDS * WH_PER_GID);
}
datalayer.battery.status.remaining_capacity_Wh = battery_Wh_Remaining;
@ -381,7 +385,11 @@ void update_values_battery2() { // Handle the values coming in from battery #2
datalayer.battery2.status.current_dA =
(battery2_Current2 * 5); //0.5A/bit, multiply by 5 to get Amp+1decimal (5,5A = 11)
datalayer.battery2.info.total_capacity_Wh = (battery2_Max_GIDS * WH_PER_GID);
if (battery2_Max_GIDS == 273) { //battery2_Max_GIDS is stuck at 273 on 24kWh packs
datalayer.battery2.info.total_capacity_Wh = ((battery2_Max_GIDS * WH_PER_GID * battery2_StateOfHealth) / 100);
} else { //battery_Max_GIDS updates on newer generations, making for a total_capacity_Wh value that makes sense
datalayer.battery2.info.total_capacity_Wh = (battery2_Max_GIDS * WH_PER_GID);
}
datalayer.battery2.status.remaining_capacity_Wh = battery2_Wh_Remaining;

View file

@ -6,7 +6,7 @@
#define BATTERY_SELECTED
#define MAX_PACK_VOLTAGE_DV 4040 //5000 = 500.0V
#define MIN_PACK_VOLTAGE_DV 2600
#define MAX_CELL_DEVIATION_MV 500
#define MAX_CELL_DEVIATION_MV 150
#define MAX_CELL_VOLTAGE_MV 4250 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value

View file

@ -0,0 +1,151 @@
#include "../include.h"
#ifdef ORION_BMS
#include "../datalayer/datalayer.h"
#include "../devboard/utils/events.h"
#include "ORION-BMS.h"
/* Do not change code below unless you are sure what you are doing */
static uint16_t cellvoltages[MAX_AMOUNT_CELLS]; //array with all the cellvoltages
static uint16_t Maximum_Cell_Voltage = 3700;
static uint16_t Minimum_Cell_Voltage = 3700;
static uint16_t Pack_Health = 99;
static int16_t Pack_Current = 0;
static int16_t Average_Temperature = 0;
static uint16_t Pack_Summed_Voltage = 0;
static int16_t Average_Current = 0;
static uint16_t High_Temperature = 0;
static uint16_t Pack_SOC_ppt = 0;
static uint16_t Pack_CCL = 0; //Charge current limit (A)
static uint16_t Pack_DCL = 0; //Discharge current limit (A)
static uint16_t Maximum_Pack_Voltage = 0;
static uint16_t Minimum_Pack_Voltage = 0;
static uint16_t CellID = 0;
static uint16_t CellVoltage = 0;
static uint16_t CellResistance = 0;
static uint16_t CellOpenVoltage = 0;
static uint16_t Checksum = 0;
static uint16_t CellBalancing = 0;
static uint8_t amount_of_detected_cells = 0;
void findMinMaxCellvoltages(const uint16_t arr[], size_t size, uint16_t& Minimum_Cell_Voltage,
uint16_t& Maximum_Cell_Voltage) {
Minimum_Cell_Voltage = std::numeric_limits<uint16_t>::max();
Maximum_Cell_Voltage = 0;
bool foundValidValue = false;
for (size_t i = 0; i < size; ++i) {
if (arr[i] != 0) { // Skip zero values
if (arr[i] < Minimum_Cell_Voltage)
Minimum_Cell_Voltage = arr[i];
if (arr[i] > Maximum_Cell_Voltage)
Maximum_Cell_Voltage = arr[i];
foundValidValue = true;
}
}
// If all values were zero, set min and max to 3700
if (!foundValidValue) {
Minimum_Cell_Voltage = 3700;
Maximum_Cell_Voltage = 3700;
}
}
void update_values_battery() {
datalayer.battery.status.real_soc = Pack_SOC_ppt * 10;
datalayer.battery.status.soh_pptt = Pack_Health * 100;
datalayer.battery.status.voltage_dV = (Pack_Summed_Voltage / 10);
datalayer.battery.status.current_dA = Average_Current;
datalayer.battery.status.max_charge_power_W = (Pack_CCL * datalayer.battery.status.voltage_dV) / 100;
datalayer.battery.status.max_discharge_power_W = (Pack_DCL * datalayer.battery.status.voltage_dV) / 100;
datalayer.battery.status.remaining_capacity_Wh = static_cast<uint32_t>(
(static_cast<double>(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh);
datalayer.battery.status.temperature_min_dC = (High_Temperature - 10);
datalayer.battery.status.temperature_max_dC = High_Temperature;
//Map all cell voltages to the global array
memcpy(datalayer.battery.status.cell_voltages_mV, cellvoltages, MAX_AMOUNT_CELLS * sizeof(uint16_t));
//Find min and max cellvoltage from the array
findMinMaxCellvoltages(cellvoltages, MAX_AMOUNT_CELLS, Minimum_Cell_Voltage, Maximum_Cell_Voltage);
datalayer.battery.status.cell_max_voltage_mV = Maximum_Cell_Voltage;
datalayer.battery.status.cell_min_voltage_mV = Minimum_Cell_Voltage;
//If user did not configure amount of cells correctly in the header file, update the value
if ((amount_of_detected_cells > NUMBER_OF_CELLS) && (amount_of_detected_cells < MAX_AMOUNT_CELLS)) {
datalayer.battery.info.number_of_cells = amount_of_detected_cells;
}
}
void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
switch (rx_frame.ID) {
case 0x356:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
Pack_Summed_Voltage = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0];
Average_Current = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2];
High_Temperature = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4];
break;
case 0x351:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
Maximum_Pack_Voltage = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0];
Pack_CCL = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2];
Pack_DCL = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4];
Minimum_Pack_Voltage = (rx_frame.data.u8[7] << 8) | rx_frame.data.u8[6];
break;
case 0x355:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
Pack_Health = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2];
Pack_SOC_ppt = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4];
break;
case 0x35A:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x36:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
CellID = rx_frame.data.u8[0];
CellBalancing = (rx_frame.data.u8[3] & 0x80) >> 7;
CellVoltage = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
CellResistance = ((rx_frame.data.u8[3] & 0x7F) << 8) | rx_frame.data.u8[4];
CellOpenVoltage = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
Checksum = rx_frame.data.u8[7]; //Value = (0x36 + 8 + byte0 + byte1 + ... + byte6) & 0xFF
if (CellID >= MAX_AMOUNT_CELLS) {
CellID = MAX_AMOUNT_CELLS;
}
cellvoltages[CellID] = (CellVoltage / 10);
if (CellID > amount_of_detected_cells) {
amount_of_detected_cells = CellID;
}
break;
default:
break;
}
}
void transmit_can_battery() {
unsigned long currentMillis = millis();
// No transmission needed for this integration
}
void setup_battery(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, "DIY battery with Orion BMS (Victron setting)", 63);
datalayer.system.info.battery_protocol[63] = '\0';
datalayer.battery.info.number_of_cells = NUMBER_OF_CELLS;
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
datalayer.system.status.battery_allows_contactor_closing = true;
}
#endif

View file

@ -0,0 +1,19 @@
#ifndef ORION_BMS_H
#define ORION_BMS_H
#include <Arduino.h>
#include "../include.h"
#define BATTERY_SELECTED
/* Change the following to suit your battery */
#define NUMBER_OF_CELLS 96
#define MAX_PACK_VOLTAGE_DV 5000 //5000 = 500.0V
#define MIN_PACK_VOLTAGE_DV 1500
#define MAX_CELL_VOLTAGE_MV 4250 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value
#define MAX_CELL_DEVIATION_MV 150
void setup_battery(void);
void transmit_can_frame(CAN_frame* tx_frame, int interface);
#endif

View file

@ -10,7 +10,7 @@
#define MIN_PACK_VOLTAGE_DV 1500
#define MAX_CELL_VOLTAGE_MV 4250 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value
#define MAX_CELL_DEVIATION_MV 500
#define MAX_CELL_DEVIATION_MV 150
void setup_battery(void);
void transmit_can_frame(CAN_frame* tx_frame, int interface);

View file

@ -6,11 +6,11 @@
#define BATTERY_SELECTED
/* Change the following to suit your battery */
#define MAX_PACK_VOLTAGE_DV 5000 //TODO: Configure
#define MIN_PACK_VOLTAGE_DV 0 //TODO: Configure
#define MAX_CELL_VOLTAGE_MV 4250 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value
#define MAX_CELL_DEVIATION_MV 500 //TODO: Configure
#define MAX_PACK_VOLTAGE_DV 5000 //TODO: Configure
#define MIN_PACK_VOLTAGE_DV 0 //TODO: Configure
#define MAX_CELL_VOLTAGE_MV 4250 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value
#define MAX_CELL_DEVIATION_MV 150
void setup_battery(void);
void transmit_can_frame(CAN_frame* tx_frame, int interface);

View file

@ -6,7 +6,7 @@
#define BATTERY_SELECTED
#define MAX_PACK_VOLTAGE_DV 4150 //5000 = 500.0V
#define MIN_PACK_VOLTAGE_DV 2500
#define MAX_CELL_DEVIATION_MV 500
#define MAX_CELL_DEVIATION_MV 150
#define MAX_CELL_VOLTAGE_MV 4250 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value
#define MAX_CHARGE_POWER_W 5000 // Battery can be charged with this amount of power

View file

@ -5,7 +5,7 @@
#define BATTERY_SELECTED
#define MAX_PACK_VOLTAGE_DV 579 // 57.9V at 100% SOC (with 70% SOH, new one might be higher)
#define MIN_PACK_VOLTAGE_DV 480 // 48.4V at 13.76% SOC
#define MAX_CELL_DEVIATION_MV 500
#define MAX_CELL_DEVIATION_MV 150
#define MAX_CELL_VOLTAGE_MV 4200 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_MV 3400 //Battery is put into emergency stop if one cell goes below this value

View file

@ -3,10 +3,9 @@
#include "../include.h"
#define BATTERY_SELECTED
#define MAX_CELL_DEVIATION_MV 500
#define MAX_PACK_VOLTAGE_DV 4200 //5000 = 500.0V
#define MIN_PACK_VOLTAGE_DV 3000
#define MAX_CELL_DEVIATION_MV 500
#define MAX_CELL_DEVIATION_MV 150
#define MAX_CELL_VOLTAGE_MV 4250 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value

View file

@ -5,7 +5,7 @@
#define BATTERY_SELECTED
#define MAX_PACK_VOLTAGE_DV 4100 //5000 = 500.0V
#define MIN_PACK_VOLTAGE_DV 3000
#define MAX_CELL_DEVIATION_MV 500
#define MAX_CELL_DEVIATION_MV 150
#define MAX_CELL_VOLTAGE_MV 4250 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value

View file

@ -74,6 +74,8 @@ static uint16_t fan_start_setting_value = 0;
static uint16_t ptc_heating_start_setting_value = 0;
static uint16_t default_channel_state = 0;
static uint8_t timespent_without_soc = 0;
static bool charging_active = false;
static bool discharging_active = false;
void update_values_battery() {
@ -96,7 +98,13 @@ void update_values_battery() {
datalayer.battery.status.voltage_dV = total_voltage;
datalayer.battery.status.current_dA = total_current;
if (charging_active) {
datalayer.battery.status.current_dA = total_current;
} else if (discharging_active) {
datalayer.battery.status.current_dA = -total_current;
} else { //No direction data. Should never occur, but send current as charging, better than nothing
datalayer.battery.status.current_dA = total_current;
}
// Charge power is set in .h file
if (datalayer.battery.status.real_soc > 9900) {
@ -204,6 +212,13 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
host_temperature = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
status_accounting = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
equalization_starting_voltage = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
if ((rx_frame.data.u8[4] & 0x40) >> 6) {
charging_active = true;
discharging_active = false;
} else {
charging_active = false;
discharging_active = true;
}
} else if (mux == 0x07) { // Cellvoltages 1-3
cellvoltages[0] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
cellvoltages[1] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
@ -507,6 +522,12 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
low_temperature_protection_setting_value = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
protecting_historical_logs = rx_frame.data.u8[7];
if ((protecting_historical_logs & 0x0F) > 0) {
set_event(EVENT_RJXZS_LOG, 0);
} else {
clear_event(EVENT_RJXZS_LOG);
}
if (protecting_historical_logs == 0x01) {
// Overcurrent protection
set_event(EVENT_DISCHARGE_LIMIT_EXCEEDED, 0); // could also be EVENT_CHARGE_LIMIT_EXCEEDED

View file

@ -9,11 +9,10 @@
#define MAXDISCHARGEPOWERALLOWED 60000 // 60000W we use a define since the value supplied by Tesla is always 0
/* Do not change the defines below */
#define RAMPDOWN_SOC 900 // 90.0 SOC% to start ramping down from max charge power towards 0 at 100.00%
#define RAMPDOWNPOWERALLOWED \
15000 // What power we ramp down from towards top balancing (usually same as MAXCHARGEPOWERALLOWED)
#define FLOAT_MAX_POWER_W 200 // W, what power to allow for top balancing battery
#define FLOAT_START_MV 20 // mV, how many mV under overvoltage to start float charging
#define RAMPDOWN_SOC 900 // 90.0 SOC% to start ramping down from max charge power towards 0 at 100.00%
#define RAMPDOWNPOWERALLOWED 10000 // What power we ramp down from towards top balancing
#define FLOAT_MAX_POWER_W 200 // W, what power to allow for top balancing battery
#define FLOAT_START_MV 20 // mV, how many mV under overvoltage to start float charging
#define MAX_PACK_VOLTAGE_SX_NCMA 4600 // V+1, if pack voltage goes over this, charge stops
#define MIN_PACK_VOLTAGE_SX_NCMA 3100 // V+1, if pack voltage goes over this, charge stops

View file

@ -0,0 +1,667 @@
#include "../include.h"
#ifdef VOLVO_SPA_HYBRID_BATTERY
#include "../datalayer/datalayer.h"
#include "../datalayer/datalayer_extended.h" //For "More battery info" webpage
#include "../devboard/utils/events.h"
#include "VOLVO-SPA-HYBRID-BATTERY.h"
/* Do not change code below unless you are sure what you are doing */
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
static unsigned long previousMillis1s = 0; // will store last time a 1s CAN Message was send
static unsigned long previousMillis60s = 0; // will store last time a 60s CAN Message was send
static float BATT_U = 0; //0x3A
static float MAX_U = 0; //0x3A
static float MIN_U = 0; //0x3A
static float BATT_I = 0; //0x3A
static int32_t CHARGE_ENERGY = 0; //0x1A1
static uint8_t BATT_ERR_INDICATION = 0; //0x413
static float BATT_T_MAX = 0; //0x413
static float BATT_T_MIN = 0; //0x413
static float BATT_T_AVG = 0; //0x413
static uint16_t SOC_BMS = 0; //0X37D
static uint16_t SOC_CALC = 0;
static uint16_t CELL_U_MAX = 3700; //0x37D
static uint16_t CELL_U_MIN = 3700; //0x37D
static uint8_t CELL_ID_U_MAX = 0; //0x37D
static uint16_t HvBattPwrLimDchaSoft = 0; //0x369
static uint16_t HvBattPwrLimDcha1 = 0; //0x175
//static uint16_t HvBattPwrLimDchaSlowAgi = 0; //0x177
//static uint16_t HvBattPwrLimChrgSlowAgi = 0; //0x177
//static uint8_t batteryModuleNumber = 0x10; // First battery module
static uint8_t battery_request_idx = 0;
static uint8_t rxConsecutiveFrames = 0;
static uint16_t min_max_voltage[2]; //contains cell min[0] and max[1] values in mV
static uint8_t cellcounter = 0;
static uint32_t remaining_capacity = 0;
static uint16_t cell_voltages[102]; //array with all the cellvoltages
static bool startedUp = false;
static uint8_t DTC_reset_counter = 0;
CAN_frame VOLVO_536 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x536,
//.data = {0x00, 0x40, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00}}; //Network manage frame
.data = {0x00, 0x40, 0x40, 0x01, 0x00, 0x00, 0x00, 0x00}}; //Network manage frame
CAN_frame VOLVO_140_CLOSE = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x140,
.data = {0x00, 0x02, 0x00, 0xB7, 0xFF, 0x03, 0xFF, 0x82}}; //Close contactors message
CAN_frame VOLVO_140_OPEN = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x140,
.data = {0x00, 0x02, 0x00, 0x9E, 0xFF, 0x03, 0xFF, 0x82}}; //Open contactor message
CAN_frame VOLVO_372 = {
.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x372,
.data = {0x00, 0xA6, 0x07, 0x14, 0x04, 0x00, 0x80, 0x00}}; //Ambient Temp -->>VERIFY this data content!!!<<--
CAN_frame VOLVO_CELL_U_Req = {
.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x735,
.data = {0x03, 0x22, 0x48, 0x06, 0x00, 0x00, 0x00, 0x00}}; //Cell voltage request frame // changed
CAN_frame VOLVO_FlowControl = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x735,
.data = {0x30, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00}}; //Flowcontrol
CAN_frame VOLVO_SOH_Req = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x735,
.data = {0x03, 0x22, 0x49, 0x6D, 0x00, 0x00, 0x00, 0x00}}; //Battery SOH request frame
CAN_frame VOLVO_BECMsupplyVoltage_Req = {
.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x735,
.data = {0x03, 0x22, 0xF4, 0x42, 0x00, 0x00, 0x00, 0x00}}; //BECM supply voltage request frame
CAN_frame VOLVO_DTC_Erase = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x7FF,
.data = {0x04, 0x14, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00}}; //Global DTC erase
CAN_frame VOLVO_BECM_ECUreset = {
.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x735,
.data = {0x02, 0x11, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00}}; //BECM ECU reset command (reboot/powercycle BECM)
CAN_frame VOLVO_DTCreadout = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x7FF,
.data = {0x02, 0x19, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00}}; //Global DTC readout
void update_values_battery() { //This function maps all the values fetched via CAN to the correct parameters used for the inverter
uint8_t cnt = 0;
// Update webserver datalayer
datalayer_extended.VolvoHybrid.soc_bms = SOC_BMS;
datalayer_extended.VolvoHybrid.soc_calc = SOC_CALC;
datalayer_extended.VolvoHybrid.soc_rescaled = datalayer.battery.status.reported_soc;
datalayer_extended.VolvoHybrid.soh_bms = datalayer.battery.status.soh_pptt;
datalayer_extended.VolvoHybrid.BECMBatteryVoltage = BATT_U;
datalayer_extended.VolvoHybrid.BECMBatteryCurrent = BATT_I;
datalayer_extended.VolvoHybrid.BECMUDynMaxLim = MAX_U;
datalayer_extended.VolvoHybrid.BECMUDynMinLim = MIN_U;
datalayer_extended.VolvoHybrid.HvBattPwrLimDcha1 = HvBattPwrLimDcha1;
datalayer_extended.VolvoHybrid.HvBattPwrLimDchaSoft = HvBattPwrLimDchaSoft;
//datalayer_extended.VolvoHybrid.HvBattPwrLimDchaSlowAgi = HvBattPwrLimDchaSlowAgi;
//datalayer_extended.VolvoHybrid.HvBattPwrLimChrgSlowAgi = HvBattPwrLimChrgSlowAgi;
// Update requests from webserver datalayer
if (datalayer_extended.VolvoHybrid.UserRequestDTCreset) {
transmit_can_frame(&VOLVO_DTC_Erase, can_config.battery); //Send global DTC erase command
datalayer_extended.VolvoHybrid.UserRequestDTCreset = false;
}
if (datalayer_extended.VolvoHybrid.UserRequestBECMecuReset) {
transmit_can_frame(&VOLVO_BECM_ECUreset, can_config.battery); //Send BECM ecu reset command
datalayer_extended.VolvoHybrid.UserRequestBECMecuReset = false;
}
if (datalayer_extended.VolvoHybrid.UserRequestDTCreadout) {
transmit_can_frame(&VOLVO_DTCreadout, can_config.battery); //Send DTC readout command
datalayer_extended.VolvoHybrid.UserRequestDTCreadout = false;
}
remaining_capacity = (18830 - CHARGE_ENERGY);
//datalayer.battery.status.real_soc = SOC_BMS; // Use BMS reported SOC, havent figured out how to get the BMS to calibrate empty/full yet
SOC_CALC = remaining_capacity / 19; // Use calculated SOC based on remaining_capacity
datalayer.battery.status.real_soc = SOC_CALC * 10;
if (BATT_U > MAX_U) // Protect if overcharged
{
datalayer.battery.status.real_soc = 10000;
} else if (BATT_U < MIN_U) //Protect if undercharged
{
datalayer.battery.status.real_soc = 0;
}
datalayer.battery.status.voltage_dV = BATT_U * 10;
datalayer.battery.status.current_dA = BATT_I * 10;
datalayer.battery.status.remaining_capacity_Wh = remaining_capacity;
datalayer.battery.status.max_discharge_power_W = 6600; //default power on charge connector
datalayer.battery.status.max_charge_power_W = 6600; //default power on charge connector
datalayer.battery.status.temperature_min_dC = BATT_T_MIN;
datalayer.battery.status.temperature_max_dC = BATT_T_MAX;
datalayer.battery.status.cell_max_voltage_mV = CELL_U_MAX; // Use min/max reported from BMS
datalayer.battery.status.cell_min_voltage_mV = CELL_U_MIN;
//Map all cell voltages to the global array
for (int i = 0; i < 102; ++i) {
datalayer.battery.status.cell_voltages_mV[i] = cell_voltages[i];
}
#ifdef DEBUG_LOG
logging.print("BMS reported SOC%: ");
logging.println(SOC_BMS);
logging.print("Calculated SOC%: ");
logging.println(SOC_CALC);
logging.print("Rescaled SOC%: ");
logging.println(datalayer.battery.status.reported_soc / 100);
logging.print("Battery current: ");
logging.println(BATT_I);
logging.print("Battery voltage: ");
logging.println(BATT_U);
logging.print("Battery maximum voltage limit: ");
logging.println(MAX_U);
logging.print("Battery minimum voltage limit: ");
logging.println(MIN_U);
logging.print("Remaining Energy: ");
logging.println(remaining_capacity);
logging.print("Discharge limit: ");
logging.println(HvBattPwrLimDchaSoft);
logging.print("Battery Error Indication: ");
logging.println(BATT_ERR_INDICATION);
logging.print("Maximum battery temperature: ");
logging.println(BATT_T_MAX / 10);
logging.print("Minimum battery temperature: ");
logging.println(BATT_T_MIN / 10);
logging.print("Average battery temperature: ");
logging.println(BATT_T_AVG / 10);
logging.print("BMS Highest cell voltage: ");
logging.println(CELL_U_MAX);
logging.print("BMS Lowest cell voltage: ");
logging.println(CELL_U_MIN);
logging.print("BMS Highest cell nr: ");
logging.println(CELL_ID_U_MAX);
logging.print("Highest cell voltage: ");
logging.println(min_max_voltage[1]);
logging.print("Lowest cell voltage: ");
logging.println(min_max_voltage[0]);
logging.print("Cell voltage,");
while (cnt < 102) {
logging.print(cell_voltages[cnt++]);
logging.print(",");
}
logging.println(";");
#endif
}
void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
switch (rx_frame.ID) {
case 0x3A:
if ((rx_frame.data.u8[6] & 0x80) == 0x80)
BATT_I = (0 - ((((rx_frame.data.u8[6] & 0x7F) * 256.0 + rx_frame.data.u8[7]) * 0.1) - 1638));
else {
BATT_I = 0;
#ifdef DEBUG_LOG
logging.println("BATT_I not valid");
#endif
}
if ((rx_frame.data.u8[2] & 0x08) == 0x08)
MAX_U = (((rx_frame.data.u8[2] & 0x07) * 256.0 + rx_frame.data.u8[3]) * 0.25);
else {
//MAX_U = 0;
//logging.println("MAX_U not valid"); // Value toggles between true/false from BMS
}
if ((rx_frame.data.u8[4] & 0x08) == 0x08)
MIN_U = (((rx_frame.data.u8[4] & 0x07) * 256.0 + rx_frame.data.u8[5]) * 0.25);
else {
//MIN_U = 0;
//logging.println("MIN_U not valid"); // Value toggles between true/false from BMS
}
if ((rx_frame.data.u8[0] & 0x08) == 0x08)
BATT_U = (((rx_frame.data.u8[0] & 0x07) * 256.0 + rx_frame.data.u8[1]) * 0.25);
else {
BATT_U = 0;
#ifdef DEBUG_LOG
logging.println("BATT_U not valid");
#endif
}
if ((rx_frame.data.u8[0] & 0x40) == 0x40)
datalayer_extended.VolvoHybrid.HVSysRlySts = ((rx_frame.data.u8[0] & 0x30) >> 4);
else
datalayer_extended.VolvoHybrid.HVSysRlySts = 0xFF;
if ((rx_frame.data.u8[2] & 0x40) == 0x40)
datalayer_extended.VolvoHybrid.HVSysDCRlySts1 = ((rx_frame.data.u8[2] & 0x30) >> 4);
else
datalayer_extended.VolvoHybrid.HVSysDCRlySts1 = 0xFF;
if ((rx_frame.data.u8[2] & 0x80) == 0x80)
datalayer_extended.VolvoHybrid.HVSysDCRlySts2 = ((rx_frame.data.u8[4] & 0x30) >> 4);
else
datalayer_extended.VolvoHybrid.HVSysDCRlySts2 = 0xFF;
if ((rx_frame.data.u8[0] & 0x80) == 0x80)
datalayer_extended.VolvoHybrid.HVSysIsoRMonrSts = ((rx_frame.data.u8[4] & 0xC0) >> 6);
else
datalayer_extended.VolvoHybrid.HVSysIsoRMonrSts = 0xFF;
break;
case 0x1A1:
if ((rx_frame.data.u8[4] & 0x10) == 0x10)
CHARGE_ENERGY = ((((rx_frame.data.u8[4] & 0x0F) * 256.0 + rx_frame.data.u8[5]) * 50) - 500);
else {
CHARGE_ENERGY = 0;
set_event(EVENT_KWH_PLAUSIBILITY_ERROR, CHARGE_ENERGY);
}
break;
case 0x413:
if ((rx_frame.data.u8[0] & 0x80) == 0x80)
BATT_ERR_INDICATION = ((rx_frame.data.u8[0] & 0x40) >> 6);
else {
BATT_ERR_INDICATION = 0;
#ifdef DEBUG_LOG
logging.println("BATT_ERR_INDICATION not valid");
#endif
}
if ((rx_frame.data.u8[0] & 0x20) == 0x20) {
BATT_T_MAX = ((rx_frame.data.u8[2] & 0x1F) * 256.0 + rx_frame.data.u8[3]);
BATT_T_MIN = ((rx_frame.data.u8[4] & 0x1F) * 256.0 + rx_frame.data.u8[5]);
BATT_T_AVG = ((rx_frame.data.u8[0] & 0x1F) * 256.0 + rx_frame.data.u8[1]);
} else {
BATT_T_MAX = 0;
BATT_T_MIN = 0;
BATT_T_AVG = 0;
#ifdef DEBUG_LOG
logging.println("BATT_T not valid");
#endif
}
break;
case 0x369:
if ((rx_frame.data.u8[0] & 0x80) == 0x80) {
HvBattPwrLimDchaSoft = (((rx_frame.data.u8[6] & 0x03) * 256 + rx_frame.data.u8[6]) >> 2);
} else {
HvBattPwrLimDchaSoft = 0;
#ifdef DEBUG_LOG
logging.println("HvBattPwrLimDchaSoft not valid");
#endif
}
break;
case 0x175:
if ((rx_frame.data.u8[4] & 0x80) == 0x80) {
HvBattPwrLimDcha1 = (((rx_frame.data.u8[2] & 0x07) * 256 + rx_frame.data.u8[3]) >> 2);
} else {
HvBattPwrLimDcha1 = 0;
}
break;
case 0x177:
if ((rx_frame.data.u8[4] & 0x08) == 0x08) {
//HvBattPwrLimDchaSlowAgi = (((rx_frame.data.u8[4] & 0x07) * 256 + rx_frame.data.u8[5]) >> 2);
;
} else {
//HvBattPwrLimDchaSlowAgi = 0;
;
}
if ((rx_frame.data.u8[2] & 0x08) == 0x08) {
//HvBattPwrLimChrgSlowAgi = (((rx_frame.data.u8[2] & 0x07) * 256 + rx_frame.data.u8[3]) >> 2);
;
} else {
//HvBattPwrLimChrgSlowAgi = 0;
;
}
break;
case 0x37D:
if ((rx_frame.data.u8[0] & 0x40) == 0x40) {
SOC_BMS = ((rx_frame.data.u8[6] & 0x03) * 256 + rx_frame.data.u8[7]);
} else {
SOC_BMS = 0;
#ifdef DEBUG_LOG
logging.println("SOC_BMS not valid");
#endif
}
if ((rx_frame.data.u8[0] & 0x04) == 0x04)
//CELL_U_MAX = ((rx_frame.data.u8[2] & 0x01) * 256 + rx_frame.data.u8[3]);
;
else {
//CELL_U_MAX = 0;
;
#ifdef DEBUG_LOG
logging.println("CELL_U_MAX not valid");
#endif
}
if ((rx_frame.data.u8[0] & 0x02) == 0x02)
//CELL_U_MIN = ((rx_frame.data.u8[0] & 0x01) * 256.0 + rx_frame.data.u8[1]);
;
else {
//CELL_U_MIN = 0;
;
#ifdef DEBUG_LOG
logging.println("CELL_U_MIN not valid");
#endif
}
if ((rx_frame.data.u8[0] & 0x08) == 0x08)
//CELL_ID_U_MAX = ((rx_frame.data.u8[4] & 0x01) * 256.0 + rx_frame.data.u8[5]);
;
else {
//CELL_ID_U_MAX = 0;
;
#ifdef DEBUG_LOG
logging.println("CELL_ID_U_MAX not valid");
#endif
}
break;
case 0x635: // Diag request response
if ((rx_frame.data.u8[0] == 0x07) && (rx_frame.data.u8[1] == 0x62) && (rx_frame.data.u8[2] == 0x49) &&
(rx_frame.data.u8[3] == 0x6D)) // SOH response frame
{
datalayer.battery.status.soh_pptt = ((rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]);
transmit_can_frame(&VOLVO_BECMsupplyVoltage_Req, can_config.battery); //Send BECM supply voltage req
} else if ((rx_frame.data.u8[0] == 0x05) && (rx_frame.data.u8[1] == 0x62) && (rx_frame.data.u8[2] == 0xF4) &&
(rx_frame.data.u8[3] == 0x42)) // BECM module voltage supply
{
datalayer_extended.VolvoHybrid.BECMsupplyVoltage = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
} else if ((rx_frame.data.u8[0] == 0x10) && (rx_frame.data.u8[2] == 0x62) && (rx_frame.data.u8[3] == 0x48) &&
(rx_frame.data.u8[4] == 0x06)) // First response frame of cell voltages //changed
{
cell_voltages[battery_request_idx++] = ((rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6]);
cell_voltages[battery_request_idx] = (rx_frame.data.u8[7] << 8);
transmit_can_frame(&VOLVO_FlowControl, can_config.battery); // Send flow control
rxConsecutiveFrames = 1;
} else if ((rx_frame.data.u8[0] == 0x10) && (rx_frame.data.u8[2] == 0x59) &&
(rx_frame.data.u8[3] == 0x03)) // First response frame for DTC with more than one code
{
transmit_can_frame(&VOLVO_FlowControl, can_config.battery); // Send flow control
} else if ((rx_frame.data.u8[0] == 0x21) && (rxConsecutiveFrames == 1)) {
cell_voltages[battery_request_idx++] = cell_voltages[battery_request_idx] | rx_frame.data.u8[1];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7];
transmit_can_frame(&VOLVO_FlowControl, can_config.battery); // Send flow control
} else if ((rx_frame.data.u8[0] == 0x22) && (rxConsecutiveFrames == 1)) {
cell_voltages[battery_request_idx++] = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2]);
cell_voltages[battery_request_idx++] = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4]);
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
cell_voltages[battery_request_idx] = (rx_frame.data.u8[7] << 8);
transmit_can_frame(&VOLVO_FlowControl, can_config.battery); // Send flow control
} else if ((rx_frame.data.u8[0] == 0x23) && (rxConsecutiveFrames == 1)) {
cell_voltages[battery_request_idx++] = cell_voltages[battery_request_idx] | rx_frame.data.u8[1];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7];
transmit_can_frame(&VOLVO_FlowControl, can_config.battery); // Send flow control
} else if ((rx_frame.data.u8[0] == 0x24) && (rxConsecutiveFrames == 1)) {
cell_voltages[battery_request_idx++] = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2]);
cell_voltages[battery_request_idx++] = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4]);
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
cell_voltages[battery_request_idx] = (rx_frame.data.u8[7] << 8);
transmit_can_frame(&VOLVO_FlowControl, can_config.battery); // Send flow control
} else if ((rx_frame.data.u8[0] == 0x25) && (rxConsecutiveFrames == 1)) {
cell_voltages[battery_request_idx++] = cell_voltages[battery_request_idx] | rx_frame.data.u8[1];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7];
transmit_can_frame(&VOLVO_FlowControl, can_config.battery); // Send flow control
} else if ((rx_frame.data.u8[0] == 0x26) && (rxConsecutiveFrames == 1)) {
cell_voltages[battery_request_idx++] = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2]);
cell_voltages[battery_request_idx++] = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4]);
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
cell_voltages[battery_request_idx] = (rx_frame.data.u8[7] << 8);
transmit_can_frame(&VOLVO_FlowControl, can_config.battery); // Send flow control
} else if ((rx_frame.data.u8[0] == 0x27) && (rxConsecutiveFrames == 1)) {
cell_voltages[battery_request_idx++] = cell_voltages[battery_request_idx] | rx_frame.data.u8[1];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7];
transmit_can_frame(&VOLVO_FlowControl, can_config.battery); // Send flow control
} else if ((rx_frame.data.u8[0] == 0x28) && (rxConsecutiveFrames == 1)) {
cell_voltages[battery_request_idx++] = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2]);
cell_voltages[battery_request_idx++] = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4]);
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
cell_voltages[battery_request_idx] = (rx_frame.data.u8[7] << 8);
transmit_can_frame(&VOLVO_FlowControl, can_config.battery); // Send flow control
} else if ((rx_frame.data.u8[0] == 0x29) && (rxConsecutiveFrames == 1)) {
cell_voltages[battery_request_idx++] = cell_voltages[battery_request_idx] | rx_frame.data.u8[1];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7];
transmit_can_frame(&VOLVO_FlowControl, can_config.battery); // Send flow control
} else if ((rx_frame.data.u8[0] == 0x2A) && (rxConsecutiveFrames == 1)) {
cell_voltages[battery_request_idx++] = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2]);
cell_voltages[battery_request_idx++] = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4]);
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
cell_voltages[battery_request_idx] = (rx_frame.data.u8[7] << 8);
transmit_can_frame(&VOLVO_FlowControl, can_config.battery); // Send flow control
} else if ((rx_frame.data.u8[0] == 0x2B) && (rxConsecutiveFrames == 1)) {
cell_voltages[battery_request_idx++] = cell_voltages[battery_request_idx] | rx_frame.data.u8[1];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7];
transmit_can_frame(&VOLVO_FlowControl, can_config.battery); // Send flow control
} else if ((rx_frame.data.u8[0] == 0x2C) && (rxConsecutiveFrames == 1)) {
cell_voltages[battery_request_idx++] = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2]);
cell_voltages[battery_request_idx++] = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4]);
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
cell_voltages[battery_request_idx] = (rx_frame.data.u8[7] << 8);
transmit_can_frame(&VOLVO_FlowControl, can_config.battery); // Send flow control
} else if ((rx_frame.data.u8[0] == 0x2D) && (rxConsecutiveFrames == 1)) {
cell_voltages[battery_request_idx++] = cell_voltages[battery_request_idx] | rx_frame.data.u8[1];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7];
transmit_can_frame(&VOLVO_FlowControl, can_config.battery); // Send flow control
} else if ((rx_frame.data.u8[0] == 0x2E) && (rxConsecutiveFrames == 1)) {
cell_voltages[battery_request_idx++] = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2]);
cell_voltages[battery_request_idx++] = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4]);
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
cell_voltages[battery_request_idx] = (rx_frame.data.u8[7] << 8);
transmit_can_frame(&VOLVO_FlowControl, can_config.battery); // Send flow control
} else if ((rx_frame.data.u8[0] == 0x2F) && (rxConsecutiveFrames == 1)) {
cell_voltages[battery_request_idx++] = cell_voltages[battery_request_idx] | rx_frame.data.u8[1];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7];
transmit_can_frame(&VOLVO_FlowControl, can_config.battery); // Send flow control
rxConsecutiveFrames = 2;
} else if ((rx_frame.data.u8[0] == 0x20) && (rxConsecutiveFrames == 2)) {
cell_voltages[battery_request_idx++] = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2]);
cell_voltages[battery_request_idx++] = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4]);
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
cell_voltages[battery_request_idx] = (rx_frame.data.u8[7] << 8);
transmit_can_frame(&VOLVO_FlowControl, can_config.battery); // Send flow control
} else if ((rx_frame.data.u8[0] == 0x21) && (rxConsecutiveFrames == 2)) {
cell_voltages[battery_request_idx++] = cell_voltages[battery_request_idx] | rx_frame.data.u8[1];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7];
transmit_can_frame(&VOLVO_FlowControl, can_config.battery); // Send flow control
} else if ((rx_frame.data.u8[0] == 0x22) && (rxConsecutiveFrames == 2)) {
cell_voltages[battery_request_idx++] = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2]);
cell_voltages[battery_request_idx++] = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4]);
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
cell_voltages[battery_request_idx] = (rx_frame.data.u8[7] << 8);
transmit_can_frame(&VOLVO_FlowControl, can_config.battery); // Send flow control
} else if ((rx_frame.data.u8[0] == 0x23) && (rxConsecutiveFrames == 2)) {
cell_voltages[battery_request_idx++] = cell_voltages[battery_request_idx] | rx_frame.data.u8[1];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7];
transmit_can_frame(&VOLVO_FlowControl, can_config.battery); // Send flow control
} else if ((rx_frame.data.u8[0] == 0x24) && (rxConsecutiveFrames == 2)) {
cell_voltages[battery_request_idx++] = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2]);
cell_voltages[battery_request_idx++] = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4]);
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
cell_voltages[battery_request_idx] = (rx_frame.data.u8[7] << 8);
transmit_can_frame(&VOLVO_FlowControl, can_config.battery); // Send flow control
} else if ((rx_frame.data.u8[0] == 0x25) && (rxConsecutiveFrames == 2)) {
cell_voltages[battery_request_idx++] = cell_voltages[battery_request_idx] | rx_frame.data.u8[1];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7];
transmit_can_frame(&VOLVO_FlowControl, can_config.battery); // Send flow control
} else if ((rx_frame.data.u8[0] == 0x26) && (rxConsecutiveFrames == 2)) {
cell_voltages[battery_request_idx++] = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2]);
cell_voltages[battery_request_idx++] = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4]);
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
cell_voltages[battery_request_idx] = (rx_frame.data.u8[7] << 8);
transmit_can_frame(&VOLVO_FlowControl, can_config.battery); // Send flow control
} else if ((rx_frame.data.u8[0] == 0x27) && (rxConsecutiveFrames == 2)) {
cell_voltages[battery_request_idx++] = cell_voltages[battery_request_idx] | rx_frame.data.u8[1];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7];
transmit_can_frame(&VOLVO_FlowControl, can_config.battery); // Send flow control
} else if ((rx_frame.data.u8[0] == 0x28) && (rxConsecutiveFrames == 2)) {
cell_voltages[battery_request_idx++] = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2]);
cell_voltages[battery_request_idx++] = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4]);
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
cell_voltages[battery_request_idx] = (rx_frame.data.u8[7] << 8);
transmit_can_frame(&VOLVO_FlowControl, can_config.battery); // Send flow control
} else if ((rx_frame.data.u8[0] == 0x29) && (rxConsecutiveFrames == 2)) {
cell_voltages[battery_request_idx++] = cell_voltages[battery_request_idx] | rx_frame.data.u8[1];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7];
transmit_can_frame(&VOLVO_FlowControl, can_config.battery); // Send flow control
} else if ((rx_frame.data.u8[0] == 0x2A) && (rxConsecutiveFrames == 2)) {
cell_voltages[battery_request_idx++] = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2]);
cell_voltages[battery_request_idx++] = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4]);
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
cell_voltages[battery_request_idx] = (rx_frame.data.u8[7] << 8);
transmit_can_frame(&VOLVO_FlowControl, can_config.battery); // Send flow control
} else if ((rx_frame.data.u8[0] == 0x2B) && (rxConsecutiveFrames == 2)) {
cell_voltages[battery_request_idx++] = cell_voltages[battery_request_idx] | rx_frame.data.u8[1];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7];
transmit_can_frame(&VOLVO_FlowControl, can_config.battery); // Send flow control
} else if ((rx_frame.data.u8[0] == 0x2C) && (rxConsecutiveFrames == 2)) {
cell_voltages[battery_request_idx++] = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2]);
cell_voltages[battery_request_idx++] = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4]);
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
cell_voltages[battery_request_idx] = (rx_frame.data.u8[7] << 8);
transmit_can_frame(&VOLVO_FlowControl, can_config.battery); // Send flow control
} else if ((rx_frame.data.u8[0] == 0x2D) && (rxConsecutiveFrames == 2)) {
cell_voltages[battery_request_idx++] = cell_voltages[battery_request_idx] | rx_frame.data.u8[1];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
//cell_voltages[battery_request_idx++] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7];
//transmit_can_frame(&VOLVO_FlowControl, can_config.battery); // Send flow control
transmit_can_frame(&VOLVO_FlowControl, can_config.battery); // Send flow control
if (false) // Run until last pack is read
{
//VOLVO_CELL_U_Req.data.u8[3] = batteryModuleNumber++;
//transmit_can_frame(&VOLVO_CELL_U_Req, can_config.battery); //Send cell voltage read request for next module
;
} else {
min_max_voltage[0] = 9999;
min_max_voltage[1] = 0;
for (cellcounter = 0; cellcounter < 102; cellcounter++) {
if (min_max_voltage[0] > cell_voltages[cellcounter])
min_max_voltage[0] = cell_voltages[cellcounter];
if (min_max_voltage[1] < cell_voltages[cellcounter]) {
min_max_voltage[1] = cell_voltages[cellcounter];
CELL_ID_U_MAX = cellcounter;
}
}
CELL_U_MAX = min_max_voltage[1];
CELL_U_MIN = min_max_voltage[0];
transmit_can_frame(&VOLVO_SOH_Req, can_config.battery); //Send SOH read request
}
rxConsecutiveFrames = 0;
}
break;
default:
break;
}
}
void readCellVoltages() {
battery_request_idx = 0;
//batteryModuleNumber = 0x10;
rxConsecutiveFrames = 0;
//VOLVO_CELL_U_Req.data.u8[3] = batteryModuleNumber++;
transmit_can_frame(&VOLVO_CELL_U_Req, can_config.battery); //Send cell voltage read request for first module
}
void transmit_can_battery() {
unsigned long currentMillis = millis();
// Send 100ms CAN Message
if (currentMillis - previousMillis100 >= INTERVAL_100_MS) {
// Check if sending of CAN messages has been delayed too much.
if ((currentMillis - previousMillis100 >= INTERVAL_100_MS_DELAYED) && (currentMillis > BOOTUP_TIME)) {
set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis100));
} else {
clear_event(EVENT_CAN_OVERRUN);
}
previousMillis100 = currentMillis;
transmit_can_frame(&VOLVO_536, can_config.battery); //Send 0x536 Network managing frame to keep BMS alive
transmit_can_frame(&VOLVO_372, can_config.battery); //Send 0x372 ECMAmbientTempCalculated
if ((datalayer.battery.status.bms_status == ACTIVE) && startedUp) {
datalayer.system.status.battery_allows_contactor_closing = true;
//transmit_can_frame(&VOLVO_140_CLOSE, can_config.battery); //Send 0x140 Close contactors message
} else { //datalayer.battery.status.bms_status == FAULT , OR inverter requested opening contactors, OR system not started yet
datalayer.system.status.battery_allows_contactor_closing = false;
//transmit_can_frame(&VOLVO_140_OPEN, can_config.battery); //Send 0x140 Open contactors message
}
}
if (currentMillis - previousMillis1s >= INTERVAL_1_S) {
previousMillis1s = currentMillis;
if (!startedUp) {
transmit_can_frame(&VOLVO_DTC_Erase, can_config.battery); //Erase any DTCs preventing startup
DTC_reset_counter++;
if (DTC_reset_counter > 1) { // Performed twice before starting
startedUp = true;
}
}
}
if (currentMillis - previousMillis60s >= INTERVAL_60_S) {
previousMillis60s = currentMillis;
if (true) {
readCellVoltages();
#ifdef DEBUG_LOG
logging.println("Requesting cell voltages");
#endif
}
}
}
void setup_battery(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, "Volvo PHEV battery", 63); //changed
datalayer.system.info.battery_protocol[63] = '\0';
datalayer.battery.info.number_of_cells = 102; //was 108, changed
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
datalayer.battery.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV;
}
#endif

View file

@ -0,0 +1,16 @@
#ifndef VOLVO_SPA_HYBRID_BATTERY_H
#define VOLVO_SPA_HYBRID_BATTERY_H
#include <Arduino.h>
#include "../include.h"
#define BATTERY_SELECTED
#define MAX_PACK_VOLTAGE_DV 4294 //5000 = 500.0V
#define MIN_PACK_VOLTAGE_DV 2754
#define MAX_CELL_DEVIATION_MV 250
#define MAX_CELL_VOLTAGE_MV 4210 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value
void setup_battery(void);
void transmit_can_frame(CAN_frame* tx_frame, int interface);
#endif

View file

@ -109,7 +109,9 @@ void transmit_can() {
return; //Global block of CAN messages
}
#ifndef RS485_BATTERY_SELECTED
transmit_can_battery();
#endif
#ifdef CAN_INVERTER_SELECTED
transmit_can_inverter();
@ -302,7 +304,9 @@ void map_can_frame_to_variable(CAN_frame* rx_frame, int interface) {
#endif
if (interface == can_config.battery) {
#ifndef RS485_BATTERY_SELECTED
handle_incoming_can_frame_battery(*rx_frame);
#endif
#ifdef CHADEMO_BATTERY
ISA_handleFrame(rx_frame);
#endif

View file

@ -29,9 +29,9 @@ void init_rs485() {
pinMode(PIN_5V_EN, OUTPUT);
digitalWrite(PIN_5V_EN, HIGH);
#endif // PIN_5V_EN
#ifdef RS485_INVERTER_SELECTED
Serial2.begin(57600, SERIAL_8N1, RS485_RX_PIN, RS485_TX_PIN);
#endif // RS485_INVERTER_SELECTED
#if defined(RS485_INVERTER_SELECTED) || defined(RS485_BATTERY_SELECTED)
Serial2.begin(RS485_BAUDRATE, SERIAL_8N1, RS485_RX_PIN, RS485_TX_PIN);
#endif // RS485_INVERTER_SELECTED || RS485_BATTERY_SELECTED
#ifdef MODBUS_INVERTER_SELECTED
#ifdef BYD_MODBUS
// Init Static data to the RTU Modbus

View file

@ -124,6 +124,12 @@ typedef struct {
/** The user specified maximum allowed discharge voltage, in deciVolt. 3000 = 300.0 V */
uint16_t max_user_set_discharge_voltage_dV = BATTERY_MAX_DISCHARGE_VOLTAGE;
/** Parameters for keeping track of the limiting factor in the system */
bool user_settings_limit_discharge = false;
bool user_settings_limit_charge = false;
bool inverter_limits_discharge = false;
bool inverter_limits_charge = false;
/** Tesla specific settings that are edited on the fly when manually forcing a balance charge for LFP chemistry */
/* Bool for specifying if user has requested manual function */
bool user_requests_balancing = false;
@ -203,10 +209,12 @@ typedef struct {
typedef struct {
/** array with type of battery used, for displaying on webserver */
char battery_protocol[64] = {0};
/** array with type of inverter used, for displaying on webserver */
/** array with type of inverter protocol used, for displaying on webserver */
char inverter_protocol[64] = {0};
/** array with type of battery used, for displaying on webserver */
char shunt_protocol[64] = {0};
/** array with type of inverter brand used, for displaying on webserver */
char inverter_brand[8] = {0};
/** array with incoming CAN messages, for displaying on webserver */
char logged_can_messages[15000] = {0};
size_t logged_can_messages_offset = 0;

View file

@ -181,7 +181,9 @@ typedef struct {
/** uint16_t */
/** Voltage polled OBD2*/
uint16_t voltage_polled = 0;
/** int16_t */
/** All the temperature sensors inside the battery pack*/
int16_t battery_temperatures[10];
} DATALAYER_INFO_BYDATTO3;
typedef struct {
@ -641,6 +643,36 @@ typedef struct {
} DATALAYER_INFO_VOLVO_POLESTAR;
typedef struct {
uint16_t soc_bms = 0;
uint16_t soc_calc = 0;
uint16_t soc_rescaled = 0;
uint16_t soh_bms = 0;
uint16_t BECMsupplyVoltage = 0;
uint16_t BECMBatteryVoltage = 0;
uint16_t BECMBatteryCurrent = 0;
uint16_t BECMUDynMaxLim = 0;
uint16_t BECMUDynMinLim = 0;
uint16_t HvBattPwrLimDcha1 = 0;
uint16_t HvBattPwrLimDchaSoft = 0;
//uint16_t HvBattPwrLimDchaSlowAgi = 0;
//uint16_t HvBattPwrLimChrgSlowAgi = 0;
uint8_t HVSysRlySts = 0;
uint8_t HVSysDCRlySts1 = 0;
uint8_t HVSysDCRlySts2 = 0;
uint8_t HVSysIsoRMonrSts = 0;
/** User requesting DTC reset via WebUI*/
bool UserRequestDTCreset = false;
/** User requesting DTC readout via WebUI*/
bool UserRequestDTCreadout = false;
/** User requesting BECM reset via WebUI*/
bool UserRequestBECMecuReset = false;
} DATALAYER_INFO_VOLVO_HYBRID;
typedef struct {
/** uint16_t */
/** Values WIP*/
@ -700,6 +732,7 @@ class DataLayerExtended {
DATALAYER_INFO_NISSAN_LEAF nissanleaf;
DATALAYER_INFO_MEB meb;
DATALAYER_INFO_VOLVO_POLESTAR VolvoPolestar;
DATALAYER_INFO_VOLVO_HYBRID VolvoHybrid;
DATALAYER_INFO_ZOE_PH2 zoePH2;
};

View file

@ -21,8 +21,8 @@ battery_pause_status emulator_pause_status = NORMAL;
void update_machineryprotection() {
// Start checking that the battery is within reason. Incase we see any funny business, raise an event!
// Pause function is on
if (emulator_pause_request_ON) {
// Pause function is on OR we have a critical fault event active
if (emulator_pause_request_ON || (datalayer.battery.status.bms_status == FAULT)) {
datalayer.battery.status.max_discharge_power_W = 0;
datalayer.battery.status.max_charge_power_W = 0;
}
@ -41,6 +41,14 @@ void update_machineryprotection() {
clear_event(EVENT_BATTERY_FROZEN);
}
if (labs(datalayer.battery.status.temperature_max_dC - datalayer.battery.status.temperature_min_dC) >
BATTERY_MAX_TEMPERATURE_DEVIATION) {
set_event_latched(EVENT_BATTERY_TEMP_DEVIATION_HIGH,
datalayer.battery.status.temperature_max_dC - datalayer.battery.status.temperature_min_dC);
} else {
clear_event(EVENT_BATTERY_TEMP_DEVIATION_HIGH);
}
// Battery voltage is over designed max voltage!
if (datalayer.battery.status.voltage_dV > datalayer.battery.info.max_design_voltage_dV) {
set_event(EVENT_BATTERY_OVERVOLTAGE, datalayer.battery.status.voltage_dV);

View file

@ -210,6 +210,7 @@ void init_events(void) {
events.entries[EVENT_RESET_EFUSE].level = EVENT_LEVEL_INFO;
events.entries[EVENT_RESET_PWR_GLITCH].level = EVENT_LEVEL_INFO;
events.entries[EVENT_RESET_CPU_LOCKUP].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_RJXZS_LOG].level = EVENT_LEVEL_INFO;
events.entries[EVENT_PAUSE_BEGIN].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_PAUSE_END].level = EVENT_LEVEL_INFO;
events.entries[EVENT_WIFI_CONNECT].level = EVENT_LEVEL_INFO;
@ -218,6 +219,7 @@ void init_events(void) {
events.entries[EVENT_MQTT_DISCONNECT].level = EVENT_LEVEL_INFO;
events.entries[EVENT_EQUIPMENT_STOP].level = EVENT_LEVEL_ERROR;
events.entries[EVENT_SD_INIT_FAILED].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_BATTERY_TEMP_DEVIATION_HIGH].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_EEPROM_WRITE].log = false; // Don't log the logger...
@ -254,6 +256,9 @@ void reset_all_events() {
}
events.level = EVENT_LEVEL_INFO;
update_bms_status();
#ifdef DEBUG_LOG
logging.println("All events have been cleared.");
#endif
}
void set_event_MQTTpublished(EVENTS_ENUM_TYPE event) {
@ -430,6 +435,8 @@ const char* get_event_message_string(EVENTS_ENUM_TYPE event) {
return "The board was reset due to a detected power glitch";
case EVENT_RESET_CPU_LOCKUP:
return "The board was reset due to CPU lockup. Inform developers!";
case EVENT_RJXZS_LOG:
return "Error code active in RJXZS BMS. Clear via their smartphone app!";
case EVENT_PAUSE_BEGIN:
return "The emulator is trying to pause the battery.";
case EVENT_PAUSE_END:

View file

@ -6,7 +6,7 @@
// #define INCLUDE_EVENTS_TEST // Enable to run an event test loop, see events_test_on_target.cpp
#define EE_MAGIC_HEADER_VALUE 0x0023 // 0x0000 to 0xFFFF
#define EE_MAGIC_HEADER_VALUE 0x0024 // 0x0000 to 0xFFFF
#define GENERATE_ENUM(ENUM) ENUM,
#define GENERATE_STRING(STRING) #STRING,
@ -107,6 +107,7 @@
XX(EVENT_RESET_EFUSE) \
XX(EVENT_RESET_PWR_GLITCH) \
XX(EVENT_RESET_CPU_LOCKUP) \
XX(EVENT_RJXZS_LOG) \
XX(EVENT_PAUSE_BEGIN) \
XX(EVENT_PAUSE_END) \
XX(EVENT_WIFI_CONNECT) \
@ -116,6 +117,7 @@
XX(EVENT_EQUIPMENT_STOP) \
XX(EVENT_AUTOMATIC_PRECHARGE_FAILURE) \
XX(EVENT_SD_INIT_FAILED) \
XX(EVENT_BATTERY_TEMP_DEVIATION_HIGH) \
XX(EVENT_NOF_EVENTS)
typedef enum { EVENTS_ENUM_TYPE(GENERATE_ENUM) } EVENTS_ENUM_TYPE;

View file

@ -469,6 +469,16 @@ String advanced_battery_processor(const String& var) {
content += "<h4>SOC OBD2: " + String(datalayer_extended.bydAtto3.SOC_polled) + "</h4>";
content += "<h4>Voltage periodic: " + String(datalayer_extended.bydAtto3.voltage_periodic) + "</h4>";
content += "<h4>Voltage OBD2: " + String(datalayer_extended.bydAtto3.voltage_polled) + "</h4>";
content += "<h4>Temperature sensor 1: " + String(datalayer_extended.bydAtto3.battery_temperatures[0]) + "</h4>";
content += "<h4>Temperature sensor 2: " + String(datalayer_extended.bydAtto3.battery_temperatures[1]) + "</h4>";
content += "<h4>Temperature sensor 3: " + String(datalayer_extended.bydAtto3.battery_temperatures[2]) + "</h4>";
content += "<h4>Temperature sensor 4: " + String(datalayer_extended.bydAtto3.battery_temperatures[3]) + "</h4>";
content += "<h4>Temperature sensor 5: " + String(datalayer_extended.bydAtto3.battery_temperatures[4]) + "</h4>";
content += "<h4>Temperature sensor 6: " + String(datalayer_extended.bydAtto3.battery_temperatures[5]) + "</h4>";
content += "<h4>Temperature sensor 7: " + String(datalayer_extended.bydAtto3.battery_temperatures[6]) + "</h4>";
content += "<h4>Temperature sensor 8: " + String(datalayer_extended.bydAtto3.battery_temperatures[7]) + "</h4>";
content += "<h4>Temperature sensor 9: " + String(datalayer_extended.bydAtto3.battery_temperatures[8]) + "</h4>";
content += "<h4>Temperature sensor 10: " + String(datalayer_extended.bydAtto3.battery_temperatures[9]) + "</h4>";
#endif //BYD_ATTO_3_BATTERY
#ifdef SIMPBMS_BATTERY
@ -1320,11 +1330,101 @@ String advanced_battery_processor(const String& var) {
content += "<button onclick='Volvo_BECMecuReset()'>Restart BECM module</button>";
#endif // VOLVO_SPA_BATTERY
#ifdef VOLVO_SPA_HYBRID_BATTERY
content += "<h4>BECM reported SOC: " + String(datalayer_extended.VolvoHybrid.soc_bms) + "</h4>";
content += "<h4>Calculated SOC: " + String(datalayer_extended.VolvoHybrid.soc_calc) + "</h4>";
content += "<h4>Rescaled SOC: " + String(datalayer_extended.VolvoHybrid.soc_rescaled / 10) + "</h4>";
content += "<h4>BECM reported SOH: " + String(datalayer_extended.VolvoHybrid.soh_bms) + "</h4>";
content += "<h4>BECM supply voltage: " + String(datalayer_extended.VolvoHybrid.BECMsupplyVoltage) + " mV</h4>";
content += "<h4>HV voltage: " + String(datalayer_extended.VolvoHybrid.BECMBatteryVoltage) + " V</h4>";
content += "<h4>HV current: " + String(datalayer_extended.VolvoHybrid.BECMBatteryCurrent) + " A</h4>";
content += "<h4>Dynamic max voltage: " + String(datalayer_extended.VolvoHybrid.BECMUDynMaxLim) + " V</h4>";
content += "<h4>Dynamic min voltage: " + String(datalayer_extended.VolvoHybrid.BECMUDynMinLim) + " V</h4>";
content += "<h4>Discharge power limit 1: " + String(datalayer_extended.VolvoHybrid.HvBattPwrLimDcha1) + " kW</h4>";
content +=
"<h4>Discharge soft power limit: " + String(datalayer_extended.VolvoHybrid.HvBattPwrLimDchaSoft) + " kW</h4>";
content += "<h4>HV system relay status: ";
switch (datalayer_extended.VolvoHybrid.HVSysRlySts) {
case 0:
content += String("Open");
break;
case 1:
content += String("Closed");
break;
case 2:
content += String("KeepStatus");
break;
case 3:
content += String("OpenAndRequestActiveDischarge");
break;
default:
content += String("Not valid");
}
content += "</h4><h4>HV system relay status 1: ";
switch (datalayer_extended.VolvoHybrid.HVSysDCRlySts1) {
case 0:
content += String("Open");
break;
case 1:
content += String("Closed");
break;
case 2:
content += String("KeepStatus");
break;
case 3:
content += String("Fault");
break;
default:
content += String("Not valid");
}
content += "</h4><h4>HV system relay status 2: ";
switch (datalayer_extended.VolvoHybrid.HVSysDCRlySts2) {
case 0:
content += String("Open");
break;
case 1:
content += String("Closed");
break;
case 2:
content += String("KeepStatus");
break;
case 3:
content += String("Fault");
break;
default:
content += String("Not valid");
}
content += "</h4><h4>HV system isolation resistance monitoring status: ";
switch (datalayer_extended.VolvoHybrid.HVSysIsoRMonrSts) {
case 0:
content += String("Not valid 1");
break;
case 1:
content += String("False");
break;
case 2:
content += String("True");
break;
case 3:
content += String("Not valid 2");
break;
default:
content += String("Not valid");
}
content += "<br><br><button onclick='Volvo_askEraseDTC()'>Erase DTC</button><br>";
content += "<button onclick='Volvo_askReadDTC()'>Read DTC (result must be checked in CANlog)</button><br>";
content += "<button onclick='Volvo_BECMecuReset()'>Restart BECM module</button>";
#endif // VOLVO_SPA_HYBRID_BATTERY
#if !defined(BMW_PHEV_BATTERY) && !defined(BMW_IX_BATTERY) && !defined(BOLT_AMPERA_BATTERY) && \
!defined(TESLA_BATTERY) && !defined(NISSAN_LEAF_BATTERY) && !defined(BMW_I3_BATTERY) && \
!defined(BYD_ATTO_3_BATTERY) && !defined(RENAULT_ZOE_GEN2_BATTERY) && !defined(CELLPOWER_BMS) && \
!defined(MEB_BATTERY) && !defined(VOLVO_SPA_BATTERY) && \
!defined(KIA_HYUNDAI_64_BATTERY) && !defined(SIMPBMS_BATTERY)//Only the listed types have extra info
!defined(MEB_BATTERY) && !defined(VOLVO_SPA_BATTERY) && !defined(VOLVO_SPA_HYBRID_BATTERY) && \
!defined(KIA_HYUNDAI_64_BATTERY) && !defined(SIMPBMS_BATTERY) //Only the listed types have extra info
content += "No extra information available for this battery type";
#endif

View file

@ -430,6 +430,33 @@ void init_webserver() {
request->send(200, "text/plain", "Updated successfully");
});
// Route for erasing DTC on Volvo hybrid batteries
server.on("/volvoEraseDTC", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) {
return request->requestAuthentication();
}
datalayer_extended.VolvoHybrid.UserRequestDTCreset = true;
request->send(200, "text/plain", "Updated successfully");
});
// Route for reading DTC on Volvo hybrid batteries
server.on("/volvoReadDTC", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) {
return request->requestAuthentication();
}
datalayer_extended.VolvoHybrid.UserRequestDTCreadout = true;
request->send(200, "text/plain", "Updated successfully");
});
// Route for performing ECU reset on Volvo hybrid batteries
server.on("/volvoBECMecuReset", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) {
return request->requestAuthentication();
}
datalayer_extended.VolvoHybrid.UserRequestBECMecuReset = true;
request->send(200, "text/plain", "Updated successfully");
});
#ifdef TEST_FAKE_BATTERY
// Route for editing FakeBatteryVoltage
server.on("/updateFakeBatteryVoltage", HTTP_GET, [](AsyncWebServerRequest* request) {
@ -784,6 +811,8 @@ String processor(const String& var) {
// Display which components are used
content += "<h4 style='color: white;'>Inverter protocol: ";
content += datalayer.system.info.inverter_protocol;
content += " ";
content += datalayer.system.info.inverter_brand;
content += "</h4>";
content += "<h4 style='color: white;'>Battery protocol: ";
content += datalayer.system.info.battery_protocol;
@ -885,8 +914,18 @@ String processor(const String& var) {
} else {
content += formatPowerValue("Max discharge power", datalayer.battery.status.max_discharge_power_W, "", 1);
content += formatPowerValue("Max charge power", datalayer.battery.status.max_charge_power_W, "", 1);
content += "<h4 style='color: white;'>Max discharge current: " + String(maxCurrentDischargeFloat, 1) + " A</h4>";
content += "<h4 style='color: white;'>Max charge current: " + String(maxCurrentChargeFloat, 1) + " A</h4>";
content += "<h4 style='color: white;'>Max discharge current: " + String(maxCurrentDischargeFloat, 1) + " A";
if (datalayer.battery.settings.user_settings_limit_discharge) {
content += " (Manual)</h4>";
} else {
content += " (BMS)</h4>";
}
content += "<h4 style='color: white;'>Max charge current: " + String(maxCurrentChargeFloat, 1) + " A";
if (datalayer.battery.settings.user_settings_limit_charge) {
content += " (Manual)</h4>";
} else {
content += " (BMS)</h4>";
}
}
content += "<h4>Cell max: " + String(datalayer.battery.status.cell_max_voltage_mV) + " mV</h4>";
@ -947,9 +986,28 @@ String processor(const String& var) {
if (datalayer.battery.status.current_dA == 0) {
content += "<h4>Battery idle</h4>";
} else if (datalayer.battery.status.current_dA < 0) {
content += "<h4>Battery discharging!</h4>";
} else { // > 0
content += "<h4>Battery charging!</h4>";
content += "<h4>Battery discharging!";
if (datalayer.battery.settings.inverter_limits_discharge) {
content += " (Inverter limiting)</h4>";
} else {
if (datalayer.battery.settings.user_settings_limit_discharge) {
content += " (Settings limiting)</h4>";
} else {
content += " (Battery limiting)</h4>";
}
}
content += "</h4>";
} else { // > 0 , positive current
content += "<h4>Battery charging!";
if (datalayer.battery.settings.inverter_limits_charge) {
content += " (Inverter limiting)</h4>";
} else {
if (datalayer.battery.settings.user_settings_limit_charge) {
content += " (Settings limiting)</h4>";
} else {
content += " (Battery limiting)</h4>";
}
}
}
content += "<h4>Automatic contactor closing allowed:</h4>";

View file

@ -4,10 +4,10 @@
#include <Preferences.h>
#include <WiFi.h>
#include "../../include.h"
#include "../../lib/ESP32Async-AsyncTCP/src/AsyncTCP.h"
#include "../../lib/ESP32Async-ESPAsyncWebServer/src/ESPAsyncWebServer.h"
#include "../../lib/YiannisBourkelis-Uptime-Library/src/uptime_formatter.h"
#include "../../lib/ayushsharma82-ElegantOTA/src/ElegantOTA.h"
#include "../../lib/mathieucarbou-AsyncTCPSock/src/AsyncTCP.h"
extern const char* version_number; // The current software version, shown on webserver

View file

@ -74,7 +74,6 @@ CAN_frame BYD_210 = {.FD = false,
.ID = 0x210,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
static uint8_t inverter_name[7] = {0};
static int16_t temperature_average = 0;
static uint16_t inverter_voltage = 0;
static uint16_t inverter_SOC = 0;
@ -127,6 +126,16 @@ void update_values_can_inverter() { //This function maps all the values fetched
//SOC (100.00%)
BYD_150.data.u8[0] = (datalayer.battery.status.reported_soc >> 8);
BYD_150.data.u8[1] = (datalayer.battery.status.reported_soc & 0x00FF);
if (datalayer.battery.status.max_charge_current_dA == 0) {
//Force to 100.00% incase battery no longer wants to charge
BYD_150.data.u8[0] = (10000 >> 8);
BYD_150.data.u8[1] = (10000 & 0x00FF);
}
if (datalayer.battery.status.max_discharge_current_dA == 0) {
//Force to 0% incase battery no longer wants to discharge
BYD_150.data.u8[0] = 0;
BYD_150.data.u8[1] = 0;
}
//StateOfHealth (100.00%)
BYD_150.data.u8[2] = (datalayer.battery.status.soh_pptt >> 8);
BYD_150.data.u8[3] = (datalayer.battery.status.soh_pptt & 0x00FF);
@ -153,16 +162,6 @@ void update_values_can_inverter() { //This function maps all the values fetched
//Temperature min
BYD_210.data.u8[2] = (datalayer.battery.status.temperature_min_dC >> 8);
BYD_210.data.u8[3] = (datalayer.battery.status.temperature_min_dC & 0x00FF);
#ifdef DEBUG_LOG
if (inverter_name[0] != 0) {
logging.print("Detected inverter: ");
for (uint8_t i = 0; i < 7; i++) {
logging.print((char)inverter_name[i]);
}
logging.println();
}
#endif
}
void map_can_frame_to_variable_inverter(CAN_frame rx_frame) {
@ -174,8 +173,9 @@ void map_can_frame_to_variable_inverter(CAN_frame rx_frame) {
send_intial_data();
} else { // We can identify what inverter type we are connected to
for (uint8_t i = 0; i < 7; i++) {
inverter_name[i] = rx_frame.data.u8[i + 1];
datalayer.system.info.inverter_brand[i] = rx_frame.data.u8[i + 1];
}
datalayer.system.info.inverter_brand[7] = '\0';
}
break;
case 0x091:

View file

@ -0,0 +1,499 @@
#include "../include.h"
#ifdef FERROAMP_CAN
#include "../datalayer/datalayer.h"
#include "FERROAMP-CAN.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)
#define INVERT_LOW_HIGH_BYTES //If defined, certain frames will have inverted low/high bytes \
//useful for some inverters like Sofar that report the voltages incorrect otherwise
#define SET_30K_OFFSET //If defined, current values are sent with a 30k offest (useful for ferroamp)
/* Some inverters need to see a specific amount of cells/modules to emulate a specific Pylon battery.
Change the following only if your inverter is generating fault codes about voltage range */
#define TOTAL_CELL_AMOUNT 120 //Adjust this parameter in steps of 120 to add another 14,2kWh of capacity
#define MODULES_IN_SERIES 4
#define CELLS_PER_MODULE 30
#define VOLTAGE_LEVEL 384
#define AH_CAPACITY 37
/* Do not change code below unless you are sure what you are doing */
//Actual content messages
CAN_frame PYLON_7310 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x7310,
.data = {0x01, 0x00, 0x02, 0x01, 0x01, 0x02, 0x00, 0x00}};
CAN_frame PYLON_7311 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x7311,
.data = {0x01, 0x00, 0x02, 0x01, 0x01, 0x02, 0x00, 0x00}};
CAN_frame PYLON_7320 = {
.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x7320,
.data = {TOTAL_CELL_AMOUNT, (uint8_t)(TOTAL_CELL_AMOUNT >> 8), MODULES_IN_SERIES, CELLS_PER_MODULE, VOLTAGE_LEVEL,
(uint8_t)(VOLTAGE_LEVEL >> 8), AH_CAPACITY, (uint8_t)(AH_CAPACITY >> 8)}};
CAN_frame PYLON_7321 = {
.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x7321,
.data = {TOTAL_CELL_AMOUNT, (uint8_t)(TOTAL_CELL_AMOUNT >> 8), MODULES_IN_SERIES, CELLS_PER_MODULE, VOLTAGE_LEVEL,
(uint8_t)(VOLTAGE_LEVEL >> 8), AH_CAPACITY, (uint8_t)(AH_CAPACITY >> 8)}};
CAN_frame PYLON_4210 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4210,
.data = {0xA5, 0x09, 0x30, 0x75, 0x9D, 0x04, 0x2E, 0x64}};
CAN_frame PYLON_4220 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4220,
.data = {0x8C, 0x0A, 0xE9, 0x07, 0x4A, 0x79, 0x4A, 0x79}};
CAN_frame PYLON_4230 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4230,
.data = {0xDF, 0x0C, 0xDA, 0x0C, 0x03, 0x00, 0x06, 0x00}};
CAN_frame PYLON_4240 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4240,
.data = {0x7E, 0x04, 0x62, 0x04, 0x11, 0x00, 0x03, 0x00}};
CAN_frame PYLON_4250 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4250,
.data = {0x03, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame PYLON_4260 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4260,
.data = {0xAC, 0xC7, 0x74, 0x27, 0x03, 0x00, 0x02, 0x00}};
CAN_frame PYLON_4270 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4270,
.data = {0x7E, 0x04, 0x62, 0x04, 0x05, 0x00, 0x01, 0x00}};
CAN_frame PYLON_4280 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4280,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame PYLON_4290 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4290,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame PYLON_4211 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4211,
.data = {0xA5, 0x09, 0x30, 0x75, 0x9D, 0x04, 0x2E, 0x64}};
CAN_frame PYLON_4221 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4221,
.data = {0x8C, 0x0A, 0xE9, 0x07, 0x4A, 0x79, 0x4A, 0x79}};
CAN_frame PYLON_4231 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4231,
.data = {0xDF, 0x0C, 0xDA, 0x0C, 0x03, 0x00, 0x06, 0x00}};
CAN_frame PYLON_4241 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4241,
.data = {0x7E, 0x04, 0x62, 0x04, 0x11, 0x00, 0x03, 0x00}};
CAN_frame PYLON_4251 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4251,
.data = {0x03, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame PYLON_4261 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4261,
.data = {0xAC, 0xC7, 0x74, 0x27, 0x03, 0x00, 0x02, 0x00}};
CAN_frame PYLON_4271 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4271,
.data = {0x7E, 0x04, 0x62, 0x04, 0x05, 0x00, 0x01, 0x00}};
CAN_frame PYLON_4281 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4281,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame PYLON_4291 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4291,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
static uint16_t cell_tweaked_max_voltage_mV = 3300;
static uint16_t cell_tweaked_min_voltage_mV = 3300;
void update_values_can_inverter() { //This function maps all the values fetched from battery CAN to the correct CAN messages
//There are more mappings that could be added, but this should be enough to use as a starting point
// Note we map both 0 and 1 messages
//Ferroamp only supports LFP batteries. We need to fake an LFP voltage range if the battery used is not LFP
if (datalayer.battery.info.chemistry == battery_chemistry_enum::LFP) {
//Already LFP, pass thru value
cell_tweaked_max_voltage_mV = datalayer.battery.status.cell_max_voltage_mV;
cell_tweaked_min_voltage_mV = datalayer.battery.status.cell_min_voltage_mV;
} else { //linear interpolation to remap the value from the range [2500-4200] to [2500-3400]
cell_tweaked_max_voltage_mV =
(2500 + ((datalayer.battery.status.cell_max_voltage_mV - 2500) * (3400 - 2500)) / (4200 - 2500));
cell_tweaked_min_voltage_mV =
(2500 + ((datalayer.battery.status.cell_min_voltage_mV - 2500) * (3400 - 2500)) / (4200 - 2500));
}
//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] = (datalayer.battery.status.voltage_dV >> 8);
PYLON_4210.data.u8[1] = (datalayer.battery.status.voltage_dV & 0x00FF);
PYLON_4211.data.u8[0] = (datalayer.battery.status.voltage_dV >> 8);
PYLON_4211.data.u8[1] = (datalayer.battery.status.voltage_dV & 0x00FF);
//Current (15.0)
PYLON_4210.data.u8[2] = (datalayer.battery.status.current_dA >> 8);
PYLON_4210.data.u8[3] = (datalayer.battery.status.current_dA & 0x00FF);
PYLON_4211.data.u8[2] = (datalayer.battery.status.current_dA >> 8);
PYLON_4211.data.u8[3] = (datalayer.battery.status.current_dA & 0x00FF);
// BMS Temperature (We dont have BMS temp, send max cell voltage instead)
#ifdef INVERT_LOW_HIGH_BYTES //Useful for Sofar inverters
PYLON_4210.data.u8[4] = ((datalayer.battery.status.temperature_max_dC + 1000) & 0x00FF);
PYLON_4210.data.u8[5] = ((datalayer.battery.status.temperature_max_dC + 1000) >> 8);
PYLON_4211.data.u8[4] = ((datalayer.battery.status.temperature_max_dC + 1000) & 0x00FF);
PYLON_4211.data.u8[5] = ((datalayer.battery.status.temperature_max_dC + 1000) >> 8);
#else
PYLON_4210.data.u8[4] = ((datalayer.battery.status.temperature_max_dC + 1000) >> 8);
PYLON_4210.data.u8[5] = ((datalayer.battery.status.temperature_max_dC + 1000) & 0x00FF);
PYLON_4211.data.u8[4] = ((datalayer.battery.status.temperature_max_dC + 1000) >> 8);
PYLON_4211.data.u8[5] = ((datalayer.battery.status.temperature_max_dC + 1000) & 0x00FF);
#endif
//SOC (100.00%)
PYLON_4210.data.u8[6] = (datalayer.battery.status.reported_soc / 100); //Remove decimals
PYLON_4211.data.u8[6] = (datalayer.battery.status.reported_soc / 100); //Remove decimals
//StateOfHealth (100.00%)
PYLON_4210.data.u8[7] = (datalayer.battery.status.soh_pptt / 100);
PYLON_4211.data.u8[7] = (datalayer.battery.status.soh_pptt / 100);
// Status=Bit 0,1,2= 0:Sleep, 1:Charge, 2:Discharge 3:Idle. Bit3 ForceChargeReq. Bit4 Balance charge Request
if (datalayer.battery.status.bms_status == FAULT) {
PYLON_4251.data.u8[0] = (0x00); // Sleep
} else if (datalayer.battery.status.current_dA < 0) {
PYLON_4251.data.u8[0] = (0x01); // Charge
} else if (datalayer.battery.status.current_dA > 0) {
PYLON_4251.data.u8[0] = (0x02); // Discharge
} else if (datalayer.battery.status.current_dA == 0) {
PYLON_4251.data.u8[0] = (0x03); // Idle
}
#ifdef INVERT_LOW_HIGH_BYTES //Useful for Sofar inverters
//Voltage (370.0)
PYLON_4210.data.u8[0] = (datalayer.battery.status.voltage_dV & 0x00FF);
PYLON_4210.data.u8[1] = (datalayer.battery.status.voltage_dV >> 8);
PYLON_4211.data.u8[0] = (datalayer.battery.status.voltage_dV & 0x00FF);
PYLON_4211.data.u8[1] = (datalayer.battery.status.voltage_dV >> 8);
#ifdef SET_30K_OFFSET
//Current (15.0)
PYLON_4210.data.u8[2] = ((datalayer.battery.status.current_dA + 30000) & 0x00FF);
PYLON_4210.data.u8[3] = ((datalayer.battery.status.current_dA + 30000) >> 8);
PYLON_4211.data.u8[2] = ((datalayer.battery.status.current_dA + 30000) & 0x00FF);
PYLON_4211.data.u8[3] = ((datalayer.battery.status.current_dA + 30000) >> 8);
#else
PYLON_4210.data.u8[2] = (datalayer.battery.status.current_dA & 0x00FF);
PYLON_4210.data.u8[3] = (datalayer.battery.status.current_dA >> 8);
PYLON_4211.data.u8[2] = (datalayer.battery.status.current_dA & 0x00FF);
PYLON_4211.data.u8[3] = (datalayer.battery.status.current_dA >> 8);
#endif
// BMS Temperature (We dont have BMS temp, send max cell voltage instead)
PYLON_4210.data.u8[4] = ((datalayer.battery.status.temperature_max_dC + 1000) & 0x00FF);
PYLON_4210.data.u8[5] = ((datalayer.battery.status.temperature_max_dC + 1000) >> 8);
PYLON_4211.data.u8[4] = ((datalayer.battery.status.temperature_max_dC + 1000) & 0x00FF);
PYLON_4211.data.u8[5] = ((datalayer.battery.status.temperature_max_dC + 1000) >> 8);
//Maxvoltage (eg 400.0V = 4000 , 16bits long) Discharge Cutoff Voltage
PYLON_4220.data.u8[0] = (datalayer.battery.info.max_design_voltage_dV & 0x00FF);
PYLON_4220.data.u8[1] = (datalayer.battery.info.max_design_voltage_dV >> 8);
PYLON_4221.data.u8[0] = (datalayer.battery.info.max_design_voltage_dV & 0x00FF);
PYLON_4221.data.u8[1] = (datalayer.battery.info.max_design_voltage_dV >> 8);
//Minvoltage (eg 300.0V = 3000 , 16bits long) Charge Cutoff Voltage
PYLON_4220.data.u8[2] = (datalayer.battery.info.min_design_voltage_dV & 0x00FF);
PYLON_4220.data.u8[3] = (datalayer.battery.info.min_design_voltage_dV >> 8);
PYLON_4221.data.u8[2] = (datalayer.battery.info.min_design_voltage_dV & 0x00FF);
PYLON_4221.data.u8[3] = (datalayer.battery.info.min_design_voltage_dV >> 8);
#ifdef SET_30K_OFFSET
//Max ChargeCurrent
PYLON_4220.data.u8[4] = ((datalayer.battery.status.max_charge_current_dA + 30000) & 0x00FF);
PYLON_4220.data.u8[5] = ((datalayer.battery.status.max_charge_current_dA + 30000) >> 8);
PYLON_4221.data.u8[4] = ((datalayer.battery.status.max_charge_current_dA + 30000) & 0x00FF);
PYLON_4221.data.u8[5] = ((datalayer.battery.status.max_charge_current_dA + 30000) >> 8);
//Max DischargeCurrent
PYLON_4220.data.u8[6] = ((30000 - datalayer.battery.status.max_discharge_current_dA) & 0x00FF);
PYLON_4220.data.u8[7] = ((30000 - datalayer.battery.status.max_discharge_current_dA) >> 8);
PYLON_4221.data.u8[6] = ((30000 - datalayer.battery.status.max_discharge_current_dA) & 0x00FF);
PYLON_4221.data.u8[7] = ((30000 - datalayer.battery.status.max_discharge_current_dA) >> 8);
#else
//Max ChargeCurrent
PYLON_4220.data.u8[4] = (datalayer.battery.status.max_charge_current_dA & 0x00FF);
PYLON_4220.data.u8[5] = (datalayer.battery.status.max_charge_current_dA >> 8);
PYLON_4221.data.u8[4] = (datalayer.battery.status.max_charge_current_dA & 0x00FF);
PYLON_4221.data.u8[5] = (datalayer.battery.status.max_charge_current_dA >> 8);
//Max DishargeCurrent
PYLON_4220.data.u8[6] = (datalayer.battery.status.max_discharge_current_dA & 0x00FF);
PYLON_4220.data.u8[7] = (datalayer.battery.status.max_discharge_current_dA >> 8);
PYLON_4221.data.u8[6] = (datalayer.battery.status.max_discharge_current_dA & 0x00FF);
PYLON_4221.data.u8[7] = (datalayer.battery.status.max_discharge_current_dA >> 8);
#endif
//Max cell voltage
PYLON_4230.data.u8[0] = (cell_tweaked_max_voltage_mV & 0x00FF);
PYLON_4230.data.u8[1] = (cell_tweaked_max_voltage_mV >> 8);
PYLON_4231.data.u8[0] = (cell_tweaked_max_voltage_mV & 0x00FF);
PYLON_4231.data.u8[1] = (cell_tweaked_max_voltage_mV >> 8);
//Min cell voltage
PYLON_4230.data.u8[2] = (cell_tweaked_min_voltage_mV & 0x00FF);
PYLON_4230.data.u8[3] = (cell_tweaked_min_voltage_mV >> 8);
PYLON_4231.data.u8[2] = (cell_tweaked_min_voltage_mV & 0x00FF);
PYLON_4231.data.u8[3] = (cell_tweaked_min_voltage_mV >> 8);
//Max temperature per cell
PYLON_4240.data.u8[0] = (datalayer.battery.status.temperature_max_dC & 0x00FF);
PYLON_4240.data.u8[1] = (datalayer.battery.status.temperature_max_dC >> 8);
PYLON_4241.data.u8[0] = (datalayer.battery.status.temperature_max_dC & 0x00FF);
PYLON_4241.data.u8[1] = (datalayer.battery.status.temperature_max_dC >> 8);
//Max/Min temperature per cell
PYLON_4240.data.u8[2] = (datalayer.battery.status.temperature_min_dC & 0x00FF);
PYLON_4240.data.u8[3] = (datalayer.battery.status.temperature_min_dC >> 8);
PYLON_4240.data.u8[2] = (datalayer.battery.status.temperature_min_dC & 0x00FF);
PYLON_4240.data.u8[3] = (datalayer.battery.status.temperature_min_dC >> 8);
//Max temperature per module
PYLON_4270.data.u8[0] = (datalayer.battery.status.temperature_max_dC & 0x00FF);
PYLON_4270.data.u8[1] = (datalayer.battery.status.temperature_max_dC >> 8);
PYLON_4271.data.u8[0] = (datalayer.battery.status.temperature_max_dC & 0x00FF);
PYLON_4271.data.u8[1] = (datalayer.battery.status.temperature_max_dC >> 8);
//Min temperature per module
PYLON_4270.data.u8[2] = (datalayer.battery.status.temperature_min_dC & 0x00FF);
PYLON_4270.data.u8[3] = (datalayer.battery.status.temperature_min_dC >> 8);
PYLON_4271.data.u8[2] = (datalayer.battery.status.temperature_min_dC & 0x00FF);
PYLON_4271.data.u8[3] = (datalayer.battery.status.temperature_min_dC >> 8);
#else
//Voltage (370.0)
PYLON_4210.data.u8[0] = (datalayer.battery.status.voltage_dV >> 8;
PYLON_4210.data.u8[1] = (datalayer.battery.status.voltage_dV & 0x00FF);
PYLON_4211.data.u8[0] = (datalayer.battery.status.voltage_dV >> 8);
PYLON_4211.data.u8[1] = (datalayer.battery.status.voltage_dV & 0x00FF);
#ifdef SET_30K_OFFSET
//Current (15.0)
PYLON_4210.data.u8[2] = ((datalayer.battery.status.current_dA + 30000) >> 8);
PYLON_4210.data.u8[3] = ((datalayer.battery.status.current_dA + 30000) & 0x00FF);
PYLON_4211.data.u8[2] = ((datalayer.battery.status.current_dA + 30000) >> 8);
PYLON_4211.data.u8[3] = ((datalayer.battery.status.current_dA + 30000) & 0x00FF);
#else
PYLON_4210.data.u8[2] = (datalayer.battery.status.current_dA >> 8);
PYLON_4210.data.u8[3] = (datalayer.battery.status.current_dA & 0x00FF);
PYLON_4211.data.u8[2] = (datalayer.battery.status.current_dA >> 8);
PYLON_4211.data.u8[3] = (datalayer.battery.status.current_dA & 0x00FF);
#endif
// BMS Temperature (We dont have BMS temp, send max cell voltage instead)
PYLON_4210.data.u8[4] = ((datalayer.battery.status.temperature_max_dC + 1000) >> 8);
PYLON_4210.data.u8[5] = ((datalayer.battery.status.temperature_max_dC + 1000) & 0x00FF);
PYLON_4211.data.u8[4] = ((datalayer.battery.status.temperature_max_dC + 1000) >> 8);
PYLON_4211.data.u8[5] = ((datalayer.battery.status.temperature_max_dC + 1000) & 0x00FF);
//Maxvoltage (eg 400.0V = 4000 , 16bits long) Discharge Cutoff Voltage
PYLON_4220.data.u8[0] = (datalayer.battery.info.max_design_voltage_dV >> 8);
PYLON_4220.data.u8[1] = (datalayer.battery.info.max_design_voltage_dV & 0x00FF);
PYLON_4221.data.u8[0] = (datalayer.battery.info.max_design_voltage_dV >> 8);
PYLON_4221.data.u8[1] = (datalayer.battery.info.max_design_voltage_dV & 0x00FF);
//Minvoltage (eg 300.0V = 3000 , 16bits long) Charge Cutoff Voltage
PYLON_4220.data.u8[2] = (datalayer.battery.info.min_design_voltage_dV >> 8);
PYLON_4220.data.u8[3] = (datalayer.battery.info.min_design_voltage_dV & 0x00FF);
PYLON_4221.data.u8[2] = (datalayer.battery.info.min_design_voltage_dV >> 8);
PYLON_4221.data.u8[3] = (datalayer.battery.info.min_design_voltage_dV & 0x00FF);
#ifdef SET_30K_OFFSET
//Max ChargeCurrent
PYLON_4220.data.u8[4] = ((max_charge_current + 30000) >> 8);
PYLON_4220.data.u8[5] = ((max_charge_current + 30000) & 0x00FF);
PYLON_4221.data.u8[4] = ((max_charge_current + 30000) >> 8);
PYLON_4221.data.u8[5] = ((max_charge_current + 30000) & 0x00FF);
//Max DischargeCurrent
PYLON_4220.data.u8[6] = ((30000 - max_discharge_current) >> 8);
PYLON_4220.data.u8[7] = ((30000 - max_discharge_current) & 0x00FF);
PYLON_4221.data.u8[6] = ((30000 - max_discharge_current) >> 8);
PYLON_4221.data.u8[7] = ((30000 - max_discharge_current) & 0x00FF);
#else
//Max ChargeCurrent
PYLON_4220.data.u8[4] = (max_charge_current >> 8);
PYLON_4220.data.u8[5] = (max_charge_current & 0x00FF);
PYLON_4221.data.u8[4] = (max_charge_current >> 8);
PYLON_4221.data.u8[5] = (max_charge_current & 0x00FF);
//Max DishargeCurrent
PYLON_4220.data.u8[6] = (max_discharge_current >> 8);
PYLON_4220.data.u8[7] = (max_discharge_current & 0x00FF);
PYLON_4221.data.u8[6] = (max_discharge_current >> 8);
PYLON_4221.data.u8[7] = (max_discharge_current & 0x00FF);
#endif
//Max cell voltage
PYLON_4230.data.u8[0] = (cell_tweaked_max_voltage_mV >> 8);
PYLON_4230.data.u8[1] = (cell_tweaked_max_voltage_mV & 0x00FF);
PYLON_4231.data.u8[0] = (cell_tweaked_max_voltage_mV >> 8);
PYLON_4231.data.u8[1] = (cell_tweaked_max_voltage_mV & 0x00FF);
//Min cell voltage
PYLON_4230.data.u8[2] = (cell_tweaked_min_voltage_mV >> 8);
PYLON_4230.data.u8[3] = (cell_tweaked_min_voltage_mV & 0x00FF);
PYLON_4231.data.u8[2] = (cell_tweaked_min_voltage_mV >> 8);
PYLON_4231.data.u8[3] = (cell_tweaked_min_voltage_mV & 0x00FF);
//Max temperature per cell
PYLON_4240.data.u8[0] = (datalayer.battery.status.temperature_max_dC >> 8);
PYLON_4240.data.u8[1] = (datalayer.battery.status.temperature_max_dC & 0x00FF);
PYLON_4241.data.u8[0] = (datalayer.battery.status.temperature_max_dC >> 8);
PYLON_4241.data.u8[1] = (datalayer.battery.status.temperature_max_dC & 0x00FF);
//Max/Min temperature per cell
PYLON_4240.data.u8[2] = (datalayer.battery.status.temperature_min_dC >> 8);
PYLON_4240.data.u8[3] = (datalayer.battery.status.temperature_min_dC & 0x00FF);
PYLON_4240.data.u8[2] = (datalayer.battery.status.temperature_min_dC >> 8);
PYLON_4240.data.u8[3] = (datalayer.battery.status.temperature_min_dC & 0x00FF);
//Max temperature per module
PYLON_4270.data.u8[0] = (datalayer.battery.status.temperature_max_dC >> 8);
PYLON_4270.data.u8[1] = (datalayer.battery.status.temperature_max_dC & 0x00FF);
PYLON_4271.data.u8[0] = (datalayer.battery.status.temperature_max_dC >> 8);
PYLON_4271.data.u8[1] = (datalayer.battery.status.temperature_max_dC & 0x00FF);
//Min temperature per module
PYLON_4270.data.u8[2] = (datalayer.battery.status.temperature_min_dC >> 8);
PYLON_4270.data.u8[3] = (datalayer.battery.status.temperature_min_dC & 0x00FF);
PYLON_4271.data.u8[2] = (datalayer.battery.status.temperature_min_dC >> 8);
PYLON_4271.data.u8[3] = (datalayer.battery.status.temperature_min_dC & 0x00FF);
#endif
//Max/Min cell voltage
PYLON_4230.data.u8[0] = (cell_tweaked_max_voltage_mV >> 8);
PYLON_4230.data.u8[1] = (cell_tweaked_max_voltage_mV & 0x00FF);
PYLON_4230.data.u8[2] = (cell_tweaked_min_voltage_mV >> 8);
PYLON_4230.data.u8[3] = (cell_tweaked_min_voltage_mV & 0x00FF);
//Max/Min temperature per cell
PYLON_4240.data.u8[0] = (datalayer.battery.status.temperature_max_dC >> 8);
PYLON_4240.data.u8[1] = (datalayer.battery.status.temperature_max_dC & 0x00FF);
PYLON_4240.data.u8[2] = (datalayer.battery.status.temperature_min_dC >> 8);
PYLON_4240.data.u8[3] = (datalayer.battery.status.temperature_min_dC & 0x00FF);
//Max/Min temperature per module
PYLON_4270.data.u8[0] = (datalayer.battery.status.temperature_max_dC >> 8);
PYLON_4270.data.u8[1] = (datalayer.battery.status.temperature_max_dC & 0x00FF);
PYLON_4270.data.u8[2] = (datalayer.battery.status.temperature_min_dC >> 8);
PYLON_4270.data.u8[3] = (datalayer.battery.status.temperature_min_dC & 0x00FF);
//In case we run into any errors/faults, we can set charge / discharge forbidden
if (datalayer.battery.status.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 map_can_frame_to_variable_inverter(CAN_frame rx_frame) {
switch (rx_frame.ID) {
case 0x4200: //Message originating from inverter. Depending on which data is required, act accordingly
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
if (rx_frame.data.u8[0] == 0x02) {
send_setup_info();
}
if (rx_frame.data.u8[0] == 0x00) {
send_system_data();
}
break;
default:
break;
}
}
void transmit_can_inverter() {
// No periodic sending, we only react on received can messages
}
void send_setup_info() { //Ensemble information
#ifdef SEND_0
transmit_can_frame(&PYLON_7310, can_config.inverter);
transmit_can_frame(&PYLON_7320, can_config.inverter);
#endif
#ifdef SEND_1
transmit_can_frame(&PYLON_7311, can_config.inverter);
transmit_can_frame(&PYLON_7321, can_config.inverter);
#endif
}
void send_system_data() { //System equipment information
#ifdef SEND_0
transmit_can_frame(&PYLON_4210, can_config.inverter);
transmit_can_frame(&PYLON_4220, can_config.inverter);
transmit_can_frame(&PYLON_4230, can_config.inverter);
transmit_can_frame(&PYLON_4240, can_config.inverter);
transmit_can_frame(&PYLON_4250, can_config.inverter);
transmit_can_frame(&PYLON_4260, can_config.inverter);
transmit_can_frame(&PYLON_4270, can_config.inverter);
transmit_can_frame(&PYLON_4280, can_config.inverter);
transmit_can_frame(&PYLON_4290, can_config.inverter);
#endif
#ifdef SEND_1
transmit_can_frame(&PYLON_4211, can_config.inverter);
transmit_can_frame(&PYLON_4221, can_config.inverter);
transmit_can_frame(&PYLON_4231, can_config.inverter);
transmit_can_frame(&PYLON_4241, can_config.inverter);
transmit_can_frame(&PYLON_4251, can_config.inverter);
transmit_can_frame(&PYLON_4261, can_config.inverter);
transmit_can_frame(&PYLON_4271, can_config.inverter);
transmit_can_frame(&PYLON_4281, can_config.inverter);
transmit_can_frame(&PYLON_4291, can_config.inverter);
#endif
}
void setup_inverter(void) { // Performs one time setup at startup over CAN bus
strncpy(datalayer.system.info.inverter_protocol, "Ferroamp Pylon battery over CAN bus", 63);
datalayer.system.info.inverter_protocol[63] = '\0';
}
#endif

View file

@ -0,0 +1,12 @@
#ifndef FERROAMP_CAN_H
#define FERROAMP_CAN_H
#include "../include.h"
#define CAN_INVERTER_SELECTED
void send_system_data();
void send_setup_info();
void transmit_can_frame(CAN_frame* tx_frame, int interface);
void setup_inverter(void);
#endif

View file

@ -164,11 +164,16 @@ void update_values_can_inverter() { //This function maps all the values fetched
}
//Map values to CAN messages
//Battery operating parameters and status information
//Recommended charge voltage (eg 400.0V = 4000 , 16bits long) (MIN 0, MAX 1000V)
GROWATT_3110.data.u8[0] = (datalayer.battery.info.max_design_voltage_dV >> 8);
GROWATT_3110.data.u8[1] = (datalayer.battery.info.max_design_voltage_dV & 0x00FF);
if (datalayer.battery.settings.user_set_voltage_limits_active) { //If user is requesting a specific voltage
//User specified charge voltage (eg 400.0V = 4000 , 16bits long) (MIN 0, MAX 1000V)
GROWATT_3110.data.u8[0] = (datalayer.battery.settings.max_user_set_charge_voltage_dV >> 8);
GROWATT_3110.data.u8[1] = (datalayer.battery.settings.max_user_set_charge_voltage_dV & 0x00FF);
} else {
//Battery max voltage used as charge voltage (eg 400.0V = 4000 , 16bits long) (MIN 0, MAX 1000V)
GROWATT_3110.data.u8[0] = (datalayer.battery.info.max_design_voltage_dV >> 8);
GROWATT_3110.data.u8[1] = (datalayer.battery.info.max_design_voltage_dV & 0x00FF);
}
//Charge limited current, 125 =12.5A (0.1, A) (Min 0, Max 300A)
GROWATT_3110.data.u8[2] = (datalayer.battery.status.max_charge_current_dA >> 8);
GROWATT_3110.data.u8[3] = (datalayer.battery.status.max_charge_current_dA & 0x00FF);
@ -239,9 +244,15 @@ void update_values_can_inverter() { //This function maps all the values fetched
GROWATT_3140.data.u8[7] = 0;
//Battery working parameters and module number information
//Discharge cutoff voltage (0.1V) [0-1000V]
GROWATT_3150.data.u8[0] = (datalayer.battery.info.min_design_voltage_dV >> 8);
GROWATT_3150.data.u8[1] = (datalayer.battery.info.min_design_voltage_dV & 0x00FF);
if (datalayer.battery.settings.user_set_voltage_limits_active) { //If user is requesting a specific voltage
//Use user specified voltage as Discharge cutoff voltage (0.1V) [0-1000V]
GROWATT_3150.data.u8[0] = (datalayer.battery.settings.max_user_set_discharge_voltage_dV >> 8);
GROWATT_3150.data.u8[1] = (datalayer.battery.settings.max_user_set_discharge_voltage_dV & 0x00FF);
} else {
//Use battery min design voltage as Discharge cutoff voltage (0.1V) [0-1000V]
GROWATT_3150.data.u8[0] = (datalayer.battery.info.min_design_voltage_dV >> 8);
GROWATT_3150.data.u8[1] = (datalayer.battery.info.min_design_voltage_dV & 0x00FF);
}
//Main control unit temperature (0.1C) [-40 to 120*C]
GROWATT_3150.data.u8[2] = (datalayer.battery.status.temperature_max_dC >> 8);
GROWATT_3150.data.u8[3] = (datalayer.battery.status.temperature_max_dC & 0x00FF);

View file

@ -19,6 +19,10 @@
#include "KOSTAL-RS485.h"
#endif
#ifdef FERROAMP_CAN
#include "FERROAMP-CAN.h"
#endif
#ifdef FOXESS_CAN
#include "FOXESS-CAN.h"
#endif

View file

@ -27,59 +27,44 @@ union f32b {
byte b[4];
};
uint8_t frame1[40] = {0x06, 0xE2, 0xFF, 0x02, 0xFF, 0x29, // Frame header
0x01, 0x08, 0x80, 0x43, // 256.063 Nominal voltage / 5*51.2=256 first byte 0x01 or 0x04
0xE4, 0x70, 0x8A, 0x5C, // These might be Umin & Unax, Uint16
0xB5, 0x02, 0xD3, 0x01, // Battery Serial number? Modbus register 527
0x01, 0x05, 0xC8, 0x41, // 25.0024 ?
0xC2, 0x18, // Battery Firmware, modbus register 586
0x01, 0x03, 0x59, 0x42, // 0x00005942 = 54.25 ??
0x01, 0x01, 0x01, 0x02, 0x05, 0x02, 0xA0, 0x01, 0x01, 0x02,
0x4D, // CRC
0x00}; //
uint8_t BATTERY_INFO[40] = {
0x06, 0xE2, 0xFF, 0x02, 0xFF, 0x29, // Frame header
0x01, 0x08, 0x80, 0x43, // 256.063 Nominal voltage / 5*51.2=256 first byte 0x01 or 0x04
0xE4, 0x70, 0x8A, 0x5C, // These might be Umin & Unax, Uint16
0xB5, 0x02, 0xD3, 0x01, // Battery Serial number? Modbus register 527
0x01, 0x05, 0xC8, 0x41, // 25.0024 ?
0xC2, 0x18, // Battery Firmware, modbus register 586
0x01, 0x03, 0x59, 0x42, // 0x00005942 = 54.25 ??
0x01, 0x01, 0x01, 0x02, 0x05, 0x02, 0xA0, 0x01, 0x01, 0x02,
0x4D, // CRC
0x00}; //
// values in frame2 will be overwritten at update_modbus_registers_inverter()
// values in CyclicData will be overwritten at update_modbus_registers_inverter()
uint8_t frame2[64] = {
0x0A, // This may also been 0x06, seen at startup when live values not valid, but also occasionally single frames.
uint8_t CyclicData[64] = {
0x00, // First zero byte pointer
0xE2, 0xFF, 0x02, 0xFF, 0x29, // frame Header
0x1D, 0x5A, 0x85, 0x43, // Current Voltage (float) Modbus register 216, Bytes 6-9
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
0x00, 0x00, 0x00, 0x00, // Avg current (1s period?), Bytes 22-25
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
0x00, 0x00, 0xA0, 0x41, // Max charge current (2 byte float) Bit 36-37, ZERO WHEN SOC=100 // Sunspec: AChaMax
0xCD, 0xCC, 0xB4, 0x41, // MaxCellTemp (4 byte float) Bit 38-41
0x00, 0x00, 0xA4, 0x41, // MinCellTemp (4 byte float) Bit 42-45
0xA4, 0x70, 0x55, 0x40, // MaxCellVolt (float), Bit 46-49
0x7D, 0x3F, 0x55, 0x40, // MinCellVolt (float), Bit 50-53
0x1D, 0x5A, 0x85, 0x43, // Current Voltage (float) Modbus register 216, Bytes 6-9
0x01, 0x03, // Unknown, 0x03 seen also 0x0F, 0x07, might hava something to do with current
0x8D, 0x43, // Max Voltage (2 byte float), Bytes 12-13
0x01, 0x03, 0xAC, 0x41, // BAttery Temperature (2 byte float) Modbus register 214, Bytes 16-17
0x01, 0x01, 0x01,
0x01, // Peak Current (1s period?), Bytes 18-21 - Communication fault seen with some values (>10A?)
0x01, 0x01, 0x01,
0x01, // Avg current (1s period?), Bytes 22-25 - Communication fault seen with some values (>10A?)
0x01, 0x03, 0x48, 0x42, // Max discharge current (2 byte float), Bit 26-29,
// Sunspec: ADisChaMax
0x01, 0x03, // Unknown
0xC8, 0x41, // Battery gross capacity, Ah (2 byte float) , Bytes 30-33, Modbus 512
0x01, // Unknown
0x16, // This seems to have something to do with cell temperatures
0xA0, 0x41, // Max charge current (2 byte float) Bit 36-37, ZERO WHEN SOC=100
// Sunspec: AChaMax
0xCD, 0xCC, 0xB4, 0x41, // MaxCellTemp (4 byte float) Bit 38-41
0x01, 0x0C, 0xA4, 0x41, // MinCellTemp (4 byte float) Bit 42-45
0xA4, 0x70, 0x55, 0x40, // MaxCellVolt (float), Bit 46-49
0x7D, 0x3F, 0x55, 0x40, // MinCellVolt (float), Bit 50-53
0xFE, // Cylce count , Bit 54
0x04, // Cycle count? , Bit 55
0x01, // Byte 56
0x40, // When SOC=100 Byte57=0x40, at startup 0x03 (about 7 times), otherwise 0x02
0x64, // SOC , Bit 58
0x01, // Unknown, when byte 57 = 0x03, this 0x02, otherwise 0x01
0x01, // Unknown, Seen only 0x01
0x02, // Unknown, Mostly 0x02. seen also 0x01
0x00, // CRC (inverted sum of bytes 1-62 + 0xC0), Bit 62
0xFE, 0x04, // Cycle count,
0x00, // Byte 56
0x40, // When SOC=100 Byte57=0x40, at startup 0x03 (about 7 times), otherwise 0x02
0x64, // SOC , Bit 58
0x00, // Unknown,
0x00, // Unknown,
0x02, // Unknown, Mostly 0x02. seen also 0x01
0x00, // CRC (inverted sum of bytes 1-62 + 0xC0), Bit 62
0x00};
// FE 04 01 40 xx 01 01 02 yy (fully charged)
@ -97,7 +82,7 @@ uint8_t frame4[8] = {0x07, 0xE3, 0xFF, 0x02, 0xFF, 0x29, 0xF4, 0x00};
uint8_t frameB1[10] = {0x07, 0x63, 0xFF, 0x02, 0xFF, 0x29, 0x5E, 0x02, 0x16, 0x00};
uint8_t frameB1b[8] = {0x07, 0xE3, 0xFF, 0x02, 0xFF, 0x29, 0xF4, 0x00};
uint8_t RS485_RXFRAME[10];
uint8_t RS485_RXFRAME[300];
bool register_content_ok = false;
@ -110,13 +95,6 @@ void float2frame(byte* arr, float value, byte framepointer) {
arr[framepointer + 3] = g.b[3];
}
void float2frameMSB(byte* arr, float value, byte framepointer) {
f32b g;
g.f = value;
arr[framepointer + 0] = g.b[2];
arr[framepointer + 1] = g.b[3];
}
static void dbg_timestamp(void) {
#ifdef DEBUG_KOSTAL_RS485_DATA
logging.print("[");
@ -148,17 +126,32 @@ static void dbg_message(const char* msg) {
#endif
}
/* https://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing#Encoding_examples */
void null_stuffer(byte* lfc, int len) {
int last_null_byte = 0;
for (int i = 0; i < len; i++) {
if (lfc[i] == '\0') {
lfc[last_null_byte] = (byte)(i - last_null_byte);
last_null_byte = i;
}
}
}
static void send_kostal(byte* frame, int len) {
dbg_frame(frame, len, "TX");
Serial2.write(frame, len);
}
byte calculate_longframe_crc(byte* lfc, int lastbyte) {
byte calculate_kostal_crc(byte* lfc, int len) {
unsigned int sum = 0;
for (int i = 0; i < lastbyte; ++i) {
if (lfc[0] != 0) {
logging.printf("WARNING: first byte should be 0, but is 0x%02x\n", lfc[0]);
}
for (int i = 1; i < len; i++) {
sum += lfc[i];
}
return ((byte) ~(sum + 0xc0) & 0xff);
return (byte)(-sum & 0xff);
}
byte calculate_frame1_crc(byte* lfc, int lastbyte) {
@ -191,60 +184,65 @@ void update_RS485_registers_inverter() {
if (datalayer.system.status.battery_allows_contactor_closing &
datalayer.system.status.inverter_allows_contactor_closing) {
float2frame(frame2, (float)datalayer.battery.status.voltage_dV / 10, 6); // Confirmed OK mapping
frame2[0] = 0x0A;
float2frame(CyclicData, (float)datalayer.battery.status.voltage_dV / 10, 6); // Confirmed OK mapping
CyclicData[0] = 0x0A;
} else {
frame2[0] = 0x06;
float2frame(frame2, 0.0, 6);
CyclicData[0] = 0x06;
float2frame(CyclicData, 0.0, 6);
}
// Set nominal voltage to value between min and max voltage set by battery (Example 400 and 300 results in 350V)
nominal_voltage_dV =
(((datalayer.battery.info.max_design_voltage_dV - datalayer.battery.info.min_design_voltage_dV) / 2) +
datalayer.battery.info.min_design_voltage_dV);
float2frameMSB(frame1, (float)nominal_voltage_dV / 10, 8);
float2frame(BATTERY_INFO, (float)nominal_voltage_dV / 10, 6);
float2frameMSB(frame2, (float)datalayer.battery.info.max_design_voltage_dV / 10, 12);
float2frame(CyclicData, (float)datalayer.battery.info.max_design_voltage_dV / 10, 10);
float2frameMSB(frame2, (float)average_temperature_dC / 10, 16);
float2frame(CyclicData, (float)average_temperature_dC / 10, 14);
// Some current values causes communication error, must be resolved, why.
// float2frameMSB(frame2, (float)datalayer.battery.status.current_dA / 10, 20); // Peak discharge? current (2 byte float)
// float2frameMSB(frame2, (float)datalayer.battery.status.current_dA / 10, 24);
// float2frame(CyclicData, (float)datalayer.battery.status.current_dA / 10, 18); // Peak discharge? current (2 byte float)
// float2frame(CyclicData, (float)datalayer.battery.status.current_dA / 10, 22);
float2frameMSB(frame2, (float)datalayer.battery.status.max_discharge_current_dA / 10, 28); // BAttery capacity Ah
float2frameMSB(frame2, (float)datalayer.battery.status.max_discharge_current_dA / 10, 32);
float2frame(CyclicData, (float)datalayer.battery.status.max_discharge_current_dA / 10, 26);
// When SOC = 100%, drop down allowed charge current down.
if ((datalayer.battery.status.reported_soc / 100) < 100) {
float2frameMSB(frame2, (float)datalayer.battery.status.max_charge_current_dA / 10, 36);
frame2[57] = 0x02;
frame2[59] = 0x01;
float2frame(CyclicData, (float)datalayer.battery.status.max_charge_current_dA / 10, 34);
} else {
float2frameMSB(frame2, 0.0, 36);
//frame2[57]=0x40;
frame2[57] = 0x02;
frame2[59] = 0x01;
float2frame(CyclicData, 0.0, 34);
}
// On startup, byte 57 seems to be always 0x03 couple of frames,.
// On startup, byte 56 seems to be always 0x00 couple of frames,.
if (f2_startup_count < 9) {
CyclicData[56] = 0x00;
} else {
CyclicData[56] = 0x01;
}
// On startup, byte 59 seems to be always 0x02 couple of frames,.
if (f2_startup_count < 14) {
frame2[57] = 0x03;
frame2[59] = 0x02;
CyclicData[59] = 0x02;
} else {
CyclicData[59] = 0x00;
}
float2frame(frame2, (float)datalayer.battery.status.temperature_max_dC / 10, 38);
float2frame(frame2, (float)datalayer.battery.status.temperature_min_dC / 10, 42);
if (nominal_voltage_dV > 0) {
float2frame(CyclicData, (float)(datalayer.battery.info.total_capacity_Wh / nominal_voltage_dV * 10),
30); // BAttery capacity Ah
}
float2frame(CyclicData, (float)datalayer.battery.status.temperature_max_dC / 10, 38);
float2frame(CyclicData, (float)datalayer.battery.status.temperature_min_dC / 10, 42);
float2frame(frame2, (float)datalayer.battery.status.cell_max_voltage_mV / 1000, 46);
float2frame(frame2, (float)datalayer.battery.status.cell_min_voltage_mV / 1000, 50);
float2frame(CyclicData, (float)datalayer.battery.status.cell_max_voltage_mV / 1000, 46);
float2frame(CyclicData, (float)datalayer.battery.status.cell_min_voltage_mV / 1000, 50);
frame2[58] = (byte)(datalayer.battery.status.reported_soc / 100); // Confirmed OK mapping
CyclicData[58] = (byte)(datalayer.battery.status.reported_soc / 100); // Confirmed OK mapping
register_content_ok = true;
frame1[38] = calculate_frame1_crc(frame1, 38);
BATTERY_INFO[38] = calculate_frame1_crc(BATTERY_INFO, 38);
if (incoming_message_counter > 0) {
incoming_message_counter--;
@ -328,7 +326,7 @@ void receive_RS485() // Runs as fast as possible to handle the serial stream
}
if (headerA && (RS485_RXFRAME[6] == 0x4A) && (RS485_RXFRAME[7] == 0x08)) { // "frame 1"
send_kostal(frame1, 40);
send_kostal(BATTERY_INFO, 40);
if (!startupMillis) {
startupMillis = currentMillis;
}
@ -340,13 +338,10 @@ void receive_RS485() // Runs as fast as possible to handle the serial stream
f2_startup_count++;
}
byte tmpframe[64]; //copy values to prevent data manipulation during rewrite/crc calculation
memcpy(tmpframe, frame2, 64);
for (int i = 1; i < 63; i++) {
if (tmpframe[i] == 0x00) {
tmpframe[i] = 0x01;
}
}
tmpframe[62] = calculate_longframe_crc(tmpframe, 62);
memcpy(tmpframe, CyclicData, 64);
tmpframe[62] = calculate_kostal_crc(tmpframe, 62);
null_stuffer(tmpframe, 64);
send_kostal(tmpframe, 64);
}
if (headerA && (RS485_RXFRAME[6] == 0x53) && (RS485_RXFRAME[7] == 0x03)) { // "frame 3"

View file

@ -4,6 +4,7 @@
#include "../include.h"
#define RS485_INVERTER_SELECTED
#define RS485_BAUDRATE 57600
//#define DEBUG_KOSTAL_RS485_DATA // Enable this line to get TX / RX printed out via logging
#if defined(DEBUG_KOSTAL_RS485_DATA) && !defined(DEBUG_LOG)

View file

@ -310,7 +310,7 @@ void update_values_can_inverter() { //This function maps all the values fetched
PYLON_4271.data.u8[3] = (datalayer.battery.status.temperature_min_dC >> 8);
#else // Not INVERT_LOW_HIGH_BYTES
//Voltage (370.0)
PYLON_4210.data.u8[0] = (datalayer.battery.status.voltage_dV >> 8;
PYLON_4210.data.u8[0] = (datalayer.battery.status.voltage_dV >> 8);
PYLON_4210.data.u8[1] = (datalayer.battery.status.voltage_dV & 0x00FF);
PYLON_4211.data.u8[0] = (datalayer.battery.status.voltage_dV >> 8);
PYLON_4211.data.u8[1] = (datalayer.battery.status.voltage_dV & 0x00FF);
@ -348,28 +348,28 @@ void update_values_can_inverter() { //This function maps all the values fetched
#ifdef SET_30K_OFFSET
//Max ChargeCurrent
PYLON_4220.data.u8[4] = ((max_charge_current + 30000) >> 8);
PYLON_4220.data.u8[5] = ((max_charge_current + 30000) & 0x00FF);
PYLON_4221.data.u8[4] = ((max_charge_current + 30000) >> 8);
PYLON_4221.data.u8[5] = ((max_charge_current + 30000) & 0x00FF);
PYLON_4220.data.u8[4] = ((datalayer.battery.status.max_charge_current_dA + 30000) >> 8);
PYLON_4220.data.u8[5] = ((datalayer.battery.status.max_charge_current_dA + 30000) & 0x00FF);
PYLON_4221.data.u8[4] = ((datalayer.battery.status.max_charge_current_dA + 30000) >> 8);
PYLON_4221.data.u8[5] = ((datalayer.battery.status.max_charge_current_dA + 30000) & 0x00FF);
//Max DischargeCurrent
PYLON_4220.data.u8[6] = ((30000 - max_discharge_current) >> 8);
PYLON_4220.data.u8[7] = ((30000 - max_discharge_current) & 0x00FF);
PYLON_4221.data.u8[6] = ((30000 - max_discharge_current) >> 8);
PYLON_4221.data.u8[7] = ((30000 - max_discharge_current) & 0x00FF);
PYLON_4220.data.u8[6] = ((30000 - datalayer.battery.status.max_discharge_current_dA) >> 8);
PYLON_4220.data.u8[7] = ((30000 - datalayer.battery.status.max_discharge_current_dA) & 0x00FF);
PYLON_4221.data.u8[6] = ((30000 - datalayer.battery.status.max_discharge_current_dA) >> 8);
PYLON_4221.data.u8[7] = ((30000 - datalayer.battery.status.max_discharge_current_dA) & 0x00FF);
#else // Not SET_30K_OFFSET
//Max ChargeCurrent
PYLON_4220.data.u8[4] = (max_charge_current >> 8);
PYLON_4220.data.u8[5] = (max_charge_current & 0x00FF);
PYLON_4221.data.u8[4] = (max_charge_current >> 8);
PYLON_4221.data.u8[5] = (max_charge_current & 0x00FF);
PYLON_4220.data.u8[4] = (datalayer.battery.status.max_charge_current_dA >> 8);
PYLON_4220.data.u8[5] = (datalayer.battery.status.max_charge_current_dA & 0x00FF);
PYLON_4221.data.u8[4] = (datalayer.battery.status.max_charge_current_dA >> 8);
PYLON_4221.data.u8[5] = (datalayer.battery.status.max_charge_current_dA & 0x00FF);
//Max DishargeCurrent
PYLON_4220.data.u8[6] = (max_discharge_current >> 8);
PYLON_4220.data.u8[7] = (max_discharge_current & 0x00FF);
PYLON_4221.data.u8[6] = (max_discharge_current >> 8);
PYLON_4221.data.u8[7] = (max_discharge_current & 0x00FF);
PYLON_4220.data.u8[6] = (datalayer.battery.status.max_discharge_current_dA >> 8);
PYLON_4220.data.u8[7] = (datalayer.battery.status.max_discharge_current_dA & 0x00FF);
PYLON_4221.data.u8[6] = (datalayer.battery.status.max_discharge_current >> 8);
PYLON_4221.data.u8[7] = (datalayer.battery.status.max_discharge_current_dA & 0x00FF);
#endif //SET_30K_OFFSET
//Max cell voltage

View file

@ -39,28 +39,43 @@ CAN_frame PYLON_35E = {.FD = false,
MANUFACTURER_NAME[7],
}};
// when e.g. the min temperature is 0, max is 100 and the warning percent is 80%
// a warning should be generated at 20 (i.e. at 20% of the value range)
// this function calculates this 20% point for a given min/max
int16_t warning_threshold_of_min(int16_t min_val, int16_t max_val) {
int16_t diff = max_val - min_val;
return min_val + (diff * (100 - WARNINGS_PERCENT)) / 100;
}
void update_values_can_inverter() {
// This function maps all the values fetched from battery CAN to the correct CAN messages
// TODO: officially this value is "battery charge voltage". Do we need to add something here to the actual voltage?
PYLON_351.data.u8[0] = datalayer.battery.status.voltage_dV & 0xff;
PYLON_351.data.u8[1] = datalayer.battery.status.voltage_dV >> 8;
// Set "battery charge voltage" to volts + 1 or user supplied value
uint16_t charge_voltage_dV = datalayer.battery.info.max_design_voltage_dV;
if (datalayer.battery.settings.user_set_voltage_limits_active)
charge_voltage_dV = datalayer.battery.settings.max_user_set_charge_voltage_dV;
if (charge_voltage_dV > datalayer.battery.info.max_design_voltage_dV)
charge_voltage_dV = datalayer.battery.info.max_design_voltage_dV;
PYLON_351.data.u8[0] = charge_voltage_dV & 0xff;
PYLON_351.data.u8[1] = charge_voltage_dV >> 8;
PYLON_351.data.u8[2] = datalayer.battery.status.max_charge_current_dA & 0xff;
PYLON_351.data.u8[3] = datalayer.battery.status.max_charge_current_dA >> 8;
PYLON_351.data.u8[4] = datalayer.battery.status.max_discharge_current_dA & 0xff;
PYLON_351.data.u8[5] = datalayer.battery.status.max_discharge_current_dA >> 8;
PYLON_355.data.u8[0] = (datalayer.battery.status.reported_soc / 10) & 0xff;
PYLON_355.data.u8[1] = (datalayer.battery.status.reported_soc / 10) >> 8;
PYLON_355.data.u8[2] = (datalayer.battery.status.soh_pptt / 10) & 0xff;
PYLON_355.data.u8[3] = (datalayer.battery.status.soh_pptt / 10) >> 8;
PYLON_355.data.u8[0] = (datalayer.battery.status.reported_soc / 100) & 0xff;
PYLON_355.data.u8[1] = (datalayer.battery.status.reported_soc / 100) >> 8;
PYLON_355.data.u8[2] = (datalayer.battery.status.soh_pptt / 100) & 0xff;
PYLON_355.data.u8[3] = (datalayer.battery.status.soh_pptt / 100) >> 8;
PYLON_356.data.u8[0] = datalayer.battery.status.voltage_dV & 0xff;
PYLON_356.data.u8[1] = datalayer.battery.status.voltage_dV >> 8;
int16_t voltage_cV = datalayer.battery.status.voltage_dV * 10;
int16_t temperature = (datalayer.battery.status.temperature_min_dC + datalayer.battery.status.temperature_max_dC) / 2;
PYLON_356.data.u8[0] = voltage_cV & 0xff;
PYLON_356.data.u8[1] = voltage_cV >> 8;
PYLON_356.data.u8[2] = datalayer.battery.status.current_dA & 0xff;
PYLON_356.data.u8[3] = datalayer.battery.status.current_dA >> 8;
PYLON_356.data.u8[4] = datalayer.battery.status.temperature_max_dC & 0xff;
PYLON_356.data.u8[5] = datalayer.battery.status.temperature_max_dC >> 8;
PYLON_356.data.u8[4] = temperature & 0xff;
PYLON_356.data.u8[5] = temperature >> 8;
// initialize all errors and warnings to 0
PYLON_359.data.u8[0] = 0x00;
@ -78,20 +93,23 @@ void update_values_can_inverter() {
PYLON_359.data.u8[0] |= 0x10;
if (datalayer.battery.status.temperature_max_dC >= BATTERY_MAXTEMPERATURE)
PYLON_359.data.u8[0] |= 0x0C;
if (datalayer.battery.status.voltage_dV * 100 <= datalayer.battery.info.min_cell_voltage_mV)
if (datalayer.battery.status.voltage_dV <= datalayer.battery.info.min_design_voltage_dV)
PYLON_359.data.u8[0] |= 0x04;
// we never set PYLON_359.data.u8[1] |= 0x80 called "BMS internal"
if (datalayer.battery.status.bms_status == FAULT)
PYLON_359.data.u8[1] |= 0x80;
if (datalayer.battery.status.current_dA <= -1 * datalayer.battery.status.max_charge_current_dA)
PYLON_359.data.u8[1] |= 0x01;
// WARNINGS (using same rules as errors but reporting earlier)
if (datalayer.battery.status.current_dA >= datalayer.battery.status.max_discharge_current_dA * WARNINGS_PERCENT / 100)
PYLON_359.data.u8[2] |= 0x80;
if (datalayer.battery.status.temperature_min_dC <= BATTERY_MINTEMPERATURE * WARNINGS_PERCENT / 100)
if (datalayer.battery.status.temperature_min_dC <=
warning_threshold_of_min(BATTERY_MINTEMPERATURE, BATTERY_MAXTEMPERATURE))
PYLON_359.data.u8[2] |= 0x10;
if (datalayer.battery.status.temperature_max_dC >= BATTERY_MAXTEMPERATURE * WARNINGS_PERCENT / 100)
PYLON_359.data.u8[2] |= 0x0C;
if (datalayer.battery.status.voltage_dV * 100 <= datalayer.battery.info.min_cell_voltage_mV + 100)
if (datalayer.battery.status.voltage_dV <= warning_threshold_of_min(datalayer.battery.info.min_design_voltage_dV,
datalayer.battery.info.max_design_voltage_dV))
PYLON_359.data.u8[2] |= 0x04;
// we never set PYLON_359.data.u8[3] |= 0x80 called "BMS internal"
if (datalayer.battery.status.current_dA <=
@ -99,10 +117,17 @@ void update_values_can_inverter() {
PYLON_359.data.u8[3] |= 0x01;
PYLON_35C.data.u8[0] = 0xC0; // enable charging and discharging
PYLON_35C.data.u8[1] = 0x00;
if (datalayer.battery.status.real_soc <= datalayer.battery.settings.min_percentage)
if (datalayer.battery.status.bms_status == FAULT)
PYLON_35C.data.u8[0] = 0x00; // disable all
else if (datalayer.battery.settings.user_set_voltage_limits_active &&
datalayer.battery.status.voltage_dV > datalayer.battery.settings.max_user_set_charge_voltage_dV)
PYLON_35C.data.u8[0] = 0x40; // only allow discharging
else if (datalayer.battery.settings.user_set_voltage_limits_active &&
datalayer.battery.status.voltage_dV < datalayer.battery.settings.max_user_set_discharge_voltage_dV)
PYLON_35C.data.u8[0] = 0xA0; // enable charing, set charge immediately
if (datalayer.battery.status.real_soc >= datalayer.battery.settings.max_percentage)
else if (datalayer.battery.status.real_soc <= datalayer.battery.settings.min_percentage)
PYLON_35C.data.u8[0] = 0xA0; // enable charing, set charge immediately
else if (datalayer.battery.status.real_soc >= datalayer.battery.settings.max_percentage)
PYLON_35C.data.u8[0] = 0x40; // enable discharging only
// PYLON_35E is pre-filled with the manufacturer name
@ -119,12 +144,35 @@ void map_can_frame_to_variable_inverter(CAN_frame rx_frame) {
}
}
#ifdef DEBUG_VIA_USB
void dump_frame(CAN_frame* frame) {
Serial.print("[PYLON-LV] sending CAN frame ");
Serial.print(frame->ID, HEX);
Serial.print(": ");
for (int i = 0; i < 8; i++) {
Serial.print(frame->data.u8[i] >> 4, HEX);
Serial.print(frame->data.u8[i] & 0xf, HEX);
Serial.print(" ");
}
Serial.println();
}
#endif
void transmit_can_inverter() {
unsigned long currentMillis = millis();
if (currentMillis - previousMillis1000ms >= 1000) {
previousMillis1000ms = currentMillis;
#ifdef DEBUG_VIA_USB
dump_frame(&PYLON_351);
dump_frame(&PYLON_355);
dump_frame(&PYLON_356);
dump_frame(&PYLON_359);
dump_frame(&PYLON_35C);
dump_frame(&PYLON_35E);
#endif
transmit_can_frame(&PYLON_351, can_config.inverter);
transmit_can_frame(&PYLON_355, can_config.inverter);
transmit_can_frame(&PYLON_356, can_config.inverter);

View file

@ -198,10 +198,10 @@ void update_values_can_inverter() { //This function maps all the values fetched
SOLAX_1801.data.u8[4] = 1;
//Ultra messages
SOLAX_187E.data.u8[0] = (uint8_t)datalayer.battery.status.reported_remaining_capacity_Wh;
SOLAX_187E.data.u8[1] = (datalayer.battery.status.reported_remaining_capacity_Wh >> 8);
SOLAX_187E.data.u8[2] = (datalayer.battery.status.reported_remaining_capacity_Wh >> 16);
SOLAX_187E.data.u8[3] = (datalayer.battery.status.reported_remaining_capacity_Wh >> 24);
SOLAX_187E.data.u8[0] = (uint8_t)datalayer.battery.info.total_capacity_Wh;
SOLAX_187E.data.u8[1] = (datalayer.battery.info.total_capacity_Wh >> 8);
SOLAX_187E.data.u8[2] = (datalayer.battery.info.total_capacity_Wh >> 16);
SOLAX_187E.data.u8[3] = (datalayer.battery.info.total_capacity_Wh >> 24);
SOLAX_187E.data.u8[5] = (uint8_t)(datalayer.battery.status.reported_soc / 100);
}

View file

@ -0,0 +1,15 @@
set(COMPONENT_SRCDIRS
"src"
)
set(COMPONENT_ADD_INCLUDEDIRS
"src"
)
set(COMPONENT_REQUIRES
"arduino-esp32"
)
register_component()
target_compile_options(${COMPONENT_TARGET} PRIVATE -fno-rtti)

View file

@ -0,0 +1,129 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socioeconomic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
https://sidweb.nl/cms3/en/contact.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.

View file

@ -0,0 +1,55 @@
![https://avatars.githubusercontent.com/u/195753706?s=96&v=4](https://avatars.githubusercontent.com/u/195753706?s=96&v=4)
# AsyncTCP
[![License: LGPL 3.0](https://img.shields.io/badge/License-LGPL%203.0-yellow.svg)](https://opensource.org/license/lgpl-3-0/)
[![Continuous Integration](https://github.com/ESP32Async/AsyncTCP/actions/workflows/ci.yml/badge.svg)](https://github.com/ESP32Async/AsyncTCP/actions/workflows/ci.yml)
[![PlatformIO Registry](https://badges.registry.platformio.org/packages/ESP32Async/library/AsyncTCP.svg)](https://registry.platformio.org/libraries/ESP32Async/AsyncTCP)
Discord Server: [https://discord.gg/X7zpGdyUcY](https://discord.gg/X7zpGdyUcY)
## Async TCP Library for ESP32 Arduino
This is a fully asynchronous TCP library, aimed at enabling trouble-free, multi-connection network environment for Espressif's ESP32 MCUs.
This library is the base for [ESPAsyncWebServer](https://github.com/ESP32Async/ESPAsyncWebServer)
## How to install
The library can be downloaded from the releases page at [https://github.com/ESP32Async/AsyncTCP/releases](https://github.com/ESP32Async/AsyncTCP/releases).
It is also deployed in these registries:
- Arduino Library Registry: [https://github.com/arduino/library-registry](https://github.com/arduino/library-registry)
- ESP Component Registry [https://components.espressif.com/components/esp32async/asynctcp/](https://components.espressif.com/components/esp32async/asynctcp/)
- PlatformIO Registry: [https://registry.platformio.org/libraries/esp32async/AsyncTCP](https://registry.platformio.org/libraries/esp32async/AsyncTCP)
- Use: `lib_deps=ESP32Async/AsyncTCP` to point to latest version
- Use: `lib_deps=ESP32Async/AsyncTCP @ ^<x.y.z>` to point to latest version with the same major version
- Use: `lib_deps=ESP32Async/AsyncTCP @ <x.y.z>` to always point to the same version (reproductible build)
## AsyncClient and AsyncServer
The base classes on which everything else is built. They expose all possible scenarios, but are really raw and require more skills to use.
## Important recommendations
Most of the crashes are caused by improper configuration of the library for the project.
Here are some recommendations to avoid them.
I personally use the following configuration in my projects:
```c++
-D CONFIG_ASYNC_TCP_MAX_ACK_TIME=5000 // (keep default)
-D CONFIG_ASYNC_TCP_PRIORITY=10 // (keep default)
-D CONFIG_ASYNC_TCP_QUEUE_SIZE=64 // (keep default)
-D CONFIG_ASYNC_TCP_RUNNING_CORE=1 // force async_tcp task to be on same core as the app (default is core 0)
-D CONFIG_ASYNC_TCP_STACK_SIZE=4096 // reduce the stack size (default is 16K)
```
## Compatibility
- ESP32
- Arduino Core 2.x and 3.x

View file

@ -0,0 +1,3 @@
COMPONENT_ADD_INCLUDEDIRS := src
COMPONENT_SRCDIRS := src
CXXFLAGS += -fno-rtti

View file

@ -0,0 +1,31 @@
{
"name": "AsyncTCP",
"version": "3.3.5",
"description": "Asynchronous TCP Library for ESP32",
"keywords": "async,tcp",
"repository": {
"type": "git",
"url": "https://github.com/ESP32Async/AsyncTCP.git"
},
"authors":
{
"name": "ESP32Async",
"maintainer": true
},
"license": "LGPL-3.0",
"frameworks": "arduino",
"platforms": [
"espressif32",
"libretiny"
],
"export": {
"include": [
"examples",
"src",
"library.json",
"library.properties",
"LICENSE",
"README.md"
]
}
}

View file

@ -0,0 +1,11 @@
name=Async TCP
includes=AsyncTCP.h
version=3.3.5
author=ESP32Async
maintainer=ESP32Async
sentence=Async TCP Library for ESP32
paragraph=Async TCP Library for ESP32
category=Other
url=https://github.com/ESP32Async/AsyncTCP.git
architectures=*
license=LGPL-3.0

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,332 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
#ifndef ASYNCTCP_H_
#define ASYNCTCP_H_
#include "AsyncTCPVersion.h"
#define ASYNCTCP_FORK_ESP32Async
#include "IPAddress.h"
#if ESP_IDF_VERSION_MAJOR < 5
#include "IPv6Address.h"
#endif
#include "lwip/ip6_addr.h"
#include "lwip/ip_addr.h"
#include <functional>
#ifndef LIBRETINY
#include "sdkconfig.h"
extern "C" {
#include "freertos/semphr.h"
#include "lwip/pbuf.h"
}
#else
extern "C" {
#include <lwip/pbuf.h>
#include <semphr.h>
}
#define CONFIG_ASYNC_TCP_RUNNING_CORE -1 // any available core
#endif
// If core is not defined, then we are running in Arduino or PIO
#ifndef CONFIG_ASYNC_TCP_RUNNING_CORE
#define CONFIG_ASYNC_TCP_RUNNING_CORE -1 // any available core
#endif
// guard AsyncTCP task with watchdog
#ifndef CONFIG_ASYNC_TCP_USE_WDT
#define CONFIG_ASYNC_TCP_USE_WDT 1
#endif
#ifndef CONFIG_ASYNC_TCP_STACK_SIZE
#define CONFIG_ASYNC_TCP_STACK_SIZE 8192 * 2
#endif
#ifndef CONFIG_ASYNC_TCP_PRIORITY
#define CONFIG_ASYNC_TCP_PRIORITY 10
#endif
#ifndef CONFIG_ASYNC_TCP_QUEUE_SIZE
#define CONFIG_ASYNC_TCP_QUEUE_SIZE 64
#endif
#ifndef CONFIG_ASYNC_TCP_MAX_ACK_TIME
#define CONFIG_ASYNC_TCP_MAX_ACK_TIME 5000
#endif
class AsyncClient;
#define ASYNC_WRITE_FLAG_COPY 0x01 // will allocate new buffer to hold the data while sending (else will hold reference to the data given)
#define ASYNC_WRITE_FLAG_MORE 0x02 // will not send PSH flag, meaning that there should be more data to be sent before the application should react.
typedef std::function<void(void *, AsyncClient *)> AcConnectHandler;
typedef std::function<void(void *, AsyncClient *, size_t len, uint32_t time)> AcAckHandler;
typedef std::function<void(void *, AsyncClient *, int8_t error)> AcErrorHandler;
typedef std::function<void(void *, AsyncClient *, void *data, size_t len)> AcDataHandler;
typedef std::function<void(void *, AsyncClient *, struct pbuf *pb)> AcPacketHandler;
typedef std::function<void(void *, AsyncClient *, uint32_t time)> AcTimeoutHandler;
struct tcp_pcb;
struct ip_addr;
class AsyncClient {
public:
AsyncClient(tcp_pcb *pcb = 0);
~AsyncClient();
AsyncClient &operator=(const AsyncClient &other);
AsyncClient &operator+=(const AsyncClient &other);
bool operator==(const AsyncClient &other);
bool operator!=(const AsyncClient &other) {
return !(*this == other);
}
bool connect(const IPAddress &ip, uint16_t port);
#if ESP_IDF_VERSION_MAJOR < 5
bool connect(const IPv6Address &ip, uint16_t port);
#endif
bool connect(const char *host, uint16_t port);
/**
* @brief close connection
*
* @param now - ignored
*/
void close(bool now = false);
// same as close()
void stop() {
close(false);
};
int8_t abort();
bool free();
// ack is not pending
bool canSend();
// TCP buffer space available
size_t space();
/**
* @brief add data to be send (but do not send yet)
* @note add() would call lwip's tcp_write()
By default apiflags=ASYNC_WRITE_FLAG_COPY
You could try to use apiflags with this flag unset to pass data by reference and avoid copy to socket buffer,
but looks like it does not work for Arduino's lwip in ESP32/IDF at least
it is enforced in https://github.com/espressif/esp-lwip/blob/0606eed9d8b98a797514fdf6eabb4daf1c8c8cd9/src/core/tcp_out.c#L422C5-L422C30
if LWIP_NETIF_TX_SINGLE_PBUF is set, and it is set indeed in IDF
https://github.com/espressif/esp-idf/blob/a0f798cfc4bbd624aab52b2c194d219e242d80c1/components/lwip/port/include/lwipopts.h#L744
*
* @param data
* @param size
* @param apiflags
* @return size_t amount of data that has been copied
*/
size_t add(const char *data, size_t size, uint8_t apiflags = ASYNC_WRITE_FLAG_COPY);
/**
* @brief send data previously add()'ed
*
* @return true on success
* @return false on error
*/
bool send();
/**
* @brief add and enqueue data for sending
* @note it is same as add() + send()
* @note only make sense when canSend() == true
*
* @param data
* @param size
* @param apiflags
* @return size_t
*/
size_t write(const char *data, size_t size, uint8_t apiflags = ASYNC_WRITE_FLAG_COPY);
/**
* @brief add and enqueue data for sending
* @note treats data as null-terminated string
*
* @param data
* @return size_t
*/
size_t write(const char *data) {
return data == NULL ? 0 : write(data, strlen(data));
};
uint8_t state();
bool connecting();
bool connected();
bool disconnecting();
bool disconnected();
// disconnected or disconnecting
bool freeable();
uint16_t getMss();
uint32_t getRxTimeout();
// no RX data timeout for the connection in seconds
void setRxTimeout(uint32_t timeout);
uint32_t getAckTimeout();
// no ACK timeout for the last sent packet in milliseconds
void setAckTimeout(uint32_t timeout);
void setNoDelay(bool nodelay);
bool getNoDelay();
void setKeepAlive(uint32_t ms, uint8_t cnt);
uint32_t getRemoteAddress();
uint16_t getRemotePort();
uint32_t getLocalAddress();
uint16_t getLocalPort();
#if LWIP_IPV6
ip6_addr_t getRemoteAddress6();
ip6_addr_t getLocalAddress6();
#if ESP_IDF_VERSION_MAJOR < 5
IPv6Address remoteIP6();
IPv6Address localIP6();
#else
IPAddress remoteIP6();
IPAddress localIP6();
#endif
#endif
// compatibility
IPAddress remoteIP();
uint16_t remotePort();
IPAddress localIP();
uint16_t localPort();
// set callback - on successful connect
void onConnect(AcConnectHandler cb, void *arg = 0);
// set callback - disconnected
void onDisconnect(AcConnectHandler cb, void *arg = 0);
// set callback - ack received
void onAck(AcAckHandler cb, void *arg = 0);
// set callback - unsuccessful connect or error
void onError(AcErrorHandler cb, void *arg = 0);
// set callback - data received (called if onPacket is not used)
void onData(AcDataHandler cb, void *arg = 0);
// set callback - data received
// !!! You MUST call ackPacket() or free the pbuf yourself to prevent memory leaks
void onPacket(AcPacketHandler cb, void *arg = 0);
// set callback - ack timeout
void onTimeout(AcTimeoutHandler cb, void *arg = 0);
// set callback - every 125ms when connected
void onPoll(AcConnectHandler cb, void *arg = 0);
// ack pbuf from onPacket
void ackPacket(struct pbuf *pb);
// ack data that you have not acked using the method below
size_t ack(size_t len);
// will not ack the current packet. Call from onData
void ackLater() {
_ack_pcb = false;
}
static const char *errorToString(int8_t error);
const char *stateToString();
// internal callbacks - Do NOT call any of the functions below in user code!
static int8_t _s_poll(void *arg, struct tcp_pcb *tpcb);
static int8_t _s_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *pb, int8_t err);
static int8_t _s_fin(void *arg, struct tcp_pcb *tpcb, int8_t err);
static int8_t _s_lwip_fin(void *arg, struct tcp_pcb *tpcb, int8_t err);
static void _s_error(void *arg, int8_t err);
static int8_t _s_sent(void *arg, struct tcp_pcb *tpcb, uint16_t len);
static int8_t _s_connected(void *arg, struct tcp_pcb *tpcb, int8_t err);
static void _s_dns_found(const char *name, struct ip_addr *ipaddr, void *arg);
int8_t _recv(tcp_pcb *pcb, pbuf *pb, int8_t err);
tcp_pcb *pcb() {
return _pcb;
}
protected:
bool _connect(ip_addr_t addr, uint16_t port);
tcp_pcb *_pcb;
int8_t _closed_slot;
AcConnectHandler _connect_cb;
void *_connect_cb_arg;
AcConnectHandler _discard_cb;
void *_discard_cb_arg;
AcAckHandler _sent_cb;
void *_sent_cb_arg;
AcErrorHandler _error_cb;
void *_error_cb_arg;
AcDataHandler _recv_cb;
void *_recv_cb_arg;
AcPacketHandler _pb_cb;
void *_pb_cb_arg;
AcTimeoutHandler _timeout_cb;
void *_timeout_cb_arg;
AcConnectHandler _poll_cb;
void *_poll_cb_arg;
bool _ack_pcb;
uint32_t _tx_last_packet;
uint32_t _rx_ack_len;
uint32_t _rx_last_packet;
uint32_t _rx_timeout;
uint32_t _rx_last_ack;
uint32_t _ack_timeout;
uint16_t _connect_port;
int8_t _close();
void _free_closed_slot();
bool _allocate_closed_slot();
int8_t _connected(tcp_pcb *pcb, int8_t err);
void _error(int8_t err);
int8_t _poll(tcp_pcb *pcb);
int8_t _sent(tcp_pcb *pcb, uint16_t len);
int8_t _fin(tcp_pcb *pcb, int8_t err);
int8_t _lwip_fin(tcp_pcb *pcb, int8_t err);
void _dns_found(struct ip_addr *ipaddr);
public:
AsyncClient *prev;
AsyncClient *next;
};
class AsyncServer {
public:
AsyncServer(IPAddress addr, uint16_t port);
#if ESP_IDF_VERSION_MAJOR < 5
AsyncServer(IPv6Address addr, uint16_t port);
#endif
AsyncServer(uint16_t port);
~AsyncServer();
void onClient(AcConnectHandler cb, void *arg);
void begin();
void end();
void setNoDelay(bool nodelay);
bool getNoDelay();
uint8_t status();
// Do not use any of the functions below!
static int8_t _s_accept(void *arg, tcp_pcb *newpcb, int8_t err);
static int8_t _s_accepted(void *arg, AsyncClient *client);
protected:
uint16_t _port;
bool _bind4 = false;
bool _bind6 = false;
IPAddress _addr;
#if ESP_IDF_VERSION_MAJOR < 5
IPv6Address _addr6;
#endif
bool _noDelay;
tcp_pcb *_pcb;
AcConnectHandler _connect_cb;
void *_connect_cb_arg;
int8_t _accept(tcp_pcb *newpcb, int8_t err);
int8_t _accepted(AsyncClient *client);
};
#endif /* ASYNCTCP_H_ */

View file

@ -0,0 +1,40 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
/** Major version number (X.x.x) */
#define ASYNCTCP_VERSION_MAJOR 3
/** Minor version number (x.X.x) */
#define ASYNCTCP_VERSION_MINOR 3
/** Patch version number (x.x.X) */
#define ASYNCTCP_VERSION_PATCH 5
/**
* Macro to convert version number into an integer
*
* To be used in comparisons, such as ASYNCTCP_VERSION >= ASYNCTCP_VERSION_VAL(2, 0, 0)
*/
#define ASYNCTCP_VERSION_VAL(major, minor, patch) ((major << 16) | (minor << 8) | (patch))
/**
* Current version, as an integer
*
* To be used in comparisons, such as ASYNCTCP_VERSION_NUM >= ASYNCTCP_VERSION_VAL(2, 0, 0)
*/
#define ASYNCTCP_VERSION_NUM ASYNCTCP_VERSION_VAL(ASYNCTCP_VERSION_MAJOR, ASYNCTCP_VERSION_MINOR, ASYNCTCP_VERSION_PATCH)
/**
* Current version, as string
*/
#define df2xstr(s) #s
#define df2str(s) df2xstr(s)
#define ASYNCTCP_VERSION df2str(ASYNCTCP_VERSION_MAJOR) "." df2str(ASYNCTCP_VERSION_MINOR) "." df2str(ASYNCTCP_VERSION_PATCH)
#ifdef __cplusplus
}
#endif

View file

@ -23,7 +23,7 @@
#include <Arduino.h>
#ifdef ESP32
#include "../../mathieucarbou-AsyncTCPSock/src/AsyncTCP.h"
#include "../../ESP32Async-AsyncTCP/src/AsyncTCP.h"
#include <mutex>
#ifndef SSE_MAX_QUEUED_MESSAGES
#define SSE_MAX_QUEUED_MESSAGES 32

View file

@ -23,7 +23,7 @@
#include <Arduino.h>
#ifdef ESP32
#include "../../mathieucarbou-AsyncTCPSock/src/AsyncTCP.h"
#include "../../ESP32Async-AsyncTCP/src/AsyncTCP.h"
#include <mutex>
#ifndef WS_MAX_QUEUED_MESSAGES
#define WS_MAX_QUEUED_MESSAGES 32

View file

@ -32,7 +32,7 @@
#include <vector>
#ifdef ESP32
#include "../../mathieucarbou-AsyncTCPSock/src/AsyncTCP.h"
#include "../../ESP32Async-AsyncTCP/src/AsyncTCP.h"
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>

View file

@ -19,11 +19,11 @@
{
"owner": "mathieucarbou",
"name": "ESPAsyncWebServer",
"version": "^3.1.1",
"version": "^3.3.11",
"platforms": ["espressif8266", "espressif32"]
}
],
"version": "3.1.5",
"version": "3.1.6",
"frameworks": "arduino",
"platforms": ["espressif8266", "espressif32", "raspberrypi"],
"build": {

View file

@ -1,5 +1,5 @@
name=ElegantOTA
version=3.1.5
version=3.1.6
author=Ayush Sharma
category=Communication
maintainer=Ayush Sharma <asrocks5@gmail.com>

View file

@ -43,9 +43,6 @@ void ElegantOTAClass::begin(ELEGANTOTA_WEBSERVER *server, const char * username,
return request->requestAuthentication();
}
// Pre-OTA update callback
if (preUpdateCallback != NULL) preUpdateCallback();
// Get header x-ota-mode value, if present
OTA_Mode mode = OTA_MODE_FIRMWARE;
// Get mode from arg
@ -76,7 +73,7 @@ void ElegantOTAClass::begin(ELEGANTOTA_WEBSERVER *server, const char * username,
#endif
// Pre-OTA update callback
//if (preUpdateCallback != NULL) preUpdateCallback();
if (preUpdateCallback != NULL) preUpdateCallback();
// Start update process
#if defined(ESP8266)
@ -179,9 +176,9 @@ void ElegantOTAClass::begin(ELEGANTOTA_WEBSERVER *server, const char * username,
update_size = ((size_t)&_FS_end - (size_t)&_FS_start);
LittleFS.end();
} else {
FSInfo64 i;
FSInfo i;
LittleFS.begin();
LittleFS.info64(i);
LittleFS.info(i);
update_size = i.totalBytes - i.usedBytes;
}
// Start update process

View file

@ -64,7 +64,7 @@ _____ _ _ ___ _____ _
#include "Update.h"
#include "StreamString.h"
#if ELEGANTOTA_USE_ASYNC_WEBSERVER == 1
#include "../../mathieucarbou-AsyncTCPSock/src/AsyncTCP.h"
#include "../../ESP32Async-AsyncTCP/src/AsyncTCP.h"
#include "../../ESP32Async-ESPAsyncWebServer/src/ESPAsyncWebServer.h"
#define ELEGANTOTA_WEBSERVER AsyncWebServer
#else

View file

@ -1,22 +0,0 @@
{
"name":"AsyncTCPSock",
"description":"Reimplementation of an Asynchronous TCP Library for ESP32, using BSD Sockets",
"keywords":"async,tcp",
"authors":
{
"name": "Alex Villacís Lasso",
"maintainer": true
},
"repository":
{
"type": "git",
"url": "https://github.com/yubox-node-org/AsyncTCPSock.git"
},
"version": "1.0.2-dev",
"license": "LGPL-3.0",
"frameworks": "arduino",
"platforms": "espressif32",
"build": {
"libCompatMode": 2
}
}

View file

@ -1,9 +0,0 @@
name=AsyncTCPSock
version=1.0.2-dev
author=avillacis
maintainer=avillacis
sentence=Reimplemented Async TCP Library for ESP32 using BSD Sockets
paragraph=This is a reimplementation of AsyncTCP (Async TCP Library for ESP32) by Me No Dev, using high-level BSD Sockets instead of the low-level packet API and a message queue.
category=Other
url=https://github.com/yubox-node-org/AsyncTCPSock
architectures=*

File diff suppressed because it is too large Load diff

View file

@ -1,321 +0,0 @@
/*
Reimplementation of an asynchronous TCP library for Espressif MCUs, using
BSD sockets.
Copyright (c) 2020 Alex Villacís Lasso.
Original AsyncTCP API Copyright (c) 2016 Hristo Gochkov. All rights reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef ASYNCTCP_H_
#define ASYNCTCP_H_
#include "../../../system_settings.h"
#include "../../../devboard/hal/hal.h"
#include "IPAddress.h"
#include "sdkconfig.h"
#include <functional>
#include <deque>
#include <list>
#if ASYNC_TCP_SSL_ENABLED
#include <ssl_client.h>
#include "AsyncTCP_TLS_Context.h"
#endif
extern "C" {
#include "lwip/err.h"
#include "lwip/sockets.h"
}
#define ASYNCTCP_VERSION "1.0.2-dev"
#define ASYNCTCP_VERSION_MAJOR 1
#define ASYNCTCP_VERSION_MINOR 2
#define ASYNCTCP_VERSION_REVISION 2
#define ASYNCTCP_FORK_mathieucarbou
//If core is not defined, then we are running in Arduino or PIO
#ifndef CONFIG_ASYNC_TCP_RUNNING_CORE
#define CONFIG_ASYNC_TCP_RUNNING_CORE WIFI_CORE
#define CONFIG_ASYNC_TCP_USE_WDT 0 //if enabled, adds between 33us and 200us per event
#endif
#ifndef CONFIG_ASYNC_TCP_STACK_SIZE
#define CONFIG_ASYNC_TCP_STACK_SIZE 16384 // 8192 * 2
#endif
#ifndef CONFIG_ASYNC_TCP_STACK
#define CONFIG_ASYNC_TCP_STACK CONFIG_ASYNC_TCP_STACK_SIZE
#endif
#ifndef CONFIG_ASYNC_TCP_PRIORITY
#define CONFIG_ASYNC_TCP_PRIORITY 3
#endif
#ifndef CONFIG_ASYNC_TCP_TASK_PRIORITY
#define CONFIG_ASYNC_TCP_TASK_PRIORITY TASK_CONNECTIVITY_PRIO
#endif
#ifndef CONFIG_ASYNC_TCP_TASK_NAME
#define CONFIG_ASYNC_TCP_TASK_NAME "asyncTcpSock"
#endif
class AsyncClient;
#ifndef CONFIG_ASYNC_TCP_MAX_ACK_TIME
#define CONFIG_ASYNC_TCP_MAX_ACK_TIME 5000
#endif
#ifndef ASYNC_MAX_ACK_TIME
#define ASYNC_MAX_ACK_TIME CONFIG_ASYNC_TCP_MAX_ACK_TIME
#endif
#define ASYNC_WRITE_FLAG_COPY 0x01 //will allocate new buffer to hold the data while sending (else will hold reference to the data given)
#define ASYNC_WRITE_FLAG_MORE 0x02 //will not send PSH flag, meaning that there should be more data to be sent before the application should react.
#define SSL_HANDSHAKE_TIMEOUT 5000 // timeout to complete SSL handshake
typedef std::function<void(void*, AsyncClient*)> AcConnectHandler;
typedef std::function<void(void*, AsyncClient*, size_t len, uint32_t time)> AcAckHandler;
typedef std::function<void(void*, AsyncClient*, int8_t error)> AcErrorHandler;
typedef std::function<void(void*, AsyncClient*, void *data, size_t len)> AcDataHandler;
//typedef std::function<void(void*, AsyncClient*, struct pbuf *pb)> AcPacketHandler;
typedef std::function<void(void*, AsyncClient*, uint32_t time)> AcTimeoutHandler;
class AsyncSocketBase
{
private:
static std::list<AsyncSocketBase *> & _getSocketBaseList(void);
protected:
int _socket = -1;
bool _selected = false;
bool _isdnsfinished = false;
uint32_t _sock_lastactivity = 0;
virtual void _sockIsReadable(void) {} // Action to take on readable socket
virtual bool _sockIsWriteable(void) { return false; } // Action to take on writable socket
virtual void _sockPoll(void) {} // Action to take on idle socket activity poll
virtual void _sockDelayedConnect(void) {} // Action to take on DNS-resolve finished
virtual bool _pendingWrite(void) { return false; } // Test if there is data pending to be written
virtual bool _isServer(void) { return false; } // Will a read from this socket result in one more client?
public:
AsyncSocketBase(void);
virtual ~AsyncSocketBase();
friend void _asynctcpsock_task(void *);
};
class AsyncClient : public AsyncSocketBase
{
public:
AsyncClient(int sockfd = -1);
~AsyncClient();
#if ASYNC_TCP_SSL_ENABLED
bool connect(IPAddress ip, uint16_t port, bool secure = false);
bool connect(const char* host, uint16_t port, bool secure = false);
void setRootCa(const char* rootca, const size_t len);
void setClientCert(const char* cli_cert, const size_t len);
void setClientKey(const char* cli_key, const size_t len);
void setPsk(const char* psk_ident, const char* psk);
#else
bool connect(IPAddress ip, uint16_t port);
bool connect(const char* host, uint16_t port);
#endif // ASYNC_TCP_SSL_ENABLED
void close(bool now = false);
int8_t abort();
bool free();
bool canSend() { return space() > 0; }
size_t space();
size_t add(const char* data, size_t size, uint8_t apiflags=ASYNC_WRITE_FLAG_COPY);//add for sending
bool send();
//write equals add()+send()
size_t write(const char* data);
size_t write(const char* data, size_t size, uint8_t apiflags=ASYNC_WRITE_FLAG_COPY); //only when canSend() == true
uint8_t state() { return _conn_state; }
bool connected();
bool freeable();//disconnected or disconnecting
uint32_t getAckTimeout();
void setAckTimeout(uint32_t timeout);//no ACK timeout for the last sent packet in milliseconds
uint32_t getRxTimeout();
void setRxTimeout(uint32_t timeout);//no RX data timeout for the connection in seconds
void setNoDelay(bool nodelay);
bool getNoDelay();
uint32_t getRemoteAddress();
uint16_t getRemotePort();
uint32_t getLocalAddress();
uint16_t getLocalPort();
//compatibility
IPAddress remoteIP();
uint16_t remotePort();
IPAddress localIP();
uint16_t localPort();
void onConnect(AcConnectHandler cb, void* arg = 0); //on successful connect
void onDisconnect(AcConnectHandler cb, void* arg = 0); //disconnected
void onAck(AcAckHandler cb, void* arg = 0); //ack received
void onError(AcErrorHandler cb, void* arg = 0); //unsuccessful connect or error
void onData(AcDataHandler cb, void* arg = 0); //data received
void onTimeout(AcTimeoutHandler cb, void* arg = 0); //ack timeout
void onPoll(AcConnectHandler cb, void* arg = 0); //every 125ms when connected
// The following functions are just for API compatibility and do nothing
size_t ack(size_t len) { return len; }
void ackLater() {}
const char * errorToString(int8_t error);
// const char * stateToString();
protected:
bool _sockIsWriteable(void);
void _sockIsReadable(void);
void _sockPoll(void);
void _sockDelayedConnect(void);
bool _pendingWrite(void);
private:
AcConnectHandler _connect_cb;
void* _connect_cb_arg;
AcConnectHandler _discard_cb;
void* _discard_cb_arg;
AcAckHandler _sent_cb;
void* _sent_cb_arg;
AcErrorHandler _error_cb;
void* _error_cb_arg;
AcDataHandler _recv_cb;
void* _recv_cb_arg;
AcTimeoutHandler _timeout_cb;
void* _timeout_cb_arg;
AcConnectHandler _poll_cb;
void* _poll_cb_arg;
uint32_t _rx_last_packet;
uint32_t _rx_since_timeout;
uint32_t _ack_timeout;
// Used on asynchronous DNS resolving scenario - I do not want to connect()
// from the LWIP thread itself.
struct ip_addr _connect_addr;
uint16_t _connect_port = 0;
//const char * _connect_dnsname = NULL;
#if ASYNC_TCP_SSL_ENABLED
size_t _root_ca_len;
char* _root_ca;
size_t _cli_cert_len;
char* _cli_cert;
size_t _cli_key_len;
char* _cli_key;
bool _secure;
bool _handshake_done;
const char* _psk_ident;
const char* _psk;
String _hostname;
AsyncTCP_TLS_Context * _sslctx;
#endif // ASYNC_TCP_SSL_ENABLED
// The following private struct represents a buffer enqueued with the add()
// method. Each of these buffers are flushed whenever the socket becomes
// writable
typedef struct {
uint8_t * data; // Pointer to data queued for write
uint32_t length; // Length of data queued for write
uint32_t written; // Length of data written to socket so far
uint32_t queued_at;// Timestamp at which this data buffer was queued
uint32_t written_at; // Timestamp at which this data buffer was completely written
int write_errno; // If != 0, errno value while writing this buffer
bool owned; // If true, we malloc'ed the data and should be freed after completely written.
// If false, app owns the memory and should ensure it remains valid until acked
} queued_writebuf;
// Internal struct used to implement sent buffer notification
typedef struct {
uint32_t length;
uint32_t delay;
} notify_writebuf;
// Queue of buffers to write to socket
SemaphoreHandle_t _write_mutex;
std::deque<queued_writebuf> _writeQueue;
bool _ack_timeout_signaled = false;
// Remaining space willing to queue for writing
uint32_t _writeSpaceRemaining;
// Simulation of connection state
uint8_t _conn_state;
void _error(int8_t err);
void _close(void);
void _removeAllCallbacks(void);
bool _flushWriteQueue(void);
void _clearWriteQueue(void);
void _collectNotifyWrittenBuffers(std::deque<notify_writebuf> &, int &);
void _notifyWrittenBuffers(std::deque<notify_writebuf> &, int);
#if ASYNC_TCP_SSL_ENABLED
int _runSSLHandshakeLoop(void);
#endif
friend void _tcpsock_dns_found(const char * name, struct ip_addr * ipaddr, void * arg);
};
#if ASYNC_TCP_SSL_ENABLED
typedef std::function<int(void* arg, const char *filename, uint8_t **buf)> AcSSlFileHandler;
#endif
class AsyncServer : public AsyncSocketBase
{
public:
AsyncServer(IPAddress addr, uint16_t port);
AsyncServer(uint16_t port);
~AsyncServer();
void onClient(AcConnectHandler cb, void* arg);
#if ASYNC_TCP_SSL_ENABLED
// Dummy, so it compiles with ESP Async WebServer library enabled.
void onSslFileRequest(AcSSlFileHandler cb, void* arg) {};
void beginSecure(const char *cert, const char *private_key_file, const char *password) {};
#endif
void begin();
void end();
void setNoDelay(bool nodelay) { _noDelay = nodelay; }
bool getNoDelay() { return _noDelay; }
uint8_t status();
protected:
uint16_t _port;
IPAddress _addr;
bool _noDelay;
AcConnectHandler _connect_cb;
void* _connect_cb_arg;
// Listening socket is readable on incoming connection
void _sockIsReadable(void);
// Mark this class as a server
bool _isServer(void) { return true; }
};
#endif /* ASYNCTCP_H_ */

View file

@ -1,6 +0,0 @@
#ifndef ASYNCTCP_SSL_H_
#define ASYNCTCP_SSL_H_
#include "AsyncTCP_SSL.hpp"
#endif /* ASYNCTCP_SSL_H_ */

View file

@ -1,17 +0,0 @@
#ifndef ASYNCTCP_SSL_HPP
#define ASYNCTCP_SSL_HPP
#ifdef ASYNC_TCP_SSL_ENABLED
#include <AsyncTCP.h>
#define AsyncSSLClient AsyncClient
#define AsyncSSLServer AsyncServer
#define ASYNC_TCP_SSL_VERSION "AsyncTCPSock SSL shim v0.0.1"
#else
#error Compatibility shim requires ASYNC_TCP_SSL_ENABLED to be defined!
#endif
#endif

View file

@ -1,346 +0,0 @@
#include <Arduino.h>
#include <esp32-hal-log.h>
#include <lwip/err.h>
#include <lwip/sockets.h>
#include <lwip/sys.h>
#include <lwip/netdb.h>
#include <mbedtls/sha256.h>
#include <mbedtls/oid.h>
#include "AsyncTCP_TLS_Context.h"
#if ASYNC_TCP_SSL_ENABLED
#if !defined(MBEDTLS_KEY_EXCHANGE__SOME__PSK_ENABLED) && !defined(MBEDTLS_KEY_EXCHANGE_SOME_PSK_ENABLED)
# warning "Please configure IDF framework to include mbedTLS -> Enable pre-shared-key ciphersuites and activate at least one cipher"
#else
static const char *pers = "esp32-tls";
static int _handle_error(int err, const char * function, int line)
{
if(err == -30848){
return err;
}
#ifdef MBEDTLS_ERROR_C
char error_buf[100];
mbedtls_strerror(err, error_buf, 100);
log_e("[%s():%d]: (%d) %s", function, line, err, error_buf);
#else
log_e("[%s():%d]: code %d", function, line, err);
#endif
return err;
}
#define handle_error(e) _handle_error(e, __FUNCTION__, __LINE__)
AsyncTCP_TLS_Context::AsyncTCP_TLS_Context(void)
{
mbedtls_ssl_init(&ssl_ctx);
mbedtls_ssl_config_init(&ssl_conf);
mbedtls_ctr_drbg_init(&drbg_ctx);
_socket = -1;
_have_ca_cert = false;
_have_client_cert = false;
_have_client_key = false;
handshake_timeout = 120000;
}
int AsyncTCP_TLS_Context::startSSLClientInsecure(int sck, const char * host_or_ip)
{
return _startSSLClient(sck, host_or_ip,
NULL, 0,
NULL, 0,
NULL, 0,
NULL, NULL,
true);
}
int AsyncTCP_TLS_Context::startSSLClient(int sck, const char * host_or_ip,
const char *pskIdent, const char *psKey)
{
return _startSSLClient(sck, host_or_ip,
NULL, 0,
NULL, 0,
NULL, 0,
pskIdent, psKey,
false);
}
int AsyncTCP_TLS_Context::startSSLClient(int sck, const char * host_or_ip,
const char *rootCABuff,
const char *cli_cert,
const char *cli_key)
{
return startSSLClient(sck, host_or_ip,
(const unsigned char *)rootCABuff, (rootCABuff != NULL) ? strlen(rootCABuff) + 1 : 0,
(const unsigned char *)cli_cert, (cli_cert != NULL) ? strlen(cli_cert) + 1 : 0,
(const unsigned char *)cli_key, (cli_key != NULL) ? strlen(cli_key) + 1 : 0);
}
int AsyncTCP_TLS_Context::startSSLClient(int sck, const char * host_or_ip,
const unsigned char *rootCABuff, const size_t rootCABuff_len,
const unsigned char *cli_cert, const size_t cli_cert_len,
const unsigned char *cli_key, const size_t cli_key_len)
{
return _startSSLClient(sck, host_or_ip,
rootCABuff, rootCABuff_len,
cli_cert, cli_cert_len,
cli_key, cli_key_len,
NULL, NULL,
false);
}
int AsyncTCP_TLS_Context::_startSSLClient(int sck, const char * host_or_ip,
const unsigned char *rootCABuff, const size_t rootCABuff_len,
const unsigned char *cli_cert, const size_t cli_cert_len,
const unsigned char *cli_key, const size_t cli_key_len,
const char *pskIdent, const char *psKey,
bool insecure)
{
int ret;
int enable = 1;
// The insecure flag will skip server certificate validation. Otherwise some
// certificate is required.
if (rootCABuff == NULL && pskIdent == NULL && psKey == NULL && !insecure) {
return -1;
}
#define ROE(x,msg) { if (((x)<0)) { log_e("LWIP Socket config of " msg " failed."); return -1; }}
// ROE(lwip_setsockopt(sck, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)),"SO_RCVTIMEO");
// ROE(lwip_setsockopt(sck, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)),"SO_SNDTIMEO");
ROE(lwip_setsockopt(sck, IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(enable)),"TCP_NODELAY");
ROE(lwip_setsockopt(sck, SOL_SOCKET, SO_KEEPALIVE, &enable, sizeof(enable)),"SO_KEEPALIVE");
log_v("Seeding the random number generator");
mbedtls_entropy_init(&entropy_ctx);
ret = mbedtls_ctr_drbg_seed(&drbg_ctx, mbedtls_entropy_func,
&entropy_ctx, (const unsigned char *) pers, strlen(pers));
if (ret < 0) {
return handle_error(ret);
}
log_v("Setting up the SSL/TLS structure...");
if ((ret = mbedtls_ssl_config_defaults(&ssl_conf,
MBEDTLS_SSL_IS_CLIENT,
MBEDTLS_SSL_TRANSPORT_STREAM,
MBEDTLS_SSL_PRESET_DEFAULT)) != 0) {
return handle_error(ret);
}
if (insecure) {
mbedtls_ssl_conf_authmode(&ssl_conf, MBEDTLS_SSL_VERIFY_NONE);
log_i("WARNING: Skipping SSL Verification. INSECURE!");
} else if (rootCABuff != NULL) {
log_v("Loading CA cert");
mbedtls_x509_crt_init(&ca_cert);
mbedtls_ssl_conf_authmode(&ssl_conf, MBEDTLS_SSL_VERIFY_REQUIRED);
ret = mbedtls_x509_crt_parse(&ca_cert, rootCABuff, rootCABuff_len);
_have_ca_cert = true;
mbedtls_ssl_conf_ca_chain(&ssl_conf, &ca_cert, NULL);
if (ret < 0) {
// free the ca_cert in the case parse failed, otherwise, the old ca_cert still in the heap memory, that lead to "out of memory" crash.
_deleteHandshakeCerts();
return handle_error(ret);
}
} else if (pskIdent != NULL && psKey != NULL) {
log_v("Setting up PSK");
// convert PSK from hex to binary
if ((strlen(psKey) & 1) != 0 || strlen(psKey) > 2*MBEDTLS_PSK_MAX_LEN) {
log_e("pre-shared key not valid hex or too long");
return -1;
}
unsigned char psk[MBEDTLS_PSK_MAX_LEN];
size_t psk_len = strlen(psKey)/2;
for (int j=0; j<strlen(psKey); j+= 2) {
char c = psKey[j];
if (c >= '0' && c <= '9') c -= '0';
else if (c >= 'A' && c <= 'F') c -= 'A' - 10;
else if (c >= 'a' && c <= 'f') c -= 'a' - 10;
else return -1;
psk[j/2] = c<<4;
c = psKey[j+1];
if (c >= '0' && c <= '9') c -= '0';
else if (c >= 'A' && c <= 'F') c -= 'A' - 10;
else if (c >= 'a' && c <= 'f') c -= 'a' - 10;
else return -1;
psk[j/2] |= c;
}
// set mbedtls config
ret = mbedtls_ssl_conf_psk(&ssl_conf, psk, psk_len,
(const unsigned char *)pskIdent, strlen(pskIdent));
if (ret != 0) {
log_e("mbedtls_ssl_conf_psk returned %d", ret);
return handle_error(ret);
}
} else {
return -1;
}
if (!insecure && cli_cert != NULL && cli_key != NULL) {
mbedtls_x509_crt_init(&client_cert);
mbedtls_pk_init(&client_key);
log_v("Loading CRT cert");
ret = mbedtls_x509_crt_parse(&client_cert, cli_cert, cli_cert_len);
_have_client_cert = true;
if (ret < 0) {
// free the client_cert in the case parse failed, otherwise, the old client_cert still in the heap memory, that lead to "out of memory" crash.
_deleteHandshakeCerts();
return handle_error(ret);
}
log_v("Loading private key");
#if MBEDTLS_VERSION_NUMBER < 0x03000000
ret = mbedtls_pk_parse_key(&client_key, cli_key, cli_key_len, NULL, 0);
#else
ret = mbedtls_pk_parse_key(&client_key, cli_key, cli_key_len, NULL, 0, mbedtls_ctr_drbg_random, &drbg_ctx);
#endif
_have_client_key = true;
if (ret != 0) {
_deleteHandshakeCerts();
return handle_error(ret);
}
mbedtls_ssl_conf_own_cert(&ssl_conf, &client_cert, &client_key);
}
log_v("Setting hostname for TLS session...");
// Hostname set here should match CN in server certificate
if ((ret = mbedtls_ssl_set_hostname(&ssl_ctx, host_or_ip)) != 0){
_deleteHandshakeCerts();
return handle_error(ret);
}
mbedtls_ssl_conf_rng(&ssl_conf, mbedtls_ctr_drbg_random, &drbg_ctx);
if ((ret = mbedtls_ssl_setup(&ssl_ctx, &ssl_conf)) != 0) {
_deleteHandshakeCerts();
return handle_error(ret);
}
_socket = sck;
mbedtls_ssl_set_bio(&ssl_ctx, &_socket, mbedtls_net_send, mbedtls_net_recv, NULL );
handshake_start_time = 0;
return 0;
}
int AsyncTCP_TLS_Context::runSSLHandshake(void)
{
int ret, flags;
if (_socket < 0) return -1;
if (handshake_start_time == 0) handshake_start_time = millis();
ret = mbedtls_ssl_handshake(&ssl_ctx);
if (ret != 0) {
// Something happened before SSL handshake could be completed
// Negotiation error, other than socket not readable/writable when required
if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) {
return handle_error(ret);
}
// Handshake is taking too long
if ((millis()-handshake_start_time) > handshake_timeout)
return -1;
// Either MBEDTLS_ERR_SSL_WANT_READ or MBEDTLS_ERR_SSL_WANT_WRITE
return ret;
}
// Handshake completed, validate remote side if required...
if (_have_client_cert && _have_client_key) {
log_d("Protocol is %s Ciphersuite is %s", mbedtls_ssl_get_version(&ssl_ctx), mbedtls_ssl_get_ciphersuite(&ssl_ctx));
if ((ret = mbedtls_ssl_get_record_expansion(&ssl_ctx)) >= 0) {
log_d("Record expansion is %d", ret);
} else {
log_w("Record expansion is unknown (compression)");
}
}
log_v("Verifying peer X.509 certificate...");
if ((flags = mbedtls_ssl_get_verify_result(&ssl_ctx)) != 0) {
char buf[512];
memset(buf, 0, sizeof(buf));
mbedtls_x509_crt_verify_info(buf, sizeof(buf), " ! ", flags);
log_e("Failed to verify peer certificate! verification info: %s", buf);
_deleteHandshakeCerts();
return handle_error(ret);
} else {
log_v("Certificate verified.");
}
_deleteHandshakeCerts();
log_v("Free internal heap after TLS %u", ESP.getFreeHeap());
return 0;
}
int AsyncTCP_TLS_Context::write(const uint8_t *data, size_t len)
{
if (_socket < 0) return -1;
log_v("Writing packet, %d bytes unencrypted...", len);
int ret = mbedtls_ssl_write(&ssl_ctx, data, len);
if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE && ret < 0) {
log_v("Handling error %d", ret); //for low level debug
return handle_error(ret);
}
return ret;
}
int AsyncTCP_TLS_Context::read(uint8_t * data, size_t len)
{
int ret = mbedtls_ssl_read(&ssl_ctx, data, len);
if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE && ret < 0) {
log_v("Handling error %d", ret); //for low level debug
return handle_error(ret);
}
if (ret > 0) log_v("Read packet, %d out of %d requested bytes...", ret, len);
return ret;
}
void AsyncTCP_TLS_Context::_deleteHandshakeCerts(void)
{
if (_have_ca_cert) {
log_v("Cleaning CA certificate.");
mbedtls_x509_crt_free(&ca_cert);
_have_ca_cert = false;
}
if (_have_client_cert) {
log_v("Cleaning client certificate.");
mbedtls_x509_crt_free(&client_cert);
_have_client_cert = false;
}
if (_have_client_key) {
log_v("Cleaning client certificate key.");
mbedtls_pk_free(&client_key);
_have_client_key = false;
}
}
AsyncTCP_TLS_Context::~AsyncTCP_TLS_Context()
{
_deleteHandshakeCerts();
log_v("Cleaning SSL connection.");
mbedtls_ssl_free(&ssl_ctx);
mbedtls_ssl_config_free(&ssl_conf);
mbedtls_ctr_drbg_free(&drbg_ctx);
mbedtls_entropy_free(&entropy_ctx); // <-- Is this OK to free if mbedtls_entropy_init() has not been called on it?
}
#endif
#endif // ASYNC_TCP_SSL_ENABLED

View file

@ -1,79 +0,0 @@
#pragma once
#if ASYNC_TCP_SSL_ENABLED
#include "mbedtls/version.h"
#include "mbedtls/platform.h"
#if MBEDTLS_VERSION_NUMBER < 0x03000000
#include "mbedtls/net.h"
#else
#include "mbedtls/net_sockets.h"
#endif
#include "mbedtls/debug.h"
#include "mbedtls/ssl.h"
#include "mbedtls/entropy.h"
#include "mbedtls/ctr_drbg.h"
#include "mbedtls/error.h"
#define ASYNCTCP_TLS_CAN_RETRY(r) (((r) == MBEDTLS_ERR_SSL_WANT_READ) || ((r) == MBEDTLS_ERR_SSL_WANT_WRITE))
#define ASYNCTCP_TLS_EOF(r) (((r) == MBEDTLS_ERR_SSL_CONN_EOF) || ((r) == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY))
class AsyncTCP_TLS_Context
{
private:
// These fields must persist for the life of the encrypted connection, destroyed on
// object destructor.
mbedtls_ssl_context ssl_ctx;
mbedtls_ssl_config ssl_conf;
mbedtls_ctr_drbg_context drbg_ctx;
mbedtls_entropy_context entropy_ctx;
// These allocate memory during handshake but must be freed on either success or failure
mbedtls_x509_crt ca_cert;
mbedtls_x509_crt client_cert;
mbedtls_pk_context client_key;
bool _have_ca_cert;
bool _have_client_cert;
bool _have_client_key;
unsigned long handshake_timeout;
unsigned long handshake_start_time;
int _socket;
int _startSSLClient(int sck, const char * host_or_ip,
const unsigned char *rootCABuff, const size_t rootCABuff_len,
const unsigned char *cli_cert, const size_t cli_cert_len,
const unsigned char *cli_key, const size_t cli_key_len,
const char *pskIdent, const char *psKey,
bool insecure);
// Delete certificates used in handshake
void _deleteHandshakeCerts(void);
public:
AsyncTCP_TLS_Context(void);
virtual ~AsyncTCP_TLS_Context();
int startSSLClientInsecure(int sck, const char * host_or_ip);
int startSSLClient(int sck, const char * host_or_ip,
const char *pskIdent, const char *psKey);
int startSSLClient(int sck, const char * host_or_ip,
const char *rootCABuff,
const char *cli_cert,
const char *cli_key);
int startSSLClient(int sck, const char * host_or_ip,
const unsigned char *rootCABuff, const size_t rootCABuff_len,
const unsigned char *cli_cert, const size_t cli_cert_len,
const unsigned char *cli_key, const size_t cli_key_len);
int runSSLHandshake(void);
int write(const uint8_t *data, size_t len);
int read(uint8_t * data, size_t len);
};
#endif // ASYNC_TCP_SSL_ENABLED