mirror of
https://github.com/dalathegreat/Battery-Emulator.git
synced 2025-10-05 19:42:08 +02:00
Merge branch 'main' into SIMPBMS
This commit is contained in:
commit
10b85f36f0
80 changed files with 4692 additions and 2346 deletions
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
206
Software/src/battery/DALY-BMS.cpp
Normal file
206
Software/src/battery/DALY-BMS.cpp
Normal 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
|
17
Software/src/battery/DALY-BMS.h
Normal file
17
Software/src/battery/DALY-BMS.h
Normal 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
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
151
Software/src/battery/ORION-BMS.cpp
Normal file
151
Software/src/battery/ORION-BMS.cpp
Normal 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
|
19
Software/src/battery/ORION-BMS.h
Normal file
19
Software/src/battery/ORION-BMS.h
Normal 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
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
667
Software/src/battery/VOLVO-SPA-HYBRID-BATTERY.cpp
Normal file
667
Software/src/battery/VOLVO-SPA-HYBRID-BATTERY.cpp
Normal 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
|
16
Software/src/battery/VOLVO-SPA-HYBRID-BATTERY.h
Normal file
16
Software/src/battery/VOLVO-SPA-HYBRID-BATTERY.h
Normal 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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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>";
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
499
Software/src/inverter/FERROAMP-CAN.cpp
Normal file
499
Software/src/inverter/FERROAMP-CAN.cpp
Normal 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
|
12
Software/src/inverter/FERROAMP-CAN.h
Normal file
12
Software/src/inverter/FERROAMP-CAN.h
Normal 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
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
15
Software/src/lib/ESP32Async-AsyncTCP/CMakeLists.txt
Normal file
15
Software/src/lib/ESP32Async-AsyncTCP/CMakeLists.txt
Normal 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)
|
129
Software/src/lib/ESP32Async-AsyncTCP/CODE_OF_CONDUCT.md
Normal file
129
Software/src/lib/ESP32Async-AsyncTCP/CODE_OF_CONDUCT.md
Normal 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.
|
55
Software/src/lib/ESP32Async-AsyncTCP/README.md
Normal file
55
Software/src/lib/ESP32Async-AsyncTCP/README.md
Normal file
|
@ -0,0 +1,55 @@
|
|||

|
||||
|
||||
# AsyncTCP
|
||||
|
||||
[](https://opensource.org/license/lgpl-3-0/)
|
||||
[](https://github.com/ESP32Async/AsyncTCP/actions/workflows/ci.yml)
|
||||
[](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
|
3
Software/src/lib/ESP32Async-AsyncTCP/component.mk
Normal file
3
Software/src/lib/ESP32Async-AsyncTCP/component.mk
Normal file
|
@ -0,0 +1,3 @@
|
|||
COMPONENT_ADD_INCLUDEDIRS := src
|
||||
COMPONENT_SRCDIRS := src
|
||||
CXXFLAGS += -fno-rtti
|
31
Software/src/lib/ESP32Async-AsyncTCP/library.json
Normal file
31
Software/src/lib/ESP32Async-AsyncTCP/library.json
Normal 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"
|
||||
]
|
||||
}
|
||||
}
|
11
Software/src/lib/ESP32Async-AsyncTCP/library.properties
Normal file
11
Software/src/lib/ESP32Async-AsyncTCP/library.properties
Normal 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
|
1665
Software/src/lib/ESP32Async-AsyncTCP/src/AsyncTCP.cpp
Normal file
1665
Software/src/lib/ESP32Async-AsyncTCP/src/AsyncTCP.cpp
Normal file
File diff suppressed because it is too large
Load diff
332
Software/src/lib/ESP32Async-AsyncTCP/src/AsyncTCP.h
Normal file
332
Software/src/lib/ESP32Async-AsyncTCP/src/AsyncTCP.h
Normal 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_ */
|
40
Software/src/lib/ESP32Async-AsyncTCP/src/AsyncTCPVersion.h
Normal file
40
Software/src/lib/ESP32Async-AsyncTCP/src/AsyncTCPVersion.h
Normal 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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
@ -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_ */
|
|
@ -1,6 +0,0 @@
|
|||
#ifndef ASYNCTCP_SSL_H_
|
||||
#define ASYNCTCP_SSL_H_
|
||||
|
||||
#include "AsyncTCP_SSL.hpp"
|
||||
|
||||
#endif /* ASYNCTCP_SSL_H_ */
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
Loading…
Add table
Add a link
Reference in a new issue