Merge branch 'main' into more-batt

This commit is contained in:
Jaakko Haakana 2025-05-21 21:15:40 +03:00
commit 12df312eef
64 changed files with 3145 additions and 2034 deletions

View file

@ -46,6 +46,10 @@ void setup_can_shunt();
#include "FOXESS-BATTERY.h"
#endif
#ifdef GEELY_GEOMETRY_C_BATTERY
#include "GEELY-GEOMETRY-C-BATTERY.h"
#endif
#ifdef ORION_BMS
#include "ORION-BMS.h"
#endif
@ -151,7 +155,7 @@ void setup_battery(void);
void update_values_battery();
#ifdef RS485_BATTERY_SELECTED
void transmit_rs485();
void transmit_rs485(unsigned long currentMillis);
void receive_RS485();
#else
void handle_incoming_can_frame_battery(CAN_frame rx_frame);

View file

@ -156,24 +156,22 @@ void decode_packet(uint8_t command, uint8_t data[8]) {
}
}
void DalyBms::transmit_rs485() {
void DalyBms::transmit_rs485(unsigned long currentMillis) {
static uint8_t nextCommand = 0x90;
if (millis() - lastPacket > 60) {
if (currentMillis - lastPacket > 60) {
lastPacket = currentMillis;
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;

View file

@ -22,7 +22,7 @@ class DalyBms : public RS485Battery {
public:
void setup();
void update_values();
void transmit_rs485();
void transmit_rs485(unsigned long currentMillis);
void receive_RS485();
private:

View file

@ -0,0 +1,675 @@
#include "../include.h"
#ifdef GEELY_GEOMETRY_C_BATTERY
#include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h"
#include "../datalayer/datalayer_extended.h" //For "More battery info" webpage
#include "../devboard/utils/events.h"
#include "GEELY-GEOMETRY-C-BATTERY.h"
/* TODO
- Contactor closing: CAN log needed from complete H-CAN of Geely Geometry C vehicle. We are not sure what needs to be sent towards the battery yet to get contactor closing working. DTC readout complains that a "Power CAN BUS Data Missing" message is still missing
- Unsure if the current CAN sending routine is enough to keep BMS alive 24/7. Testing needed
- There are a few UNKNOWN PID polls, these need to be decoded
- Some critical values are still missing:
- Current sensor (Mandatory!)
- Max charge power (can be estimated)
- Max discharge power (can be estimated)
- Cell voltage min/max (not mandatory, but very nice to have)
- All cell voltages (not mandatory, but very nice to have)
Node descriptions, these can send CAN messages in the Geely Geometry C
DSCU (Drivers Seat Control Unit)
OBC (On Board Charger)
FRS (Front Radar System)
IPU (Integrated Power Unit Control)
EGSM (Electronic Gear Shifter)
MMI
T-BOX (Electrocar Communication Control Module)
IPK
FCS
FRS
TCM(SAS)
RPS
ESC
ACU(YRS)
DSCU
PEPS
ESCL
BCM (Body Control Module)
AC
BMSH (Battery Management System)
VCU (Vehicle Control Unit)
AVAS
IB
RSRS
RML
There are 4 CAN buses in the Geometry C, we are interested in the Hybrid CAN (HB-CAN)
- Hybrid, HB CAN: gateway, electronic shifter, VCU, T-BOX, BMS, high and low voltage charging system, integrated power controller
- Infotainent, IF CAN: gateway, diagnostic interface, combined instrument, controller, head-up display, audio host, T-BOX
- Comfort, CF CAN: gateway, diagnostic interface, low-speed alarm controller, thermal management control module, electronic
steering column lock, BCM, seat module
- Chassis, CS CAN: gateway, steering wheel angle sensor, front monocular camera, VCU, millimeter wave radar probe, EPS,
smart booster, ESC, airbag control module, automatic parking module
*/
/* Do not change code below unless you are sure what you are doing */
void GeelyGeometryCBattery::update_values() {
datalayer_battery->status.soh_pptt;
datalayer_battery->status.real_soc = poll_soc * 10;
datalayer_battery->status.voltage_dV = battery_voltage;
datalayer_battery->status.current_dA;
if (poll_amount_cells == 102) { // We have determined that we are on 70kWh pack
datalayer_battery->info.total_capacity_Wh = 70000;
datalayer_battery->info.max_design_voltage_dV = MAX_PACK_VOLTAGE_70_DV;
datalayer_battery->info.min_design_voltage_dV = MIN_PACK_VOLTAGE_70_DV;
}
//Calculate the remaining Wh amount from SOC% and max Wh value.
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.max_discharge_power_W = discharge_power_allowed * 100;
datalayer_battery->status.max_charge_power_W;
datalayer_battery->status.cell_min_voltage_mV = maximum_cell_voltage - 10; //TODO: Fix once we have min value
datalayer_battery->status.cell_max_voltage_mV = maximum_cell_voltage;
// Initialize highest and lowest to the first element
maximum_temperature = poll_temperature[0];
minimum_temperature = poll_temperature[0];
// Iterate through the array to find the highest and lowest values
for (uint8_t i = 1; i < 6; ++i) {
if (poll_temperature[i] > maximum_temperature) {
maximum_temperature = poll_temperature[i];
}
if (poll_temperature[i] < minimum_temperature) {
minimum_temperature = poll_temperature[i];
}
}
datalayer_battery->status.temperature_min_dC = minimum_temperature * 10;
datalayer_battery->status.temperature_max_dC = maximum_temperature * 10;
if (HVIL_signal > 0) {
set_event(EVENT_HVIL_FAILURE, HVIL_signal);
} else {
clear_event(EVENT_HVIL_FAILURE);
}
//Update webserver more battery info page
memcpy(datalayer_geometryc->BatterySerialNumber, serialnumbers, sizeof(serialnumbers));
memcpy(datalayer_geometryc->ModuleTemperatures, poll_temperature, sizeof(poll_temperature));
memcpy(datalayer_geometryc->BatterySoftwareVersion, poll_software_version, sizeof(poll_software_version));
memcpy(datalayer_geometryc->BatteryHardwareVersion, poll_hardware_version, sizeof(poll_hardware_version));
datalayer_geometryc->soc = poll_soc;
datalayer_geometryc->CC2voltage = poll_cc2_voltage;
datalayer_geometryc->cellMaxVoltageNumber = poll_cell_max_voltage_number;
datalayer_geometryc->cellMinVoltageNumber = poll_cell_min_voltage_number;
datalayer_geometryc->cellTotalAmount = poll_amount_cells;
datalayer_geometryc->specificialVoltage = poll_specificial_voltage;
datalayer_geometryc->unknown1 = poll_unknown1;
datalayer_geometryc->rawSOCmax = poll_raw_soc_max;
datalayer_geometryc->rawSOCmin = poll_raw_soc_min;
datalayer_geometryc->unknown4 = poll_unknown4;
datalayer_geometryc->capModMax = poll_cap_module_max;
datalayer_geometryc->capModMin = poll_cap_module_min;
datalayer_geometryc->unknown7 = poll_unknown7;
datalayer_geometryc->unknown8 = poll_unknown8;
}
const unsigned char crctable[256] = { // CRC8_SAE_J1850_ZER0 formula,0x2F Poly,initial value 0xFF,Final XOR value 0xFF
0x00, 0x2F, 0x5E, 0x71, 0xBC, 0x93, 0xE2, 0xCD, 0x57, 0x78, 0x09, 0x26, 0xEB, 0xC4, 0xB5, 0x9A, 0xAE, 0x81, 0xF0,
0xDF, 0x12, 0x3D, 0x4C, 0x63, 0xF9, 0xD6, 0xA7, 0x88, 0x45, 0x6A, 0x1B, 0x34, 0x73, 0x5C, 0x2D, 0x02, 0xCF, 0xE0,
0x91, 0xBE, 0x24, 0x0B, 0x7A, 0x55, 0x98, 0xB7, 0xC6, 0xE9, 0xDD, 0xF2, 0x83, 0xAC, 0x61, 0x4E, 0x3F, 0x10, 0x8A,
0xA5, 0xD4, 0xFB, 0x36, 0x19, 0x68, 0x47, 0xE6, 0xC9, 0xB8, 0x97, 0x5A, 0x75, 0x04, 0x2B, 0xB1, 0x9E, 0xEF, 0xC0,
0x0D, 0x22, 0x53, 0x7C, 0x48, 0x67, 0x16, 0x39, 0xF4, 0xDB, 0xAA, 0x85, 0x1F, 0x30, 0x41, 0x6E, 0xA3, 0x8C, 0xFD,
0xD2, 0x95, 0xBA, 0xCB, 0xE4, 0x29, 0x06, 0x77, 0x58, 0xC2, 0xED, 0x9C, 0xB3, 0x7E, 0x51, 0x20, 0x0F, 0x3B, 0x14,
0x65, 0x4A, 0x87, 0xA8, 0xD9, 0xF6, 0x6C, 0x43, 0x32, 0x1D, 0xD0, 0xFF, 0x8E, 0xA1, 0xE3, 0xCC, 0xBD, 0x92, 0x5F,
0x70, 0x01, 0x2E, 0xB4, 0x9B, 0xEA, 0xC5, 0x08, 0x27, 0x56, 0x79, 0x4D, 0x62, 0x13, 0x3C, 0xF1, 0xDE, 0xAF, 0x80,
0x1A, 0x35, 0x44, 0x6B, 0xA6, 0x89, 0xF8, 0xD7, 0x90, 0xBF, 0xCE, 0xE1, 0x2C, 0x03, 0x72, 0x5D, 0xC7, 0xE8, 0x99,
0xB6, 0x7B, 0x54, 0x25, 0x0A, 0x3E, 0x11, 0x60, 0x4F, 0x82, 0xAD, 0xDC, 0xF3, 0x69, 0x46, 0x37, 0x18, 0xD5, 0xFA,
0x8B, 0xA4, 0x05, 0x2A, 0x5B, 0x74, 0xB9, 0x96, 0xE7, 0xC8, 0x52, 0x7D, 0x0C, 0x23, 0xEE, 0xC1, 0xB0, 0x9F, 0xAB,
0x84, 0xF5, 0xDA, 0x17, 0x38, 0x49, 0x66, 0xFC, 0xD3, 0xA2, 0x8D, 0x40, 0x6F, 0x1E, 0x31, 0x76, 0x59, 0x28, 0x07,
0xCA, 0xE5, 0x94, 0xBB, 0x21, 0x0E, 0x7F, 0x50, 0x9D, 0xB2, 0xC3, 0xEC, 0xD8, 0xF7, 0x86, 0xA9, 0x64, 0x4B, 0x3A,
0x15, 0x8F, 0xA0, 0xD1, 0xFE, 0x33, 0x1C, 0x6D, 0x42};
bool is_message_corrupt(CAN_frame* rx_frame) {
uint8_t crc = 0xFF; // Initial value
for (uint8_t j = 0; j < 7; j++) {
crc = crctable[crc ^ rx_frame->data.u8[j]];
}
crc = (crc ^ 0xFF); // Final XOR
return crc != rx_frame->data.u8[7];
}
void GeelyGeometryCBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
switch (rx_frame.ID) {
case 0x0B0: //10ms
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
if (is_message_corrupt(&rx_frame)) {
datalayer.battery2.status.CAN_error_counter++;
break; //Message content malformed, abort reading data from it
}
//Contains:
//HVIL Signal
// - HVIL not connected: 000000B0 00 8 10 06 00 00 00 00 8E 31
// - HVIL connected: 000000B0 00 8 10 00 00 00 00 00 82 0D
// Based on this, the two HVIL is most likely frame1
HVIL_signal = (rx_frame.data.u8[1] & 0x0F);
//BatteryDchgSysFaultLevel
//ChgFaultLevel
//frame7, CRC
//frame6, low byte counter 0-F
break;
case 0x178: //10ms (64 13 88 00 0E 30 0A 85)
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
if (is_message_corrupt(&rx_frame)) {
datalayer.battery2.status.CAN_error_counter++;
break; //Message content malformed, abort reading data from it
}
battery_voltage = ((rx_frame.data.u8[4] & 0x1F) << 8) | rx_frame.data.u8[5];
//frame7, CRC
//frame6, low byte counter 0-F
break;
case 0x179: //20ms (3E 52 BA 5D A4 3F 0C D9)
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
if (is_message_corrupt(&rx_frame)) {
datalayer.battery2.status.CAN_error_counter++;
break; //Message content malformed, abort reading data from it
}
//2BA = 69.8 //Potentially charge power allowed
//frame7, CRC
//frame6, low byte counter 0-F
break;
case 0x17A: //100ms (Battery 01 B4 52 28 4A 46 6E AE)
//(Car log 0A 3D EE F1 BD C6 67 F7)
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
if (is_message_corrupt(&rx_frame)) {
datalayer.battery2.status.CAN_error_counter++;
break; //Message content malformed, abort reading data from it
}
//frame7, CRC
//frame6, low byte counter 0-F
break;
case 0x17B: //20ms (00 00 10 00 0F FE 03 C9) (car is the same, static)
if (is_message_corrupt(&rx_frame)) {
datalayer.battery2.status.CAN_error_counter++;
break; //Message content malformed, abort reading data from it
}
//frame7, CRC
//frame6, low byte counter 0-F
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x210: //100ms (38 04 3A 01 38 22 22 39)
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
if (is_message_corrupt(&rx_frame)) {
datalayer.battery2.status.CAN_error_counter++;
break; //Message content malformed, abort reading data from it
}
discharge_power_allowed = ((rx_frame.data.u8[1] & 0x0F) << 8) | rx_frame.data.u8[2]; //TODO, not confirmed
//43A = 108.2kW potentially discharge power allowed
//frame7, CRC
//frame6, counter 0 - 0x22
break;
case 0x211: //100ms (00 D8 C6 00 00 00 0F 3A)
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
if (is_message_corrupt(&rx_frame)) {
datalayer.battery2.status.CAN_error_counter++;
break; //Message content malformed, abort reading data from it
}
//frame7, CRC
//frame6, low byte counter 0-F
break;
case 0x212: //500ms (Completely empty)
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x351: //100ms (4A 31 71 B8 6E F8 84 00)
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x352: //500ms (76 78 00 00 82 FF FF 00)
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x353: //500ms (00 00 00 00 62 00 EA 5D) (car 00 00 00 00 00 00 E6 04)
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x354: //500ms (Completely empty)
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x355: //500ms (89 4A 03 5C 39 06 04 00)
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x356: //1s (6B 09 0C 69 0A F1 D3 86)
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x357: //20ms (18 17 6F 20 00 00 00 00)
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
//Frame 0 and 1 seems to count something large
break;
case 0x358: //1s (03 DF 10 3C DA 0E 20 DE)
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x359: //1s (1F 40 00 00 00 00 00 36)
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
//Frame7 loops 01-21-22-36-01-21...
break;
case 0x35B: //200ms (Serialnumbers)
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
mux = rx_frame.data.u8[0];
switch (mux) {
case 0x01:
serialnumbers[0] = rx_frame.data.u8[1];
serialnumbers[1] = rx_frame.data.u8[2];
serialnumbers[2] = rx_frame.data.u8[3];
serialnumbers[3] = rx_frame.data.u8[4];
serialnumbers[4] = rx_frame.data.u8[5];
serialnumbers[5] = rx_frame.data.u8[6];
serialnumbers[6] = rx_frame.data.u8[7];
break;
case 0x02:
serialnumbers[7] = rx_frame.data.u8[1];
serialnumbers[8] = rx_frame.data.u8[2];
serialnumbers[9] = rx_frame.data.u8[3];
serialnumbers[10] = rx_frame.data.u8[4];
serialnumbers[11] = rx_frame.data.u8[5];
serialnumbers[12] = rx_frame.data.u8[6];
serialnumbers[13] = rx_frame.data.u8[7];
break;
case 0x03:
serialnumbers[14] = rx_frame.data.u8[1];
serialnumbers[15] = rx_frame.data.u8[2];
serialnumbers[16] = rx_frame.data.u8[3];
serialnumbers[17] = rx_frame.data.u8[4];
serialnumbers[18] = rx_frame.data.u8[5];
serialnumbers[19] = rx_frame.data.u8[6];
serialnumbers[20] = rx_frame.data.u8[7];
break;
case 0x04:
serialnumbers[21] = rx_frame.data.u8[1];
serialnumbers[22] = rx_frame.data.u8[2];
serialnumbers[23] = rx_frame.data.u8[3];
serialnumbers[24] = rx_frame.data.u8[4];
serialnumbers[25] = rx_frame.data.u8[5];
serialnumbers[26] = rx_frame.data.u8[6];
serialnumbers[27] = rx_frame.data.u8[7];
break;
default:
break;
}
break;
case 0x424: //500ms (24 10 01 01 02 00 00 00)
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x7EA:
if (rx_frame.data.u8[0] == 0x10) { //Multiframe response, send ACK
transmit_can_frame(&GEELY_ACK, can_config.battery);
//Multiframe has the poll reply slightly different location
incoming_poll = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
}
if (rx_frame.data.u8[0] < 0x10) { //One line response
incoming_poll = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3];
switch (incoming_poll) {
case POLL_SOC:
poll_soc = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CC2_VOLTAGE:
poll_cc2_voltage = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CELL_MAX_VOLTAGE_NUMBER:
poll_cell_max_voltage_number = rx_frame.data.u8[4];
break;
case POLL_CELL_MIN_VOLTAGE_NUMBER:
poll_cell_min_voltage_number = rx_frame.data.u8[4];
break;
case POLL_AMOUNT_CELLS:
poll_amount_cells = rx_frame.data.u8[4];
datalayer_battery->info.number_of_cells = poll_amount_cells;
break;
case POLL_SPECIFICIAL_VOLTAGE:
poll_specificial_voltage = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_UNKNOWN_1:
poll_unknown1 = rx_frame.data.u8[4];
break;
case POLL_RAW_SOC_MAX:
poll_raw_soc_max = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_RAW_SOC_MIN:
poll_raw_soc_min = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_UNKNOWN_4:
poll_unknown4 = rx_frame.data.u8[4];
break;
case POLL_CAPACITY_MODULE_MAX:
poll_cap_module_max = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CAPACITY_MODULE_MIN:
poll_cap_module_min = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_UNKNOWN_7:
poll_unknown7 = rx_frame.data.u8[4];
break;
case POLL_UNKNOWN_8:
poll_unknown8 = rx_frame.data.u8[4];
break;
default:
break;
}
}
switch (incoming_poll) //Multiframe response
{
case POLL_MULTI_TEMPS:
switch (rx_frame.data.u8[0]) {
case 0x10:
poll_temperature[0] = (rx_frame.data.u8[5] - TEMP_OFFSET);
poll_temperature[1] = (rx_frame.data.u8[6] - TEMP_OFFSET);
poll_temperature[2] = (rx_frame.data.u8[7] - TEMP_OFFSET);
break;
case 0x21:
poll_temperature[3] = (rx_frame.data.u8[1] - TEMP_OFFSET);
poll_temperature[4] = (rx_frame.data.u8[2] - TEMP_OFFSET);
poll_temperature[5] = (rx_frame.data.u8[3] - TEMP_OFFSET);
break;
default:
break;
}
break;
case POLL_MULTI_SOFTWARE_VERSION:
switch (rx_frame.data.u8[0]) {
case 0x10:
poll_software_version[0] = rx_frame.data.u8[5];
poll_software_version[1] = rx_frame.data.u8[6];
poll_software_version[2] = rx_frame.data.u8[7];
break;
case 0x21:
poll_software_version[3] = rx_frame.data.u8[1];
poll_software_version[4] = rx_frame.data.u8[2];
poll_software_version[5] = rx_frame.data.u8[3];
poll_software_version[6] = rx_frame.data.u8[4];
poll_software_version[7] = rx_frame.data.u8[5];
poll_software_version[8] = rx_frame.data.u8[6];
poll_software_version[9] = rx_frame.data.u8[7];
break;
case 0x22:
poll_software_version[10] = rx_frame.data.u8[1];
poll_software_version[11] = rx_frame.data.u8[2];
poll_software_version[12] = rx_frame.data.u8[3];
poll_software_version[13] = rx_frame.data.u8[4];
poll_software_version[14] = rx_frame.data.u8[5];
poll_software_version[15] = rx_frame.data.u8[6];
break;
case 0x23:
break;
default:
break;
}
break;
case POLL_MULTI_HARDWARE_VERSION:
switch (rx_frame.data.u8[0]) {
case 0x10:
poll_hardware_version[0] = rx_frame.data.u8[5];
poll_hardware_version[1] = rx_frame.data.u8[6];
poll_hardware_version[2] = rx_frame.data.u8[7];
break;
case 0x21:
poll_hardware_version[3] = rx_frame.data.u8[1];
poll_hardware_version[4] = rx_frame.data.u8[2];
poll_hardware_version[5] = rx_frame.data.u8[3];
poll_hardware_version[6] = rx_frame.data.u8[4];
poll_hardware_version[7] = rx_frame.data.u8[5];
poll_hardware_version[8] = rx_frame.data.u8[6];
poll_hardware_version[9] = rx_frame.data.u8[7];
break;
case 0x22:
poll_hardware_version[10] = rx_frame.data.u8[1];
poll_hardware_version[11] = rx_frame.data.u8[2];
poll_hardware_version[12] = rx_frame.data.u8[3];
poll_hardware_version[13] = rx_frame.data.u8[4];
poll_hardware_version[14] = rx_frame.data.u8[5];
poll_hardware_version[15] = rx_frame.data.u8[6];
break;
case 0x23:
break;
default:
break;
}
break;
default:
//Not a multiframe response, do nothing
break;
}
break;
default:
break;
}
}
uint8_t calc_crc8_geely(CAN_frame* rx_frame) {
uint8_t crc = 0xFF; // Initial value
for (uint8_t j = 0; j < 7; j++) {
crc = crctable[crc ^ rx_frame->data.u8[j]];
}
return crc ^ 0xFF; // Final XOR
}
void GeelyGeometryCBattery::transmit_can(unsigned long currentMillis) {
// Send 10ms CAN Message
if (currentMillis - previousMillis10 >= INTERVAL_10_MS) {
previousMillis10 = currentMillis;
GEELY_191.data.u8[6] = ((GEELY_191.data.u8[6] & 0xF0) | counter_10ms);
GEELY_191.data.u8[7] = calc_crc8_geely(&GEELY_191);
GEELY_0A6.data.u8[6] = ((GEELY_0A6.data.u8[6] & 0xF0) | counter_10ms);
GEELY_0A6.data.u8[7] = calc_crc8_geely(&GEELY_0A6);
GEELY_165.data.u8[6] = ((GEELY_165.data.u8[6] & 0xF0) | counter_10ms);
GEELY_165.data.u8[7] = calc_crc8_geely(&GEELY_165);
GEELY_1A4.data.u8[6] = ((GEELY_1A4.data.u8[6] & 0xF0) | counter_10ms);
GEELY_1A4.data.u8[7] = calc_crc8_geely(&GEELY_1A4);
GEELY_162.data.u8[6] = ((GEELY_162.data.u8[6] & 0xF0) | counter_10ms);
GEELY_162.data.u8[7] = calc_crc8_geely(&GEELY_162);
GEELY_1A5.data.u8[6] = ((GEELY_1A5.data.u8[6] & 0xF0) | counter_10ms);
GEELY_1A5.data.u8[7] = calc_crc8_geely(&GEELY_1A5);
GEELY_220.data.u8[6] = ((GEELY_220.data.u8[6] & 0xF0) | counter_10ms);
GEELY_220.data.u8[7] = calc_crc8_geely(&GEELY_220);
GEELY_0E0.data.u8[4] = ((GEELY_0E0.data.u8[4] & 0xF0) | counter_10ms); //unique
GEELY_0E0.data.u8[5] = calc_crc8_geely(&GEELY_0E0); //unique
counter_10ms = (counter_10ms + 1) % 17; // 0-1-...F-0-1 etc.
transmit_can_frame(&GEELY_191, can_config.battery);
transmit_can_frame(&GEELY_0A6, can_config.battery);
transmit_can_frame(&GEELY_160, can_config.battery);
transmit_can_frame(&GEELY_165, can_config.battery);
transmit_can_frame(&GEELY_1A4, can_config.battery);
transmit_can_frame(&GEELY_162, can_config.battery); //CONFIRMED MANDATORY! VCU message
transmit_can_frame(&GEELY_1A5, can_config.battery);
transmit_can_frame(&GEELY_220, can_config.battery); //CONFIRMED MANDATORY! OBC message
transmit_can_frame(&GEELY_0E0, can_config.battery);
}
if (currentMillis - previousMillis20 >= INTERVAL_20_MS) {
previousMillis20 = currentMillis;
GEELY_145.data.u8[6] = ((GEELY_145.data.u8[6] & 0xF0) | counter_10ms);
GEELY_145.data.u8[7] = calc_crc8_geely(&GEELY_145);
GEELY_150.data.u8[6] = ((GEELY_150.data.u8[6] & 0xF0) | counter_10ms);
GEELY_150.data.u8[7] = calc_crc8_geely(&GEELY_150);
counter_20ms = (counter_20ms + 1) % 17; // 0-1-...F-0-1 etc.
transmit_can_frame(&GEELY_145, can_config.battery); //CONFIRMED MANDATORY! shifter
transmit_can_frame(&GEELY_0F9, can_config.battery); //CONFIRMED MANDATORY! shifter
transmit_can_frame(&GEELY_0FA, can_config.battery); //Might be unnecessary, not in workshop manual
transmit_can_frame(&GEELY_197, can_config.battery); //Might be unnecessary, not in workshop manual
transmit_can_frame(&GEELY_150, can_config.battery);
}
if (currentMillis - previousMillis50 >= INTERVAL_50_MS) {
previousMillis50 = currentMillis;
GEELY_1A3.data.u8[6] = ((GEELY_1A3.data.u8[6] & 0xF0) | counter_10ms);
GEELY_1A3.data.u8[7] = calc_crc8_geely(&GEELY_1A3);
GEELY_0A8.data.u8[6] = ((GEELY_0A8.data.u8[6] & 0xF0) | counter_10ms);
GEELY_0A8.data.u8[7] = calc_crc8_geely(&GEELY_0A8);
counter_50ms = (counter_50ms + 1) % 17; // 0-1-...F-0-1 etc.
transmit_can_frame(&GEELY_1B2, can_config.battery);
transmit_can_frame(&GEELY_221, can_config.battery); //CONFIRMED MANDATORY! OBC message
//transmit_can_frame(&GEELY_1A3, can_config.battery); //Might be unnecessary, radar info
transmit_can_frame(&GEELY_1A7, can_config.battery); //Might be unnecessary
transmit_can_frame(&GEELY_0A8, can_config.battery); //CONFIRMED MANDATORY! IPU message
transmit_can_frame(&GEELY_1F2, can_config.battery); //Might be unnecessary, not in manual
transmit_can_frame(&GEELY_1A6, can_config.battery); //Might be unnecessary, not in manual
}
// Send 100ms CAN Message
if (currentMillis - previousMillis100 >= INTERVAL_100_MS) {
previousMillis100 = currentMillis;
GEELY_0A8.data.u8[6] = ((GEELY_0A8.data.u8[6] & 0x0F) | (counter_10ms << 4)); //unique bitshift
GEELY_0A8.data.u8[7] = calc_crc8_geely(&GEELY_0A8);
counter_100ms = (counter_100ms + 1) % 17; // 0-1-...F-0-1 etc.
transmit_can_frame(&GEELY_222, can_config.battery); //CONFIRMED MANDATORY! OBC message
//transmit_can_frame(&GEELY_2D2, can_config.battery); //Might be unnecessary, seat info
transmit_can_frame(&GEELY_292, can_config.battery); //CONFIRMED MANDATORY! T-BOX
}
// Send 200ms CAN Message
if (currentMillis - previousMillis200 >= INTERVAL_200_MS) {
previousMillis200 = currentMillis;
switch (poll_pid) {
case POLL_SOC:
GEELY_POLL.data.u8[2] = (uint8_t)(POLL_SOC >> 8);
GEELY_POLL.data.u8[3] = (uint8_t)POLL_SOC;
poll_pid = POLL_CC2_VOLTAGE;
break;
case POLL_CC2_VOLTAGE:
GEELY_POLL.data.u8[2] = (uint8_t)(POLL_CC2_VOLTAGE >> 8);
GEELY_POLL.data.u8[3] = (uint8_t)POLL_CC2_VOLTAGE;
poll_pid = POLL_CELL_MAX_VOLTAGE_NUMBER;
break;
case POLL_CELL_MAX_VOLTAGE_NUMBER:
GEELY_POLL.data.u8[2] = (uint8_t)(POLL_CELL_MAX_VOLTAGE_NUMBER >> 8);
GEELY_POLL.data.u8[3] = (uint8_t)POLL_CELL_MAX_VOLTAGE_NUMBER;
poll_pid = POLL_CELL_MIN_VOLTAGE_NUMBER;
break;
case POLL_CELL_MIN_VOLTAGE_NUMBER:
GEELY_POLL.data.u8[2] = (uint8_t)(POLL_CELL_MIN_VOLTAGE_NUMBER >> 8);
GEELY_POLL.data.u8[3] = (uint8_t)POLL_CELL_MIN_VOLTAGE_NUMBER;
poll_pid = POLL_AMOUNT_CELLS;
break;
case POLL_AMOUNT_CELLS:
GEELY_POLL.data.u8[2] = (uint8_t)(POLL_AMOUNT_CELLS >> 8);
GEELY_POLL.data.u8[3] = (uint8_t)POLL_AMOUNT_CELLS;
poll_pid = POLL_SPECIFICIAL_VOLTAGE;
break;
case POLL_SPECIFICIAL_VOLTAGE:
GEELY_POLL.data.u8[2] = (uint8_t)(POLL_SPECIFICIAL_VOLTAGE >> 8);
GEELY_POLL.data.u8[3] = (uint8_t)POLL_SPECIFICIAL_VOLTAGE;
poll_pid = POLL_UNKNOWN_1;
break;
case POLL_UNKNOWN_1:
GEELY_POLL.data.u8[2] = (uint8_t)(POLL_UNKNOWN_1 >> 8);
GEELY_POLL.data.u8[3] = (uint8_t)POLL_UNKNOWN_1;
poll_pid = POLL_RAW_SOC_MAX;
break;
case POLL_RAW_SOC_MAX:
GEELY_POLL.data.u8[2] = (uint8_t)(POLL_RAW_SOC_MAX >> 8);
GEELY_POLL.data.u8[3] = (uint8_t)POLL_RAW_SOC_MAX;
poll_pid = POLL_RAW_SOC_MIN;
break;
case POLL_RAW_SOC_MIN:
GEELY_POLL.data.u8[2] = (uint8_t)(POLL_RAW_SOC_MIN >> 8);
GEELY_POLL.data.u8[3] = (uint8_t)POLL_RAW_SOC_MIN;
poll_pid = POLL_UNKNOWN_4;
break;
case POLL_UNKNOWN_4:
GEELY_POLL.data.u8[2] = (uint8_t)(POLL_UNKNOWN_4 >> 8);
GEELY_POLL.data.u8[3] = (uint8_t)POLL_UNKNOWN_4;
poll_pid = POLL_CAPACITY_MODULE_MAX;
break;
case POLL_CAPACITY_MODULE_MAX:
GEELY_POLL.data.u8[2] = (uint8_t)(POLL_CAPACITY_MODULE_MAX >> 8);
GEELY_POLL.data.u8[3] = (uint8_t)POLL_CAPACITY_MODULE_MAX;
poll_pid = POLL_CAPACITY_MODULE_MIN;
break;
case POLL_CAPACITY_MODULE_MIN:
GEELY_POLL.data.u8[2] = (uint8_t)(POLL_CAPACITY_MODULE_MIN >> 8);
GEELY_POLL.data.u8[3] = (uint8_t)POLL_CAPACITY_MODULE_MIN;
poll_pid = POLL_UNKNOWN_7;
break;
case POLL_UNKNOWN_7:
GEELY_POLL.data.u8[2] = (uint8_t)(POLL_UNKNOWN_7 >> 8);
GEELY_POLL.data.u8[3] = (uint8_t)POLL_UNKNOWN_7;
poll_pid = POLL_UNKNOWN_8;
break;
case POLL_UNKNOWN_8:
GEELY_POLL.data.u8[2] = (uint8_t)(POLL_UNKNOWN_8 >> 8);
GEELY_POLL.data.u8[3] = (uint8_t)POLL_UNKNOWN_8;
poll_pid = POLL_MULTI_TEMPS;
break;
case POLL_MULTI_TEMPS:
GEELY_POLL.data.u8[2] = (uint8_t)(POLL_MULTI_TEMPS >> 8);
GEELY_POLL.data.u8[3] = (uint8_t)POLL_MULTI_TEMPS;
poll_pid = POLL_MULTI_UNKNOWN_2;
break;
case POLL_MULTI_UNKNOWN_2:
GEELY_POLL.data.u8[2] = (uint8_t)(POLL_MULTI_UNKNOWN_2 >> 8);
GEELY_POLL.data.u8[3] = (uint8_t)POLL_MULTI_UNKNOWN_2;
poll_pid = POLL_MULTI_UNKNOWN_3;
break;
case POLL_MULTI_UNKNOWN_3:
GEELY_POLL.data.u8[2] = (uint8_t)(POLL_MULTI_UNKNOWN_3 >> 8);
GEELY_POLL.data.u8[3] = (uint8_t)POLL_MULTI_UNKNOWN_3;
poll_pid = POLL_MULTI_UNKNOWN_4;
break;
case POLL_MULTI_UNKNOWN_4:
GEELY_POLL.data.u8[2] = (uint8_t)(POLL_MULTI_UNKNOWN_4 >> 8);
GEELY_POLL.data.u8[3] = (uint8_t)POLL_MULTI_UNKNOWN_4;
poll_pid = POLL_MULTI_HARDWARE_VERSION;
break;
case POLL_MULTI_HARDWARE_VERSION:
GEELY_POLL.data.u8[2] = (uint8_t)(POLL_MULTI_HARDWARE_VERSION >> 8);
GEELY_POLL.data.u8[3] = (uint8_t)POLL_MULTI_HARDWARE_VERSION;
poll_pid = POLL_MULTI_SOFTWARE_VERSION;
break;
case POLL_MULTI_SOFTWARE_VERSION:
GEELY_POLL.data.u8[2] = (uint8_t)(POLL_MULTI_SOFTWARE_VERSION >> 8);
GEELY_POLL.data.u8[3] = (uint8_t)POLL_MULTI_SOFTWARE_VERSION;
poll_pid = POLL_SOC;
break;
default:
poll_pid = POLL_SOC;
break;
}
transmit_can_frame(&GEELY_POLL, can_config.battery);
}
}
void GeelyGeometryCBattery::setup(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, "Geely Geometry C", 63);
datalayer.system.info.battery_protocol[63] = '\0';
datalayer.system.status.battery_allows_contactor_closing = true;
datalayer_battery->info.number_of_cells = 102; //70kWh pack has 102S, startup in this mode
datalayer_battery->info.max_design_voltage_dV = MAX_PACK_VOLTAGE_70_DV; //Startup in extreme ends
datalayer_battery->info.min_design_voltage_dV = MIN_PACK_VOLTAGE_53_DV; //Before pack size determined
datalayer_battery->info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
datalayer_battery->info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
datalayer_battery->info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV;
}
#endif

View file

@ -0,0 +1,217 @@
#ifndef GEELY_GEOMETRY_C_BATTERY_H
#define GEELY_GEOMETRY_C_BATTERY_H
#include "../datalayer/datalayer.h"
#include "../datalayer/datalayer_extended.h"
#include "../include.h"
#include "CanBattery.h"
#define BATTERY_SELECTED
#define SELECTED_BATTERY_CLASS GeelyGeometryCBattery
#define POLL_SOC 0x4B35
#define POLL_CC2_VOLTAGE 0x4BCF
#define POLL_CELL_MAX_VOLTAGE_NUMBER 0x4B1E
#define POLL_CELL_MIN_VOLTAGE_NUMBER 0x4B20
#define POLL_AMOUNT_CELLS 0x4B07
#define POLL_SPECIFICIAL_VOLTAGE 0x4B05
#define POLL_UNKNOWN_1 0x4BDA //245 on two batteries
#define POLL_RAW_SOC_MAX 0x4BC3
#define POLL_RAW_SOC_MIN 0x4BC4
#define POLL_UNKNOWN_4 0xDF00 //144 (the other battery 143)
#define POLL_CAPACITY_MODULE_MAX 0x4B3D
#define POLL_CAPACITY_MODULE_MIN 0x4B3E
#define POLL_UNKNOWN_7 0x4B24 //1 (the other battery 23)
#define POLL_UNKNOWN_8 0x4B26 //16 (the other battery 33)
#define POLL_MULTI_TEMPS 0x4B0F
#define POLL_MULTI_UNKNOWN_2 0x4B10
#define POLL_MULTI_UNKNOWN_3 0x4B53
#define POLL_MULTI_UNKNOWN_4 0x4B54
#define POLL_MULTI_HARDWARE_VERSION 0x4B6B
#define POLL_MULTI_SOFTWARE_VERSION 0x4B6C
class GeelyGeometryCBattery : public CanBattery {
public:
virtual void setup(void);
virtual void handle_incoming_can_frame(CAN_frame rx_frame);
virtual void update_values();
virtual void transmit_can(unsigned long currentMillis);
private:
const int MAX_PACK_VOLTAGE_70_DV 4420 //70kWh
const int MIN_PACK_VOLTAGE_70_DV 2860 const int MAX_PACK_VOLTAGE_53_DV 4160 //53kWh
const int MIN_PACK_VOLTAGE_53_DV 2700 const int MAX_CELL_DEVIATION_MV 150 const int
MAX_CELL_VOLTAGE_MV 4250 //Battery is put into emergency stop if one cell goes over this value
const int MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value
DATALAYER_BATTERY_TYPE* datalayer_battery;
DATALAYER_INFO_GEELY_GEOMETRY_C* datalayer_geometryc;
CAN_frame GEELY_191 = {.FD = false, //PAS_APA_Status , 10ms
.ext_ID = false,
.DLC = 8,
.ID = 0x191,
.data = {0x00, 0x00, 0x81, 0x20, 0x00, 0x00, 0x00, 0x01}};
CAN_frame GEELY_2D2 = {.FD = false, //DSCU 100ms
.ext_ID = false,
.DLC = 8,
.ID = 0x2D2,
.data = {0x60, 0x8E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame GEELY_0A6 = {.FD = false, //VCU 10ms
.ext_ID = false,
.DLC = 8,
.ID = 0x0A6,
.data = {0xFA, 0x0F, 0xA0, 0x00, 0x00, 0xFA, 0x00, 0xE4}};
CAN_frame GEELY_160 = {.FD = false, //VCU 10ms
.ext_ID = false,
.DLC = 8,
.ID = 0x160,
.data = {0x00, 0x01, 0x67, 0xF7, 0xC0, 0x19, 0x00, 0x20}};
CAN_frame GEELY_165 = {.FD = false, //VCU_ModeControl 10ms
.ext_ID = false,
.DLC = 8,
.ID = 0x165,
.data = {0x00, 0x81, 0xA1, 0x00, 0x00, 0x1E, 0x00, 0xD6}};
CAN_frame GEELY_1A4 = {.FD = false, //VCU 10ms
.ext_ID = false,
.DLC = 8,
.ID = 0x1A4,
.data = {0x17, 0x73, 0x17, 0x70, 0x02, 0x1C, 0x00, 0x56}};
CAN_frame GEELY_162 = {.FD = false, //VCU 10ms
.ext_ID = false,
.DLC = 8,
.ID = 0x162,
.data = {0x00, 0x05, 0x06, 0x81, 0x00, 0x09, 0x00, 0xC6}};
CAN_frame GEELY_1A5 = {.FD = false, //VCU 10ms
.ext_ID = false,
.DLC = 8,
.ID = 0x1A5,
.data = {0x17, 0x70, 0x24, 0x0B, 0x00, 0x00, 0x00, 0xF9}};
CAN_frame GEELY_1B2 = {.FD = false, //??? 50ms
.ext_ID = false,
.DLC = 8,
.ID = 0x1B2,
.data = {0x17, 0x70, 0x24, 0x0B, 0x00, 0x00, 0x00, 0xF9}};
CAN_frame GEELY_221 = {.FD = false, //OBC 50ms
.ext_ID = false,
.DLC = 8,
.ID = 0x221,
.data = {0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00}};
CAN_frame GEELY_220 = {.FD = false, //OBC 100ms
.ext_ID = false,
.DLC = 8,
.ID = 0x220,
.data = {0x0B, 0x43, 0x69, 0xF3, 0x3A, 0x10, 0x00, 0x31}};
CAN_frame GEELY_1A3 = {.FD = false, //FRS 50ms
.ext_ID = false,
.DLC = 8,
.ID = 0x1A3,
.data = {0xFF, 0x18, 0x20, 0x00, 0x00, 0x00, 0x00, 0x4F}};
CAN_frame GEELY_1A7 = {.FD = false, //??? 50ms
.ext_ID = false,
.DLC = 8,
.ID = 0x1A7,
.data = {0x00, 0x7F, 0x00, 0x00, 0x7F, 0x00, 0x00, 0x00}};
CAN_frame GEELY_0A8 = {.FD = false, //IPU 100ms
.ext_ID = false,
.DLC = 8,
.ID = 0x0A8,
.data = {0x00, 0x2E, 0xDC, 0x4E, 0x20, 0x00, 0x20, 0xA2}};
CAN_frame GEELY_1F2 = {.FD = false, //??? 50ms
.ext_ID = false,
.DLC = 8,
.ID = 0x1F2,
.data = {0x9B, 0xA3, 0x99, 0xA2, 0x41, 0x42, 0x41, 0x42}};
CAN_frame GEELY_222 = {.FD = false, //OBC 100ms
.ext_ID = false,
.DLC = 8,
.ID = 0x222,
.data = {0x00, 0x00, 0x00, 0xFF, 0xF8, 0x00, 0x00, 0x00}};
CAN_frame GEELY_1A6 = {.FD = false, //OBC 100ms
.ext_ID = false,
.DLC = 8,
.ID = 0x1A6,
.data = {0x00, 0x7F, 0x00, 0x00, 0x7F, 0x00, 0x00, 0x00}};
CAN_frame GEELY_145 = {.FD = false, //EGSM 20ms
.ext_ID = false,
.DLC = 8,
.ID = 0x145,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6A}};
CAN_frame GEELY_0E0 = {.FD = false, //IPU 10ms
.ext_ID = false,
.DLC = 8,
.ID = 0x0E0,
.data = {0xFF, 0x09, 0x00, 0xE0, 0x00, 0x8F, 0x00, 0x00}};
CAN_frame GEELY_0F9 = {.FD = false, //??? 20ms
.ext_ID = false,
.DLC = 8,
.ID = 0x0F9,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame GEELY_292 = {.FD = false, //T-BOX 100ms
.ext_ID = false,
.DLC = 8,
.ID = 0x292,
.data = {0x00, 0x00, 0x00, 0x1F, 0xE7, 0xE7, 0x00, 0xBC}};
CAN_frame GEELY_0FA = {.FD = false, //??? 20ms
.ext_ID = false,
.DLC = 8,
.ID = 0x0FA,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame GEELY_197 = {.FD = false, //??? 20ms
.ext_ID = false,
.DLC = 8,
.ID = 0x197,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6A}};
CAN_frame GEELY_150 = {.FD = false, //EPS 20ms
.ext_ID = false,
.DLC = 8,
.ID = 0x150,
.data = {0x7E, 0x00, 0x24, 0x00, 0x01, 0x01, 0x00, 0xA9}};
CAN_frame GEELY_POLL = {.FD = false, //Polling frame
.ext_ID = false,
.DLC = 8,
.ID = 0x7E2,
.data = {0x03, 0x22, 0x4B, 0xDA, 0x00, 0x00, 0x00, 0x00}};
CAN_frame GEELY_ACK = {.FD = false, //Ack frame
.ext_ID = false,
.DLC = 8,
.ID = 0x7E2,
.data = {0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
uint16_t poll_pid = POLL_SOC;
uint16_t incoming_poll = 0;
uint8_t counter_10ms = 0;
uint8_t counter_20ms = 0;
uint8_t counter_50ms = 0;
uint8_t counter_100ms = 0;
unsigned long previousMillis10 = 0; // will store last time a 10ms CAN Message was sent
unsigned long previousMillis20 = 0; // will store last time a 20ms CAN Message was sent
unsigned long previousMillis50 = 0; // will store last time a 50ms CAN Message was sent
unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was sent
unsigned long previousMillis200 = 0; // will store last time a 200ms CAN Message was sent
uint8_t mux = 0;
uint16_t battery_voltage = 3700;
int16_t maximum_temperature = 0;
int16_t minimum_temperature = 0;
uint8_t HVIL_signal = 0;
uint8_t serialnumbers[28] = {0};
uint16_t maximum_cell_voltage = 3700;
uint16_t discharge_power_allowed = 0;
uint16_t poll_soc = 0;
uint16_t poll_cc2_voltage = 0;
uint16_t poll_cell_max_voltage_number = 0;
uint16_t poll_cell_min_voltage_number = 0;
uint16_t poll_amount_cells = 0;
uint16_t poll_specificial_voltage = 0;
uint16_t poll_unknown1 = 0;
uint16_t poll_raw_soc_max = 0;
uint16_t poll_raw_soc_min = 0;
uint16_t poll_unknown4 = 0;
uint16_t poll_cap_module_max = 0;
uint16_t poll_cap_module_min = 0;
uint16_t poll_unknown7 = 0;
uint16_t poll_unknown8 = 0;
int16_t poll_temperature[6] = {0};
#define TEMP_OFFSET 30 //TODO, not calibrated yet, best guess
uint8_t poll_software_version[16] = {0};
uint8_t poll_hardware_version[16] = {0};
};
#endif

View file

@ -122,7 +122,7 @@ uint16_t selectSOC(uint16_t SOC_low, uint16_t SOC_high) {
}
/* These messages are needed for contactor closing */
unsigned long startMillis;
unsigned long startMillis = 0;
uint8_t messageIndex = 0;
uint8_t messageDelays[63] = {0, 0, 5, 10, 10, 15, 19, 19, 20, 20, 25, 30, 30, 35, 40, 40,
45, 49, 49, 50, 50, 52, 53, 53, 54, 55, 60, 60, 65, 67, 67, 70,
@ -1062,7 +1062,7 @@ void KiaEGmpBattery::transmit_can(unsigned long currentMillis) {
}
if (messageIndex >= 63) {
startMillis = millis(); // Start over!
startMillis = currentMillis; // Start over!
messageIndex = 0;
}
@ -1093,9 +1093,6 @@ void KiaEGmpBattery::transmit_can(unsigned long currentMillis) {
void KiaEGmpBattery::setup(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, "Kia/Hyundai EGMP platform", 63);
datalayer.system.info.battery_protocol[63] = '\0';
startMillis = millis(); // Record the starting time
datalayer.system.status.battery_allows_contactor_closing = true;
datalayer.battery.info.number_of_cells = 192; // TODO: will vary depending on battery
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;

View file

@ -90,8 +90,8 @@ class PylonBattery : public CanBattery {
uint8_t cell_quantity_in_module = 0;
uint8_t voltage_level = 0;
uint8_t ah_number = 0;
uint8_t SOC = 0;
uint8_t SOH = 0;
uint8_t SOC = 50;
uint8_t SOH = 100;
uint8_t charge_forbidden = 0;
uint8_t discharge_forbidden = 0;
};

View file

@ -2,20 +2,22 @@
#include "../../datalayer/datalayer.h"
#include "../../datalayer/datalayer_extended.h"
#include "../../include.h"
// Parameters
#ifdef PRECHARGE_CONTROL
// Parameters
#define MAX_PRECHARGE_TIME_MS 15000 // Maximum time precharge may be enabled
#define Precharge_default_PWM_Freq 11000
#define Precharge_min_PWM_Freq 5000
#define Precharge_max_PWM_Freq 34000
#define PWM_Res 8
#define PWM_OFF_DUTY 0
#define Precharge_PWM_Res 8
#define PWM_Freq 20000 // 20 kHz frequency, beyond audible range
#define PWM_Precharge_Channel 0
#ifndef INVERTER_DISCONNECT_CONTACTOR_IS_NORMALLY_OPEN
#define ON 0 //Normally closed contactors use inverted logic
#define OFF 1 //Normally closed contactors use inverted logic
#else
#define ON 1
#define OFF 0
#endif
unsigned long prechargeStartTime = 0;
static uint32_t freq = Precharge_default_PWM_Freq;
uint16_t delta_freq = 1;
@ -28,48 +30,33 @@ void init_precharge_control() {
#ifdef DEBUG_LOG
logging.printf("Precharge control initialised\n");
#endif
pinMode(PRECHARGE_PIN, OUTPUT);
digitalWrite(PRECHARGE_PIN, LOW);
pinMode(POSITIVE_CONTACTOR_PIN, OUTPUT);
digitalWrite(POSITIVE_CONTACTOR_PIN, LOW);
pinMode(HIA4V1_PIN, OUTPUT);
digitalWrite(HIA4V1_PIN, LOW);
pinMode(INVERTER_DISCONNECT_CONTACTOR_PIN, OUTPUT);
digitalWrite(INVERTER_DISCONNECT_CONTACTOR_PIN, LOW);
}
// Main functions
void handle_precharge_control() {
unsigned long currentTime = millis();
#ifdef MEB_BATTERY
void handle_precharge_control(unsigned long currentMillis) {
int32_t target_voltage = datalayer.battery.status.voltage_dV;
int32_t external_voltage = datalayer_extended.meb.BMS_voltage_intermediate_dV;
#endif
// Handle actual state machine. This first turns on Negative, then Precharge, then Positive, and finally turns OFF precharge
switch (datalayer.system.status.precharge_status) {
case AUTO_PRECHARGE_IDLE:
#if 0
if (datalayer.battery.status.bms_status != FAULT && datalayer.battery.status.real_bms_status == BMS_STANDBY &&
/*datalayer.system.status.inverter_allows_contactor_closing &&*/
!datalayer.system.settings.equipment_stop_active) {
datalayer.system.status.precharge_status = AUTO_PRECHARGE_START;
}
#else
if (datalayer.system.settings.start_precharging) {
datalayer.system.status.precharge_status = AUTO_PRECHARGE_START;
}
#endif
break;
case AUTO_PRECHARGE_START:
freq = Precharge_default_PWM_Freq;
ledcAttachChannel(PRECHARGE_PIN, freq, PWM_Res, PWM_Precharge_Channel);
ledcWriteTone(PRECHARGE_PIN, freq); // Set frequency and set dutycycle to 50%
prechargeStartTime = currentTime;
ledcAttachChannel(HIA4V1_PIN, freq, Precharge_PWM_Res, PWM_Precharge_Channel);
ledcWriteTone(HIA4V1_PIN, freq); // Set frequency and set dutycycle to 50%
prechargeStartTime = currentMillis;
datalayer.system.status.precharge_status = AUTO_PRECHARGE_PRECHARGING;
#ifdef DEBUG_LOG
logging.printf("Precharge: Starting sequence\n");
#endif
digitalWrite(POSITIVE_CONTACTOR_PIN, HIGH);
digitalWrite(INVERTER_DISCONNECT_CONTACTOR_PIN, OFF);
break;
case AUTO_PRECHARGE_PRECHARGING:
@ -97,24 +84,24 @@ void handle_precharge_control() {
logging.printf("Precharge: Target: %d V Extern: %d V Frequency: %u\n", target_voltage / 10,
external_voltage / 10, freq);
#endif
ledcWriteTone(PRECHARGE_PIN, freq);
ledcWriteTone(HIA4V1_PIN, freq);
}
if ((datalayer.battery.status.real_bms_status != BMS_STANDBY &&
datalayer.battery.status.real_bms_status != BMS_ACTIVE) ||
datalayer.battery.status.bms_status != ACTIVE || datalayer.system.settings.equipment_stop_active) {
pinMode(PRECHARGE_PIN, OUTPUT);
digitalWrite(PRECHARGE_PIN, LOW);
digitalWrite(POSITIVE_CONTACTOR_PIN, LOW);
pinMode(HIA4V1_PIN, OUTPUT);
digitalWrite(HIA4V1_PIN, LOW);
digitalWrite(INVERTER_DISCONNECT_CONTACTOR_PIN, ON);
datalayer.system.status.precharge_status = AUTO_PRECHARGE_IDLE;
#ifdef DEBUG_LOG
logging.printf("Precharge: Disabling Precharge bms not standby/active or equipment stop\n");
#endif
} else if (currentTime - prechargeStartTime >= MAX_PRECHARGE_TIME_MS ||
} else if (currentMillis - prechargeStartTime >= MAX_PRECHARGE_TIME_MS ||
datalayer.battery.status.real_bms_status == BMS_FAULT) {
pinMode(PRECHARGE_PIN, OUTPUT);
digitalWrite(PRECHARGE_PIN, LOW);
digitalWrite(POSITIVE_CONTACTOR_PIN, LOW);
pinMode(HIA4V1_PIN, OUTPUT);
digitalWrite(HIA4V1_PIN, LOW);
digitalWrite(INVERTER_DISCONNECT_CONTACTOR_PIN, ON);
datalayer.system.status.precharge_status = AUTO_PRECHARGE_OFF;
#ifdef DEBUG_LOG
logging.printf("Precharge: Disabled (timeout reached / BMS fault) -> AUTO_PRECHARGE_OFF\n");
@ -123,9 +110,9 @@ void handle_precharge_control() {
// Add event
} else if (datalayer.system.status.battery_allows_contactor_closing) {
pinMode(PRECHARGE_PIN, OUTPUT);
digitalWrite(PRECHARGE_PIN, LOW);
digitalWrite(POSITIVE_CONTACTOR_PIN, LOW);
pinMode(HIA4V1_PIN, OUTPUT);
digitalWrite(HIA4V1_PIN, LOW);
digitalWrite(INVERTER_DISCONNECT_CONTACTOR_PIN, ON);
datalayer.system.status.precharge_status = AUTO_PRECHARGE_COMPLETED;
#ifdef DEBUG_LOG
logging.printf("Precharge: Disabled (contacts closed) -> COMPLETED\n");
@ -147,8 +134,8 @@ void handle_precharge_control() {
!datalayer.system.status.inverter_allows_contactor_closing ||
datalayer.system.settings.equipment_stop_active || datalayer.battery.status.bms_status != FAULT) {
datalayer.system.status.precharge_status = AUTO_PRECHARGE_IDLE;
pinMode(PRECHARGE_PIN, OUTPUT);
digitalWrite(PRECHARGE_PIN, LOW);
pinMode(HIA4V1_PIN, OUTPUT);
digitalWrite(HIA4V1_PIN, LOW);
#ifdef DEBUG_LOG
logging.printf("Precharge: equipment stop activated -> IDLE\n");
#endif
@ -159,4 +146,4 @@ void handle_precharge_control() {
break;
}
}
#endif // AUTO_PRECHARGE_CONTROL
#endif // PRECHARGE_CONTROL

View file

@ -17,19 +17,10 @@ void init_precharge_control();
/**
* @brief Handle contactors
*
* @param[in] void
* @param[in] unsigned long currentMillis
*
* @return void
*/
void handle_precharge_control();
/**
* @brief Handle contactors of battery 2
*
* @param[in] void
*
* @return void
*/
void handle_contactors_battery2();
void handle_precharge_control(unsigned long currentMillis);
#endif // _PRECHARGE_CONTROL_H_

View file

@ -294,6 +294,33 @@ typedef struct {
uint64_t cumulative_energy_in_regen = 0;
} DATALAYER_INFO_CMFAEV;
typedef struct {
/** uint8_t */
/** Battery software/hardware/serial versions, stores raw HEX values for ASCII chars */
uint8_t BatterySoftwareVersion[16] = {0};
uint8_t BatteryHardwareVersion[16] = {0};
uint8_t BatterySerialNumber[28] = {0};
/** int16_t */
/** Module temperatures 1-6 */
int16_t ModuleTemperatures[6] = {0};
/** uint16_t */
/** Various values polled via OBD2 PIDs */
uint16_t soc = 0;
uint16_t CC2voltage = 0;
uint16_t cellMaxVoltageNumber = 0;
uint16_t cellMinVoltageNumber = 0;
uint16_t cellTotalAmount = 0;
uint16_t specificialVoltage = 0;
uint16_t unknown1 = 0;
uint16_t rawSOCmax = 0;
uint16_t rawSOCmin = 0;
uint16_t unknown4 = 0;
uint16_t capModMax = 0;
uint16_t capModMin = 0;
uint16_t unknown7 = 0;
uint16_t unknown8 = 0;
} DATALAYER_INFO_GEELY_GEOMETRY_C;
typedef struct {
uint8_t total_cell_count = 0;
int16_t battery_12V = 0;
@ -769,6 +796,7 @@ class DataLayerExtended {
DATALAYER_INFO_BYDATTO3 bydAtto3;
DATALAYER_INFO_CELLPOWER cellpower;
DATALAYER_INFO_CMFAEV CMFAEV;
DATALAYER_INFO_GEELY_GEOMETRY_C geometryC;
DATALAYER_INFO_KIAHYUNDAI64 KiaHyundai64;
DATALAYER_INFO_KIAHYUNDAI64 KiaHyundai64_2;
DATALAYER_INFO_TESLA tesla;

View file

@ -61,6 +61,10 @@
// SMA CAN contactor pins
#define INVERTER_CONTACTOR_ENABLE_PIN 36
// Automatic precharging
#define HIA4V1_PIN 25
#define INVERTER_DISCONNECT_CONTACTOR_PIN 32
// SD card
//#define SD_MISO_PIN 2
//#define SD_MOSI_PIN 15

View file

@ -62,6 +62,10 @@ The pin layout below supports the following:
// Equipment stop pin
#define EQUIPMENT_STOP_PIN GPIO_NUM_12
// Automatic precharging
#define HIA4V1_PIN GPIO_NUM_17
#define INVERTER_DISCONNECT_CONTACTOR_PIN GPIO_NUM_5
// BMW_I3_BATTERY wake up pin
#ifdef BMW_I3_BATTERY
#define WUP_PIN1 GPIO_NUM_25 // Wake up pin for battery 1

View file

@ -53,6 +53,10 @@
#define PRECHARGE_PIN 25
#define BMS_POWER 18 // Note, this pin collides with CAN add-ons and Chademo
// Automatic precharging
#define HIA4V1_PIN 25
#define INVERTER_DISCONNECT_CONTACTOR_PIN 32
// SMA CAN contactor pins
#define INVERTER_CONTACTOR_ENABLE_PIN 5

View file

@ -51,6 +51,10 @@ GPIOs on extra header
#define PRECHARGE_PIN 25
#define BMS_POWER 23
// Automatic precharging
#define HIA4V1_PIN 19 //Available as extra GPIO via pin header
#define INVERTER_DISCONNECT_CONTACTOR_PIN 25
// SMA CAN contactor pins
#define INVERTER_CONTACTOR_ENABLE_PIN 2

View file

@ -70,6 +70,7 @@ SensorConfig sensorConfigTemplate[] = {
{"state_of_health", "State Of Health", "", "%", "battery"},
{"temperature_min", "Temperature Min", "", "°C", "temperature"},
{"temperature_max", "Temperature Max", "", "°C", "temperature"},
{"cpu_temp", "CPU Temperature", "", "°C", "temperature"},
{"stat_batt_power", "Stat Batt Power", "", "W", "power"},
{"battery_current", "Battery Current", "", "A", "current"},
{"cell_max_voltage", "Cell Max Voltage", "", "V", "voltage"},
@ -169,6 +170,7 @@ void set_battery_attributes(JsonDocument& doc, const DATALAYER_BATTERY_TYPE& bat
doc["state_of_health" + suffix] = ((float)battery.status.soh_pptt) / 100.0;
doc["temperature_min" + suffix] = ((float)((int16_t)battery.status.temperature_min_dC)) / 10.0;
doc["temperature_max" + suffix] = ((float)((int16_t)battery.status.temperature_max_dC)) / 10.0;
doc["cpu_temp" + suffix] = datalayer.system.info.CPU_temperature;
doc["stat_batt_power" + suffix] = ((float)((int32_t)battery.status.active_power_W));
doc["battery_current" + suffix] = ((float)((int16_t)battery.status.current_dA)) / 10.0;
doc["battery_voltage" + suffix] = ((float)battery.status.voltage_dV) / 10.0;

View file

@ -458,6 +458,50 @@ String advanced_battery_processor(const String& var) {
"<h4>Cumulative energy regen: " + String(datalayer_extended.CMFAEV.cumulative_energy_in_regen) + "Wh</h4>";
#endif //CMFA_EV_BATTERY
#ifdef GEELY_GEOMETRY_C_BATTERY
char readableSerialNumber[29]; // One extra space for null terminator
memcpy(readableSerialNumber, datalayer_extended.geometryC.BatterySerialNumber,
sizeof(datalayer_extended.geometryC.BatterySerialNumber));
readableSerialNumber[28] = '\0'; // Null terminate the string
char readableSoftwareVersion[17]; // One extra space for null terminator
memcpy(readableSoftwareVersion, datalayer_extended.geometryC.BatterySoftwareVersion,
sizeof(datalayer_extended.geometryC.BatterySoftwareVersion));
readableSoftwareVersion[16] = '\0'; // Null terminate the string
char readableHardwareVersion[17]; // One extra space for null terminator
memcpy(readableHardwareVersion, datalayer_extended.geometryC.BatteryHardwareVersion,
sizeof(datalayer_extended.geometryC.BatteryHardwareVersion));
readableHardwareVersion[16] = '\0'; // Null terminate the string
content += "<h4>Serial number: " + String(readableSoftwareVersion) + "</h4>";
content += "<h4>Software version: " + String(readableSerialNumber) + "</h4>";
content += "<h4>Hardware version: " + String(readableHardwareVersion) + "</h4>";
content += "<h4>SOC display: " + String(datalayer_extended.geometryC.soc) + "ppt</h4>";
content += "<h4>CC2 voltage: " + String(datalayer_extended.geometryC.CC2voltage) + "mV</h4>";
content += "<h4>Cell max voltage number: " + String(datalayer_extended.geometryC.cellMaxVoltageNumber) + "</h4>";
content += "<h4>Cell min voltage number: " + String(datalayer_extended.geometryC.cellMinVoltageNumber) + "</h4>";
content += "<h4>Cell total amount: " + String(datalayer_extended.geometryC.cellTotalAmount) + "S</h4>";
content += "<h4>Specificial Voltage: " + String(datalayer_extended.geometryC.specificialVoltage) + "dV</h4>";
content += "<h4>Unknown1: " + String(datalayer_extended.geometryC.unknown1) + "</h4>";
content += "<h4>Raw SOC max: " + String(datalayer_extended.geometryC.rawSOCmax) + "</h4>";
content += "<h4>Raw SOC min: " + String(datalayer_extended.geometryC.rawSOCmin) + "</h4>";
content += "<h4>Unknown4: " + String(datalayer_extended.geometryC.unknown4) + "</h4>";
content += "<h4>Capacity module max: " + String((datalayer_extended.geometryC.capModMax / 10)) + "Ah</h4>";
content += "<h4>Capacity module min: " + String((datalayer_extended.geometryC.capModMin / 10)) + "Ah</h4>";
content += "<h4>Unknown7: " + String(datalayer_extended.geometryC.unknown7) + "</h4>";
content += "<h4>Unknown8: " + String(datalayer_extended.geometryC.unknown8) + "</h4>";
content +=
"<h4>Module 1 temperature: " + String(datalayer_extended.geometryC.ModuleTemperatures[0]) + " &deg;C</h4>";
content +=
"<h4>Module 2 temperature: " + String(datalayer_extended.geometryC.ModuleTemperatures[1]) + " &deg;C</h4>";
content +=
"<h4>Module 3 temperature: " + String(datalayer_extended.geometryC.ModuleTemperatures[2]) + " &deg;C</h4>";
content +=
"<h4>Module 4 temperature: " + String(datalayer_extended.geometryC.ModuleTemperatures[3]) + " &deg;C</h4>";
content +=
"<h4>Module 5 temperature: " + String(datalayer_extended.geometryC.ModuleTemperatures[4]) + " &deg;C</h4>";
content +=
"<h4>Module 6 temperature: " + String(datalayer_extended.geometryC.ModuleTemperatures[5]) + " &deg;C</h4>";
#endif //GEELY_GEOMETRY_C_BATTERY
#ifdef KIA_HYUNDAI_64_BATTERY
auto print_hyundai = [&content](DATALAYER_INFO_KIAHYUNDAI64& data) {
content += "<h4>Cells: " + String(data.total_cell_count) + "S</h4>";
@ -1480,7 +1524,8 @@ String advanced_battery_processor(const String& var) {
!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(VOLVO_SPA_HYBRID_BATTERY) && \
!defined(KIA_HYUNDAI_64_BATTERY) && !defined(CMFA_EV_BATTERY) //Only the listed types have extra info
!defined(KIA_HYUNDAI_64_BATTERY) && !defined(GEELY_GEOMETRY_C_BATTERY) && \
!defined(CMFA_EV_BATTERY) //Only the listed types have extra info
content += "No extra information available for this battery type";
#endif

View file

@ -39,13 +39,10 @@ String settings_processor(const String& var) {
String(getCANInterfaceName(can_config.battery_double)) + "</span></h4>";
#endif // DOUBLE_BATTERY
#ifdef CAN_INVERTER_SELECTED
content += "<h4 style='color: white;'>Inverter interface: <span id='Inverter'>" +
String(getCANInterfaceName(can_config.inverter)) + "</span></h4>";
#endif //CAN_INVERTER_SELECTED
#ifdef MODBUS_INVERTER_SELECTED
content += "<h4 style='color: white;'>Inverter interface: RS485<span id='Inverter'></span></h4>";
#endif
if (inverter) {
content += "<h4 style='color: white;'>Inverter interface: <span id='Inverter'>" +
String(inverter->interface_name()) + "</span></h4>";
}
#ifdef CAN_SHUNT_SELECTED
content += "<h4 style='color: white;'>Shunt Interface: <span id='Shunt'>" +

View file

@ -73,7 +73,7 @@ void wifi_monitor() {
#endif
// Try WiFi.reconnect() if it was successfully connected at least once
if (hasConnectedBefore) {
lastReconnectAttempt = millis(); // Reset reconnection attempt timer
lastReconnectAttempt = currentMillis; // Reset reconnection attempt timer
#ifdef DEBUG_LOG
logging.println("Wi-Fi reconnect attempt...");
#endif

View file

@ -1,8 +1,7 @@
#include "../include.h"
#ifdef AFORE_CAN
#include "AFORE-CAN.h"
#include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h"
#include "AFORE-CAN.h"
#include "../include.h"
#define SOCMAX 100
#define SOCMIN 1
@ -175,4 +174,3 @@ void AforeCanInverter::setup(void) { // Performs one time setup at startup over
strncpy(datalayer.system.info.inverter_protocol, "Afore battery over CAN", 63);
datalayer.system.info.inverter_protocol[63] = '\0';
}
#endif

View file

@ -2,8 +2,10 @@
#define AFORE_CAN_H
#include "../include.h"
#ifdef AFORE_CAN
#define CAN_INVERTER_SELECTED
#define SELECTED_INVERTER_CLASS AforeCanInverter
#endif
#include "CanInverterProtocol.h"

View file

@ -1,8 +1,7 @@
#include "../include.h"
#ifdef BYD_CAN
#include "BYD-CAN.h"
#include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h"
#include "BYD-CAN.h"
#include "../include.h"
/* Do not change code below unless you are sure what you are doing */
@ -175,4 +174,3 @@ void BydCanInverter::setup(void) { // Performs one time setup at startup over C
strncpy(datalayer.system.info.inverter_protocol, "BYD Battery-Box Premium HVS over CAN Bus", 63);
datalayer.system.info.inverter_protocol[63] = '\0';
}
#endif

View file

@ -2,11 +2,10 @@
#define BYD_CAN_H
#include "../include.h"
#ifdef BYD_CAN
#define CAN_INVERTER_SELECTED
#define SELECTED_INVERTER_CLASS BydCanInverter
#define FW_MAJOR_VERSION 0x03
#define FW_MINOR_VERSION 0x29
#endif
#include "CanInverterProtocol.h"
@ -23,7 +22,9 @@ class BydCanInverter : public CanInverterProtocol {
unsigned long previousMillis10s = 0; // will store last time a 10s CAN Message was send
unsigned long previousMillis60s = 0; // will store last time a 60s CAN Message was send
#define VOLTAGE_OFFSET_DV 20
static const int FW_MAJOR_VERSION = 0x03;
static const int FW_MINOR_VERSION = 0x29;
static const int VOLTAGE_OFFSET_DV = 20;
CAN_frame BYD_250 = {.FD = false,
.ext_ID = false,

View file

@ -1,9 +1,8 @@
#include "../include.h"
#ifdef BYD_MODBUS
#include "BYD-MODBUS.h"
#include "../datalayer/datalayer.h"
#include "../devboard/utils/events.h"
#include "../include.h"
#include "../lib/eModbus-eModbus/scripts/mbServerFCs.h"
#include "BYD-MODBUS.h"
// For modbus register definitions, see https://gitlab.com/pelle8/inverter_resources/-/blob/main/byd_registers_modbus_rtu.md
@ -166,5 +165,3 @@ void BydModbusInverter::setup(void) { // Performs one time setup at startup ove
// Start ModbusRTU background task
MBserver.begin(Serial2, MODBUS_CORE);
}
#endif

View file

@ -2,8 +2,10 @@
#define BYD_MODBUS_H
#include "../include.h"
#ifdef BYD_MODBUS
#define MODBUS_INVERTER_SELECTED
#define SELECTED_INVERTER_CLASS BydModbusInverter
#endif
#include "ModbusInverterProtocol.h"

View file

@ -7,6 +7,7 @@
class CanInverterProtocol : public InverterProtocol {
public:
virtual const char* interface_name() { return getCANInterfaceName(can_config.inverter); }
virtual void transmit_can(unsigned long currentMillis) = 0;
virtual void map_can_frame_to_variable(CAN_frame rx_frame) = 0;
};

View file

@ -1,8 +1,7 @@
#include "../include.h"
#ifdef FERROAMP_CAN
#include "FERROAMP-CAN.h"
#include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h"
#include "FERROAMP-CAN.h"
#include "../include.h"
void FerroampCanInverter::
update_values() { //This function maps all the values fetched from battery CAN to the correct CAN messages
@ -365,4 +364,3 @@ void FerroampCanInverter::setup(void) { // Performs one time setup at startup o
strncpy(datalayer.system.info.inverter_protocol, "Ferroamp Pylon battery over CAN bus", 63);
datalayer.system.info.inverter_protocol[63] = '\0';
}
#endif

View file

@ -4,17 +4,16 @@
#include "CanInverterProtocol.h"
#ifdef FERROAMP_CAN
#define CAN_INVERTER_SELECTED
#define SELECTED_INVERTER_CLASS FerroampCanInverter
#endif
class FerroampCanInverter : public CanInverterProtocol {
public:
void setup();
void update_values();
void transmit_can(unsigned long currentMillis);
void map_can_frame_to_variable(CAN_frame rx_frame);
private:
@ -27,13 +26,13 @@ class FerroampCanInverter : public CanInverterProtocol {
//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.
/* 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
static const int TOTAL_CELL_AMOUNT = 120; //Adjust this parameter in steps of 120 to add another 14,2kWh of capacity
static const int MODULES_IN_SERIES = 4;
static const int CELLS_PER_MODULE = 30;
static const int VOLTAGE_LEVEL = 384;
static const int AH_CAPACITY = 37;
//Actual content messages
CAN_frame PYLON_7310 = {.FD = false,

View file

@ -1,8 +1,8 @@
#include "../include.h"
#ifdef FOXESS_CAN
#include "FOXESS-CAN.h"
#include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h"
#include "../devboard/utils/events.h"
#include "FOXESS-CAN.h"
#include "../include.h"
/* Based on info from this excellent repo: https://github.com/FozzieUK/FoxESS-Canbus-Protocol */
/* The FoxESS protocol emulates the stackable (1-8) 48V towers found in the HV2600 / ECS4100 batteries
@ -22,352 +22,9 @@ below that you can customize, incase you use a lower voltage battery with this p
#define TOTAL_LIFETIME_WH_ACCUMULATED 0 //We dont have this value in the emulator
/* Do not change code below unless you are sure what you are doing */
static int16_t temperature_average = 0;
static uint16_t voltage_per_pack = 0;
static int16_t current_per_pack = 0;
static uint8_t temperature_max_per_pack = 0;
static uint8_t temperature_min_per_pack = 0;
static uint8_t current_pack_info = 0;
// Batch send of CAN message variables
const uint8_t delay_between_batches_ms = 10; //TODO, tweak to as low as possible before performance issues appear
static bool send_bms_info = false;
static bool send_individual_pack_status = false;
static bool send_serial_numbers = false;
static bool send_cellvoltages = false;
static unsigned long currentMillis = 0;
static unsigned long previousMillisCellvoltage = 0;
static unsigned long previousMillisSerialNumber = 0;
static unsigned long previousMillisBMSinfo = 0;
static unsigned long previousMillisIndividualPacks = 0;
static uint8_t can_message_cellvolt_index = 0;
static uint8_t can_message_serial_index = 0;
static uint8_t can_message_individualpack_index = 0;
static uint8_t can_message_bms_index = 0;
//CAN message translations from this amazing repository: https://github.com/rand12345/FOXESS_can_bus
CAN_frame FOXESS_1872 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x1872,
.data = {0x40, 0x12, 0x80, 0x0C, 0xCD, 0x00, 0xF4, 0x01}}; //BMS_Limits
CAN_frame FOXESS_1873 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x1873,
.data = {0xA3, 0x10, 0x0D, 0x00, 0x5D, 0x00, 0x77, 0x07}}; //BMS_PackData
CAN_frame FOXESS_1874 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x1874,
.data = {0xA3, 0x10, 0x0D, 0x00, 0x5D, 0x00, 0x77, 0x07}}; //BMS_CellData
CAN_frame FOXESS_1875 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x1875,
.data = {0xF9, 0x00, 0xFF, 0x08, 0x01, 0x00, 0x8E, 0x00}}; //BMS_Status
CAN_frame FOXESS_1876 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x1876,
.data = {0x01, 0x00, 0x07, 0x0D, 0x0, 0x0, 0xFE, 0x0C}}; //BMS_PackTemps
CAN_frame FOXESS_1877 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x1877,
.data = {0x00, 0x00, 0x00, 0x00, 0x82, 0x00, 0x20, 0x50}}; //BMS_Unk1
CAN_frame FOXESS_1878 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x1878,
.data = {0x07, 0x0A, 0x00, 0x00, 0xD0, 0xFF, 0x4E, 0x00}}; //BMS_PackStats
CAN_frame FOXESS_1879 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x1879,
.data = {0x00, 0x35, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; //BMS_Unk2
CAN_frame FOXESS_1881 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x1881,
.data = {0x10, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}};
CAN_frame FOXESS_1882 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x1882,
.data = {0x10, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}};
CAN_frame FOXESS_1883 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x1883,
.data = {0x10, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}};
CAN_frame FOXESS_0C05 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C05,
.data = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xD0, 0xD0}};
CAN_frame FOXESS_0C06 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C06,
.data = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xD0, 0xD0}};
CAN_frame FOXESS_0C07 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C07,
.data = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xD0, 0xD0}};
CAN_frame FOXESS_0C08 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C08,
.data = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xD0, 0xD0}};
CAN_frame FOXESS_0C09 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C09,
.data = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xD0, 0xD0}};
CAN_frame FOXESS_0C0A = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C0A,
.data = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xD0, 0xD0}};
CAN_frame FOXESS_0C0B = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C0B,
.data = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xD0, 0xD0}};
CAN_frame FOXESS_0C0C = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C0C,
.data = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xD0, 0xD0}};
// Cellvoltages
CAN_frame FOXESS_0C1D = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C1D, //Cell 1-4
.data = {0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C}}; //All cells init to 3300mV
CAN_frame FOXESS_0C21 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C21, //Cell 5-8
.data = {0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C}}; //All cells init to 3300mV
CAN_frame FOXESS_0C25 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C25, //Cell 9-12
.data = {0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C}}; //All cells init to 3300mV
CAN_frame FOXESS_0C29 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C29, //Cell 13-16
.data = {0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C}}; //All cells init to 3300mV
CAN_frame FOXESS_0C2D = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C2D, //Cell 17-20
.data = {0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C}}; //All cells init to 3300mV
CAN_frame FOXESS_0C31 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C31, //Cell 21-24
.data = {0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C}}; //All cells init to 3300mV
CAN_frame FOXESS_0C35 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C35, //Cell 25-28
.data = {0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C}}; //All cells init to 3300mV
CAN_frame FOXESS_0C39 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C39, //Cell 29-32
.data = {0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C}}; //All cells init to 3300mV
CAN_frame FOXESS_0C3D = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C3D, //Cell 33-36
.data = {0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C}}; //All cells init to 3300mV
CAN_frame FOXESS_0C41 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C41, //Cell 37-40
.data = {0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C}}; //All cells init to 3300mV
CAN_frame FOXESS_0C45 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C45, //Cell 41-44
.data = {0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C}}; //All cells init to 3300mV
CAN_frame FOXESS_0C49 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C49, //Cell 45-48
.data = {0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C}}; //All cells init to 3300mV
CAN_frame FOXESS_0C4D = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C4D, //Cell 49-52
.data = {0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C}}; //All cells init to 3300mV
CAN_frame FOXESS_0C51 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C51, //Cell 53-56
.data = {0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C}}; //All cells init to 3300mV
CAN_frame FOXESS_0C55 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C55, //Cell 57-60
.data = {0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C}}; //All cells init to 3300mV
CAN_frame FOXESS_0C59 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C59, //Cell 61-64
.data = {0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C}}; //All cells init to 3300mV
CAN_frame FOXESS_0C5D = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C5D, //Cell 65-68
.data = {0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C}}; //All cells init to 3300mV
CAN_frame FOXESS_0C61 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C61, //Cell 69-72
.data = {0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C}}; //All cells init to 3300mV
CAN_frame FOXESS_0C65 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C65, //Cell 73-76
.data = {0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C}}; //All cells init to 3300mV
CAN_frame FOXESS_0C69 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C69, //Cell 77-80
.data = {0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C}}; //All cells init to 3300mV
CAN_frame FOXESS_0C6D = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C6D, //Cell 81-84
.data = {0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C}}; //All cells init to 3300mV
CAN_frame FOXESS_0C71 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C71, //Cell 85-88
.data = {0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C}}; //All cells init to 3300mV
CAN_frame FOXESS_0C75 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C75, //Cell 89-92
.data = {0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C}}; //All cells init to 3300mV
CAN_frame FOXESS_0C79 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C79, //Cell 93-96
.data = {0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C}}; //All cells init to 3300mV
CAN_frame FOXESS_0C7D = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C7D, //Cell 97-100
.data = {0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C}}; //All cells init to 3300mV
CAN_frame FOXESS_0C81 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C81, //Cell 101-104
.data = {0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C}}; //All cells init to 3300mV
CAN_frame FOXESS_0C85 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C85, //Cell 105-108
.data = {0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C}}; //All cells init to 3300mV
CAN_frame FOXESS_0C89 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C89, //Cell 109-112
.data = {0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C}}; //All cells init to 3300mV
CAN_frame FOXESS_0C8D = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C8D, //Cell 113-116
.data = {0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C}}; //All cells init to 3300mV
CAN_frame FOXESS_0C91 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C91, //Cell 117-120
.data = {0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C}}; //All cells init to 3300mV
CAN_frame FOXESS_0C95 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C95, //Cell 121-124
.data = {0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C}}; //All cells init to 3300mV
CAN_frame FOXESS_0C99 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C99, //Cell 125-128
.data = {0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C}}; //All cells init to 3300mV
CAN_frame FOXESS_0C9D = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C9D, //Cell 129-132
.data = {0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C}}; //All cells init to 3300mV
CAN_frame FOXESS_0CA1 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0CA1, //Cell 133-136
.data = {0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C}}; //All cells init to 3300mV
CAN_frame FOXESS_0CA5 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0CA5, //Cell 137-140
.data = {0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C}}; //All cells init to 3300mV
CAN_frame FOXESS_0CA9 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0CA9, //Cell 141-144
.data = {0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C}}; //All cells init to 3300mV
// Temperatures
CAN_frame FOXESS_0D21 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0D21, //Celltemperatures Pack 1
.data = {0x49, 0x48, 0x47, 0x47, 0x48, 0x49, 0x46, 0x47}};
CAN_frame FOXESS_0D29 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0D29, //Celltemperatures Pack 2
.data = {0x49, 0x48, 0x47, 0x47, 0x48, 0x49, 0x46, 0x47}};
CAN_frame FOXESS_0D31 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0D31, //Celltemperatures Pack 3
.data = {0x49, 0x48, 0x47, 0x47, 0x48, 0x49, 0x46, 0x47}};
CAN_frame FOXESS_0D39 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0D39, //Celltemperatures Pack 4
.data = {0x49, 0x48, 0x47, 0x47, 0x48, 0x49, 0x46, 0x47}};
CAN_frame FOXESS_0D41 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0D41, //Celltemperatures Pack 5
.data = {0x49, 0x48, 0x47, 0x47, 0x48, 0x49, 0x46, 0x47}};
CAN_frame FOXESS_0D49 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0D49, //Celltemperatures Pack 6
.data = {0x49, 0x48, 0x47, 0x47, 0x48, 0x49, 0x46, 0x47}};
CAN_frame FOXESS_0D51 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0D51, //Celltemperatures Pack 7
.data = {0x49, 0x48, 0x47, 0x47, 0x48, 0x49, 0x46, 0x47}};
CAN_frame FOXESS_0D59 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0D59, //Celltemperatures Pack 8
.data = {0x49, 0x48, 0x47, 0x47, 0x48, 0x49, 0x46, 0x47}};
void update_values_can_inverter() { //This function maps all the CAN values fetched from battery. It also checks some safeties.
void FoxessCanInverter::
update_values() { //This function maps all the CAN values fetched from battery. It also checks some safeties.
//Calculate the required values
temperature_average =
@ -595,7 +252,7 @@ void update_values_can_inverter() { //This function maps all the CAN values fet
// So do we really need to map the same two values over and over to 32 places?
}
void transmit_can_inverter(unsigned long currentMillis) {
void FoxessCanInverter::transmit_can(unsigned long currentMillis) {
if (send_bms_info) {
@ -861,7 +518,7 @@ void transmit_can_inverter(unsigned long currentMillis) {
}
}
void map_can_frame_to_variable_inverter(CAN_frame rx_frame) {
void FoxessCanInverter::map_can_frame_to_variable(CAN_frame rx_frame) {
if (rx_frame.ID == 0x1871) {
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
@ -904,8 +561,7 @@ void map_can_frame_to_variable_inverter(CAN_frame rx_frame) {
}
}
}
void setup_inverter(void) { // Performs one time setup at startup over CAN bus
void FoxessCanInverter::setup(void) { // Performs one time setup at startup over CAN bus
strncpy(datalayer.system.info.inverter_protocol, "FoxESS compatible HV2600/ECS4100 battery", 63);
datalayer.system.info.inverter_protocol[63] = '\0';
}
#endif

View file

@ -2,9 +2,365 @@
#define FOXESS_CAN_H
#include "../include.h"
#define CAN_INVERTER_SELECTED
#include "CanInverterProtocol.h"
void transmit_can_frame(CAN_frame* tx_frame, int interface);
void setup_inverter(void);
#ifdef FOXESS_CAN
#define CAN_INVERTER_SELECTED
#define SELECTED_INVERTER_CLASS FoxessCanInverter
#endif
class FoxessCanInverter : public CanInverterProtocol {
public:
void setup();
void update_values();
void transmit_can(unsigned long currentMillis);
void map_can_frame_to_variable(CAN_frame rx_frame);
private:
int16_t temperature_average = 0;
uint16_t voltage_per_pack = 0;
int16_t current_per_pack = 0;
uint8_t temperature_max_per_pack = 0;
uint8_t temperature_min_per_pack = 0;
uint8_t current_pack_info = 0;
// Batch send of CAN message variables
const uint8_t delay_between_batches_ms = 10; //TODO, tweak to as low as possible before performance issues appear
bool send_bms_info = false;
bool send_individual_pack_status = false;
bool send_serial_numbers = false;
bool send_cellvoltages = false;
unsigned long currentMillis = 0;
unsigned long previousMillisCellvoltage = 0;
unsigned long previousMillisSerialNumber = 0;
unsigned long previousMillisBMSinfo = 0;
unsigned long previousMillisIndividualPacks = 0;
uint8_t can_message_cellvolt_index = 0;
uint8_t can_message_serial_index = 0;
uint8_t can_message_individualpack_index = 0;
uint8_t can_message_bms_index = 0;
//CAN message translations from this amazing repository: https://github.com/rand12345/FOXESS_can_bus
CAN_frame FOXESS_1872 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x1872,
.data = {0x40, 0x12, 0x80, 0x0C, 0xCD, 0x00, 0xF4, 0x01}}; //BMS_Limits
CAN_frame FOXESS_1873 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x1873,
.data = {0xA3, 0x10, 0x0D, 0x00, 0x5D, 0x00, 0x77, 0x07}}; //BMS_PackData
CAN_frame FOXESS_1874 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x1874,
.data = {0xA3, 0x10, 0x0D, 0x00, 0x5D, 0x00, 0x77, 0x07}}; //BMS_CellData
CAN_frame FOXESS_1875 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x1875,
.data = {0xF9, 0x00, 0xFF, 0x08, 0x01, 0x00, 0x8E, 0x00}}; //BMS_Status
CAN_frame FOXESS_1876 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x1876,
.data = {0x01, 0x00, 0x07, 0x0D, 0x0, 0x0, 0xFE, 0x0C}}; //BMS_PackTemps
CAN_frame FOXESS_1877 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x1877,
.data = {0x00, 0x00, 0x00, 0x00, 0x82, 0x00, 0x20, 0x50}}; //BMS_Unk1
CAN_frame FOXESS_1878 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x1878,
.data = {0x07, 0x0A, 0x00, 0x00, 0xD0, 0xFF, 0x4E, 0x00}}; //BMS_PackStats
CAN_frame FOXESS_1879 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x1879,
.data = {0x00, 0x35, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; //BMS_Unk2
CAN_frame FOXESS_1881 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x1881,
.data = {0x10, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}};
CAN_frame FOXESS_1882 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x1882,
.data = {0x10, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}};
CAN_frame FOXESS_1883 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x1883,
.data = {0x10, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}};
CAN_frame FOXESS_0C05 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C05,
.data = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xD0, 0xD0}};
CAN_frame FOXESS_0C06 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C06,
.data = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xD0, 0xD0}};
CAN_frame FOXESS_0C07 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C07,
.data = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xD0, 0xD0}};
CAN_frame FOXESS_0C08 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C08,
.data = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xD0, 0xD0}};
CAN_frame FOXESS_0C09 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C09,
.data = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xD0, 0xD0}};
CAN_frame FOXESS_0C0A = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C0A,
.data = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xD0, 0xD0}};
CAN_frame FOXESS_0C0B = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C0B,
.data = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xD0, 0xD0}};
CAN_frame FOXESS_0C0C = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C0C,
.data = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xD0, 0xD0}};
// Cellvoltages
CAN_frame FOXESS_0C1D = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C1D, //Cell 1-4
.data = {0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C}}; //All cells init to 3300mV
CAN_frame FOXESS_0C21 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C21, //Cell 5-8
.data = {0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C}}; //All cells init to 3300mV
CAN_frame FOXESS_0C25 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C25, //Cell 9-12
.data = {0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C}}; //All cells init to 3300mV
CAN_frame FOXESS_0C29 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C29, //Cell 13-16
.data = {0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C}}; //All cells init to 3300mV
CAN_frame FOXESS_0C2D = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C2D, //Cell 17-20
.data = {0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C}}; //All cells init to 3300mV
CAN_frame FOXESS_0C31 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C31, //Cell 21-24
.data = {0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C}}; //All cells init to 3300mV
CAN_frame FOXESS_0C35 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C35, //Cell 25-28
.data = {0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C}}; //All cells init to 3300mV
CAN_frame FOXESS_0C39 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C39, //Cell 29-32
.data = {0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C}}; //All cells init to 3300mV
CAN_frame FOXESS_0C3D = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C3D, //Cell 33-36
.data = {0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C}}; //All cells init to 3300mV
CAN_frame FOXESS_0C41 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C41, //Cell 37-40
.data = {0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C}}; //All cells init to 3300mV
CAN_frame FOXESS_0C45 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C45, //Cell 41-44
.data = {0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C}}; //All cells init to 3300mV
CAN_frame FOXESS_0C49 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C49, //Cell 45-48
.data = {0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C}}; //All cells init to 3300mV
CAN_frame FOXESS_0C4D = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C4D, //Cell 49-52
.data = {0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C}}; //All cells init to 3300mV
CAN_frame FOXESS_0C51 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C51, //Cell 53-56
.data = {0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C}}; //All cells init to 3300mV
CAN_frame FOXESS_0C55 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C55, //Cell 57-60
.data = {0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C}}; //All cells init to 3300mV
CAN_frame FOXESS_0C59 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C59, //Cell 61-64
.data = {0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C}}; //All cells init to 3300mV
CAN_frame FOXESS_0C5D = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C5D, //Cell 65-68
.data = {0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C}}; //All cells init to 3300mV
CAN_frame FOXESS_0C61 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C61, //Cell 69-72
.data = {0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C}}; //All cells init to 3300mV
CAN_frame FOXESS_0C65 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C65, //Cell 73-76
.data = {0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C}}; //All cells init to 3300mV
CAN_frame FOXESS_0C69 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C69, //Cell 77-80
.data = {0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C}}; //All cells init to 3300mV
CAN_frame FOXESS_0C6D = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C6D, //Cell 81-84
.data = {0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C}}; //All cells init to 3300mV
CAN_frame FOXESS_0C71 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C71, //Cell 85-88
.data = {0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C}}; //All cells init to 3300mV
CAN_frame FOXESS_0C75 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C75, //Cell 89-92
.data = {0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C}}; //All cells init to 3300mV
CAN_frame FOXESS_0C79 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C79, //Cell 93-96
.data = {0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C}}; //All cells init to 3300mV
CAN_frame FOXESS_0C7D = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C7D, //Cell 97-100
.data = {0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C}}; //All cells init to 3300mV
CAN_frame FOXESS_0C81 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C81, //Cell 101-104
.data = {0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C}}; //All cells init to 3300mV
CAN_frame FOXESS_0C85 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C85, //Cell 105-108
.data = {0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C}}; //All cells init to 3300mV
CAN_frame FOXESS_0C89 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C89, //Cell 109-112
.data = {0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C}}; //All cells init to 3300mV
CAN_frame FOXESS_0C8D = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C8D, //Cell 113-116
.data = {0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C}}; //All cells init to 3300mV
CAN_frame FOXESS_0C91 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C91, //Cell 117-120
.data = {0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C}}; //All cells init to 3300mV
CAN_frame FOXESS_0C95 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C95, //Cell 121-124
.data = {0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C}}; //All cells init to 3300mV
CAN_frame FOXESS_0C99 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C99, //Cell 125-128
.data = {0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C}}; //All cells init to 3300mV
CAN_frame FOXESS_0C9D = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0C9D, //Cell 129-132
.data = {0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C}}; //All cells init to 3300mV
CAN_frame FOXESS_0CA1 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0CA1, //Cell 133-136
.data = {0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C}}; //All cells init to 3300mV
CAN_frame FOXESS_0CA5 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0CA5, //Cell 137-140
.data = {0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C}}; //All cells init to 3300mV
CAN_frame FOXESS_0CA9 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0CA9, //Cell 141-144
.data = {0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C, 0xE4, 0x0C}}; //All cells init to 3300mV
// Temperatures
CAN_frame FOXESS_0D21 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0D21, //Celltemperatures Pack 1
.data = {0x49, 0x48, 0x47, 0x47, 0x48, 0x49, 0x46, 0x47}};
CAN_frame FOXESS_0D29 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0D29, //Celltemperatures Pack 2
.data = {0x49, 0x48, 0x47, 0x47, 0x48, 0x49, 0x46, 0x47}};
CAN_frame FOXESS_0D31 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0D31, //Celltemperatures Pack 3
.data = {0x49, 0x48, 0x47, 0x47, 0x48, 0x49, 0x46, 0x47}};
CAN_frame FOXESS_0D39 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0D39, //Celltemperatures Pack 4
.data = {0x49, 0x48, 0x47, 0x47, 0x48, 0x49, 0x46, 0x47}};
CAN_frame FOXESS_0D41 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0D41, //Celltemperatures Pack 5
.data = {0x49, 0x48, 0x47, 0x47, 0x48, 0x49, 0x46, 0x47}};
CAN_frame FOXESS_0D49 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0D49, //Celltemperatures Pack 6
.data = {0x49, 0x48, 0x47, 0x47, 0x48, 0x49, 0x46, 0x47}};
CAN_frame FOXESS_0D51 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0D51, //Celltemperatures Pack 7
.data = {0x49, 0x48, 0x47, 0x47, 0x48, 0x49, 0x46, 0x47}};
CAN_frame FOXESS_0D59 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x0D59, //Celltemperatures Pack 8
.data = {0x49, 0x48, 0x47, 0x47, 0x48, 0x49, 0x46, 0x47}};
};
#endif

View file

@ -1,7 +1,7 @@
#include "../include.h"
#ifdef GROWATT_HV_CAN
#include "../datalayer/datalayer.h"
#include "GROWATT-HV-CAN.h"
#include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h"
#include "../include.h"
/* TODO:
This protocol has not been tested with any inverter. Proceed with extreme caution.
@ -21,143 +21,8 @@ FCC - Full charge capacity
RM - Remaining capacity
BMS - Battery Information Collector*/
//Total number of Cells (1-512)
//(Total number of Cells = number of Packs in parallel * number of Modules in series * number of Cells in the module)
#define TOTAL_NUMBER_OF_CELLS 300
// Number of Modules in series (1-32)
#define NUMBER_OF_MODULES_IN_SERIES 20
// Number of packs in parallel (1-65536)
#define NUMBER_OF_PACKS_IN_PARALLEL 1
//Manufacturer abbreviation, part 1
#define MANUFACTURER_ASCII_0 0x47 //G
#define MANUFACTURER_ASCII_1 0x54 //T
/* Do not change code below unless you are sure what you are doing */
//Actual content messages
CAN_frame GROWATT_3110 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x3110,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame GROWATT_3120 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x3120,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame GROWATT_3130 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x3130,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame GROWATT_3140 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x3140,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame GROWATT_3150 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x3150,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame GROWATT_3160 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x3160,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame GROWATT_3170 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x3170,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame GROWATT_3180 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x3180,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame GROWATT_3190 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x3190,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame GROWATT_3200 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x3200,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame GROWATT_3210 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x3210,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame GROWATT_3220 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x3220,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame GROWATT_3230 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x3230,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame GROWATT_3240 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x3240,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame GROWATT_3250 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x3250,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame GROWATT_3260 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x3260,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame GROWATT_3270 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x3270,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame GROWATT_3280 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x3280,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame GROWATT_3290 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x3290,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame GROWATT_3F00 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x3F00,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
static unsigned long previousMillis1s = 0; // will store last time a 1s CAN Message was send
static unsigned long previousMillisBatchSend = 0;
static uint32_t unix_time = 0;
static uint16_t ampere_hours_remaining = 0;
static uint16_t ampere_hours_full = 0;
static uint16_t send_times = 0; // Overflows every 18hours. Cumulative number, plus 1 for each transmission
static uint8_t safety_specification = 0;
static uint8_t charging_command = 0;
static uint8_t discharging_command = 0;
static uint8_t shielding_external_communication_failure = 0;
static uint8_t clearing_battery_fault =
0; //When PCS receives the forced charge Mark 1 and Cell under- voltage protection fault, it will send 0XAA
static uint8_t ISO_detection_command = 0;
static uint8_t sleep_wakeup_control = 0;
static uint8_t PCS_working_status = 0; //00 standby, 01 operating
static uint8_t serial_number_counter = 0; //0-1-2-0-1-2...
static uint8_t can_message_batch_index = 0;
static const uint8_t delay_between_batches_ms = 10;
static bool inverter_alive = false;
static bool time_to_send_1s_data = false;
void update_values_can_inverter() { //This function maps all the values fetched from battery CAN to the correct CAN messages
void GrowattHvInverter::
update_values() { //This function maps all the values fetched from battery CAN to the correct CAN messages
if (datalayer.battery.status.voltage_dV > 10) { // Only update value when we have voltage available to avoid div0
ampere_hours_remaining =
@ -493,7 +358,7 @@ void update_values_can_inverter() { //This function maps all the values fetched
GROWATT_3F00.data.u8[7] = 0; // RESERVED
}
void map_can_frame_to_variable_inverter(CAN_frame rx_frame) {
void GrowattHvInverter::map_can_frame_to_variable(CAN_frame rx_frame) {
switch (rx_frame.ID) {
case 0x3010: // Heartbeat command, 1000ms
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
@ -523,7 +388,7 @@ void map_can_frame_to_variable_inverter(CAN_frame rx_frame) {
}
}
void transmit_can_inverter(unsigned long currentMillis) {
void GrowattHvInverter::transmit_can(unsigned long currentMillis) {
if (!inverter_alive) {
return; //Dont send messages towards inverter until it has started
@ -585,8 +450,7 @@ void transmit_can_inverter(unsigned long currentMillis) {
}
}
void setup_inverter(void) { // Performs one time setup at startup over CAN bus
void GrowattHvInverter::setup(void) { // Performs one time setup at startup over CAN bus
strncpy(datalayer.system.info.inverter_protocol, "Growatt High Voltage protocol via CAN", 63);
datalayer.system.info.inverter_protocol[63] = '\0';
}
#endif

View file

@ -2,9 +2,156 @@
#define GROWATT_HV_CAN_H
#include "../include.h"
#define CAN_INVERTER_SELECTED
#include "CanInverterProtocol.h"
void transmit_can_frame(CAN_frame* tx_frame, int interface);
void setup_inverter(void);
#ifdef GROWATT_HV_CAN
#define CAN_INVERTER_SELECTED
#define SELECTED_INVERTER_CLASS GrowattHvInverter
#endif
class GrowattHvInverter : public CanInverterProtocol {
public:
void setup();
void update_values();
void transmit_can(unsigned long currentMillis);
void map_can_frame_to_variable(CAN_frame rx_frame);
private:
//Total number of Cells (1-512)
//(Total number of Cells = number of Packs in parallel * number of Modules in series * number of Cells in the module)
static const int TOTAL_NUMBER_OF_CELLS = 300;
// Number of Modules in series (1-32)
static const int NUMBER_OF_MODULES_IN_SERIES = 20;
// Number of packs in parallel (1-65536)
static const int NUMBER_OF_PACKS_IN_PARALLEL = 1;
//Manufacturer abbreviation, part 1
static const int MANUFACTURER_ASCII_0 = 0x47; //G
static const int MANUFACTURER_ASCII_1 = 0x54; //T
/* Do not change code below unless you are sure what you are doing */
//Actual content messages
CAN_frame GROWATT_3110 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x3110,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame GROWATT_3120 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x3120,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame GROWATT_3130 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x3130,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame GROWATT_3140 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x3140,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame GROWATT_3150 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x3150,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame GROWATT_3160 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x3160,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame GROWATT_3170 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x3170,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame GROWATT_3180 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x3180,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame GROWATT_3190 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x3190,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame GROWATT_3200 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x3200,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame GROWATT_3210 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x3210,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame GROWATT_3220 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x3220,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame GROWATT_3230 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x3230,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame GROWATT_3240 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x3240,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame GROWATT_3250 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x3250,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame GROWATT_3260 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x3260,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame GROWATT_3270 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x3270,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame GROWATT_3280 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x3280,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame GROWATT_3290 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x3290,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame GROWATT_3F00 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x3F00,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
unsigned long previousMillis1s = 0; // will store last time a 1s CAN Message was send
unsigned long previousMillisBatchSend = 0;
uint32_t unix_time = 0;
uint16_t ampere_hours_remaining = 0;
uint16_t ampere_hours_full = 0;
uint16_t send_times = 0; // Overflows every 18hours. Cumulative number, plus 1 for each transmission
uint8_t safety_specification = 0;
uint8_t charging_command = 0;
uint8_t discharging_command = 0;
uint8_t shielding_external_communication_failure = 0;
uint8_t clearing_battery_fault =
0; //When PCS receives the forced charge Mark 1 and Cell under- voltage protection fault, it will send 0XAA
uint8_t ISO_detection_command = 0;
uint8_t sleep_wakeup_control = 0;
uint8_t PCS_working_status = 0; //00 standby, 01 operating
uint8_t serial_number_counter = 0; //0-1-2-0-1-2...
uint8_t can_message_batch_index = 0;
const uint8_t delay_between_batches_ms = 10;
bool inverter_alive = false;
bool time_to_send_1s_data = false;
};
#endif

View file

@ -1,7 +1,7 @@
#include "../include.h"
#ifdef GROWATT_LV_CAN
#include "../datalayer/datalayer.h"
#include "GROWATT-LV-CAN.h"
#include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h"
#include "../include.h"
/* Growatt BMS CAN-Bus-protocol Low Voltage Rev_04
CAN 2.0A
@ -10,76 +10,8 @@ Big-endian
The inverter replies data every second (standard frame/decimal)0x301:*/
/* Do not change code below unless you are sure what you are doing */
//Actual content messages
CAN_frame GROWATT_311 = {.FD = false, //Voltage and charge limits and status
.ext_ID = false,
.DLC = 8,
.ID = 0x311,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame GROWATT_312 = {.FD = false, //status bits , pack number, total cell number
.ext_ID = false,
.DLC = 8,
.ID = 0x312,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame GROWATT_313 = {.FD = false, //voltage, current, temp, soc, soh
.ext_ID = false,
.DLC = 8,
.ID = 0x313,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame GROWATT_314 = {.FD = false, //capacity, delta V, cycle count
.ext_ID = false,
.DLC = 8,
.ID = 0x314,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame GROWATT_319 = {.FD = false, //max/min cell voltage, num of cell max/min, protect pack ID
.ext_ID = false,
.DLC = 8,
.ID = 0x319,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame GROWATT_320 = {.FD = false, //manufacturer name, hw ver, sw ver, date and time
.ext_ID = false,
.DLC = 8,
.ID = 0x320,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame GROWATT_321 = {.FD = false, //Update status, ID
.ext_ID = false,
.DLC = 8,
.ID = 0x321,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
//Cellvoltages
CAN_frame GROWATT_315 = {.FD = false, //Cells 1-4
.ext_ID = false,
.DLC = 8,
.ID = 0x315,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame GROWATT_316 = {.FD = false, //Cells 5-8
.ext_ID = false,
.DLC = 8,
.ID = 0x316,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame GROWATT_317 = {.FD = false, //Cells 9-12
.ext_ID = false,
.DLC = 8,
.ID = 0x317,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame GROWATT_318 = {.FD = false, //Cells 13-16
.ext_ID = false,
.DLC = 8,
.ID = 0x318,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
#define VOLTAGE_OFFSET_DV 40 //Offset in deciVolt from max charge voltage and min discharge voltage
#define MAX_VOLTAGE_DV 630
#define MIN_VOLTAGE_DV 410
static uint16_t cell_delta_mV = 0;
static uint16_t ampere_hours_remaining = 0;
static uint16_t ampere_hours_full = 0;
void update_values_can_inverter() { //This function maps all the values fetched from battery CAN to the correct CAN messages
void GrowattLvInverter::
update_values() { //This function maps all the values fetched from battery CAN to the correct CAN messages
cell_delta_mV = datalayer.battery.status.cell_max_voltage_mV - datalayer.battery.status.cell_min_voltage_mV;
@ -246,7 +178,7 @@ void update_values_can_inverter() { //This function maps all the values fetched
GROWATT_318.data.u8[7] = (datalayer.battery.status.cell_voltages_mV[15] & 0x00FF);
}
void map_can_frame_to_variable_inverter(CAN_frame rx_frame) {
void GrowattLvInverter::map_can_frame_to_variable(CAN_frame rx_frame) {
switch (rx_frame.ID) {
case 0x301:
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
@ -267,12 +199,11 @@ void map_can_frame_to_variable_inverter(CAN_frame rx_frame) {
}
}
void transmit_can_inverter(unsigned long currentMillis) {
void GrowattLvInverter::transmit_can(unsigned long currentMillis) {
// No periodic sending for this battery type. Data is sent when inverter requests it
}
void setup_inverter(void) { // Performs one time setup at startup over CAN bus
void GrowattLvInverter::setup(void) { // Performs one time setup at startup over CAN bus
strncpy(datalayer.system.info.inverter_protocol, "Growatt Low Voltage (48V) protocol via CAN", 63);
datalayer.system.info.inverter_protocol[63] = '\0';
}
#endif

View file

@ -2,9 +2,87 @@
#define GROWATT_LV_CAN_H
#include "../include.h"
#define CAN_INVERTER_SELECTED
#include "CanInverterProtocol.h"
void transmit_can_frame(CAN_frame* tx_frame, int interface);
void setup_inverter(void);
#ifdef GROWATT_LV_CAN
#define CAN_INVERTER_SELECTED
#define SELECTED_INVERTER_CLASS GrowattLvInverter
#endif
class GrowattLvInverter : public CanInverterProtocol {
public:
void setup();
void update_values();
void transmit_can(unsigned long currentMillis);
void map_can_frame_to_variable(CAN_frame rx_frame);
private:
//Actual content messages
CAN_frame GROWATT_311 = {.FD = false, //Voltage and charge limits and status
.ext_ID = false,
.DLC = 8,
.ID = 0x311,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame GROWATT_312 = {.FD = false, //status bits , pack number, total cell number
.ext_ID = false,
.DLC = 8,
.ID = 0x312,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame GROWATT_313 = {.FD = false, //voltage, current, temp, soc, soh
.ext_ID = false,
.DLC = 8,
.ID = 0x313,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame GROWATT_314 = {.FD = false, //capacity, delta V, cycle count
.ext_ID = false,
.DLC = 8,
.ID = 0x314,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame GROWATT_319 = {.FD = false, //max/min cell voltage, num of cell max/min, protect pack ID
.ext_ID = false,
.DLC = 8,
.ID = 0x319,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame GROWATT_320 = {.FD = false, //manufacturer name, hw ver, sw ver, date and time
.ext_ID = false,
.DLC = 8,
.ID = 0x320,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame GROWATT_321 = {.FD = false, //Update status, ID
.ext_ID = false,
.DLC = 8,
.ID = 0x321,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
//Cellvoltages
CAN_frame GROWATT_315 = {.FD = false, //Cells 1-4
.ext_ID = false,
.DLC = 8,
.ID = 0x315,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame GROWATT_316 = {.FD = false, //Cells 5-8
.ext_ID = false,
.DLC = 8,
.ID = 0x316,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame GROWATT_317 = {.FD = false, //Cells 9-12
.ext_ID = false,
.DLC = 8,
.ID = 0x317,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame GROWATT_318 = {.FD = false, //Cells 13-16
.ext_ID = false,
.DLC = 8,
.ID = 0x318,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
static const int VOLTAGE_OFFSET_DV = 40; //Offset in deciVolt from max charge voltage and min discharge voltage
static const int MAX_VOLTAGE_DV = 630;
static const int MIN_VOLTAGE_DV = 410;
uint16_t cell_delta_mV = 0;
uint16_t ampere_hours_remaining = 0;
uint16_t ampere_hours_full = 0;
};
#endif

View file

@ -3,9 +3,7 @@
// These functions adapt the old C-style global functions inverter-API to the
// object-oriented inverter protocol API.
#ifdef SELECTED_INVERTER_CLASS
InverterProtocol* inverter;
InverterProtocol* inverter = nullptr;
#ifdef CAN_INVERTER_SELECTED
CanInverterProtocol* can_inverter;
@ -30,7 +28,9 @@ void setup_inverter() {
inverter = new SELECTED_INVERTER_CLASS();
#endif
inverter->setup();
if (inverter) {
inverter->setup();
}
}
#ifdef CAN_INVERTER_SELECTED
@ -52,5 +52,3 @@ void receive_RS485() {
((Rs485InverterProtocol*)inverter)->receive_RS485();
}
#endif
#endif

View file

@ -6,82 +6,31 @@ extern InverterProtocol* inverter;
#include "../../USER_SETTINGS.h"
#ifdef AFORE_CAN
#include "AFORE-CAN.h"
#endif
#ifdef BYD_CAN_DEYE
#define BYD_CAN
#endif
#ifdef BYD_CAN
#include "BYD-CAN.h"
#endif
#ifdef BYD_MODBUS
#include "BYD-MODBUS.h"
#endif
#ifdef BYD_KOSTAL_RS485
#include "KOSTAL-RS485.h"
#endif
#ifdef FERROAMP_CAN
#include "FERROAMP-CAN.h"
#endif
#ifdef FOXESS_CAN
#include "FOXESS-CAN.h"
#endif
#ifdef GROWATT_HV_CAN
#include "GROWATT-HV-CAN.h"
#endif
#ifdef GROWATT_LV_CAN
#include "GROWATT-LV-CAN.h"
#endif
#ifdef PYLON_CAN
#include "KOSTAL-RS485.h"
#include "PYLON-CAN.h"
#endif
#ifdef PYLON_LV_CAN
#include "PYLON-LV-CAN.h"
#endif
#ifdef SCHNEIDER_CAN
#include "SCHNEIDER-CAN.h"
#endif
#ifdef SMA_BYD_H_CAN
#include "SMA-BYD-H-CAN.h"
#endif
#ifdef SMA_BYD_HVS_CAN
#include "SMA-BYD-HVS-CAN.h"
#endif
#ifdef SMA_LV_CAN
#include "SMA-LV-CAN.h"
#endif
#ifdef SMA_TRIPOWER_CAN
#include "SMA-TRIPOWER-CAN.h"
#endif
#ifdef SOFAR_CAN
#include "SOFAR-CAN.h"
#endif
#ifdef SOLAX_CAN
#include "SOLAX-CAN.h"
#endif
#ifdef SUNGROW_CAN
#include "SUNGROW-CAN.h"
#endif
// Call to initialize the build-time selected inverter. Safe to call even though inverter was not selected.
void setup_inverter();
#ifdef CAN_INVERTER_SELECTED

View file

@ -5,6 +5,7 @@
class InverterProtocol {
public:
virtual void setup() = 0;
virtual const char* interface_name() = 0;
// This function maps all the values fetched from battery to the correct battery emulator data structures
virtual void update_values() = 0;

View file

@ -1,8 +1,7 @@
#include "../include.h"
#ifdef BYD_KOSTAL_RS485
#include "KOSTAL-RS485.h"
#include "../datalayer/datalayer.h"
#include "../devboard/utils/events.h"
#include "KOSTAL-RS485.h"
#include "../include.h"
void KostalInverterProtocol::float2frame(byte* arr, float value, byte framepointer) {
f32b g;
@ -307,4 +306,3 @@ void KostalInverterProtocol::setup(void) { // Performs one time setup at startu
Serial2.begin(baud_rate(), SERIAL_8N1, RS485_RX_PIN, RS485_TX_PIN);
}
#endif

View file

@ -5,8 +5,10 @@
#include "Rs485InverterProtocol.h"
#ifdef BYD_KOSTAL_RS485
#define RS485_INVERTER_SELECTED
#define SELECTED_INVERTER_CLASS KostalInverterProtocol
#endif
//#define DEBUG_KOSTAL_RS485_DATA // Enable this line to get TX / RX printed out via logging
//#define DEBUG_KOSTAL_RS485_DATA_USB // Enable this line to get TX / RX printed out via USB

View file

@ -10,6 +10,8 @@ extern uint16_t mbPV[];
// The abstract base class for all Modbus inverter protocols
class ModbusInverterProtocol : public InverterProtocol {
virtual const char* interface_name() { return "RS485 / Modbus"; }
protected:
// Create a ModbusRTU server instance with 2000ms timeout
ModbusInverterProtocol() : MBserver(2000) { mbPV = ::mbPV; }

View file

@ -1,144 +1,10 @@
#include "../include.h"
#ifdef PYLON_CAN
#include "../datalayer/datalayer.h"
#include "PYLON-CAN.h"
#include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h"
#include "../include.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
#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, (uint8_t)(VOLTAGE_LEVEL & 0x00FF), (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, (uint8_t)(VOLTAGE_LEVEL & 0x00FF), (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 discharge_cutoff_voltage_dV = 0;
static uint16_t charge_cutoff_voltage_dV = 0;
#define VOLTAGE_OFFSET_DV 20 // Small offset voltage to avoid generating voltage events
void update_values_can_inverter() { //This function maps all the values fetched from battery CAN to the correct CAN messages
void PylonInverter::
update_values() { //This function maps all the values fetched from battery CAN to the correct CAN messages
//Check what discharge and charge cutoff voltages to send
if (datalayer.battery.settings.user_set_voltage_limits_active) { //If user is requesting a specific voltage
@ -422,7 +288,7 @@ void update_values_can_inverter() { //This function maps all the values fetched
}
}
void map_can_frame_to_variable_inverter(CAN_frame rx_frame) {
void PylonInverter::map_can_frame_to_variable(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;
@ -438,11 +304,11 @@ void map_can_frame_to_variable_inverter(CAN_frame rx_frame) {
}
}
void transmit_can_inverter(unsigned long currentMillis) {
void PylonInverter::transmit_can(unsigned long currentMillis) {
// No periodic sending, we only react on received can messages
}
void send_setup_info() { //Ensemble information
void PylonInverter::send_setup_info() { //Ensemble information
#ifdef SEND_0
transmit_can_frame(&PYLON_7310, can_config.inverter);
transmit_can_frame(&PYLON_7320, can_config.inverter);
@ -453,7 +319,7 @@ void send_setup_info() { //Ensemble information
#endif
}
void send_system_data() { //System equipment information
void PylonInverter::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);
@ -477,8 +343,8 @@ void send_system_data() { //System equipment information
transmit_can_frame(&PYLON_4291, can_config.inverter);
#endif
}
void setup_inverter(void) { // Performs one time setup at startup over CAN bus
void PylonInverter::setup(void) { // Performs one time setup at startup over CAN bus
strncpy(datalayer.system.info.inverter_protocol, "Pylontech battery over CAN bus", 63);
datalayer.system.info.inverter_protocol[63] = '\0';
}
#endif

View file

@ -2,11 +2,159 @@
#define PYLON_CAN_H
#include "../include.h"
#define CAN_INVERTER_SELECTED
#include "CanInverterProtocol.h"
void send_system_data();
void send_setup_info();
void transmit_can_frame(CAN_frame* tx_frame, int interface);
void setup_inverter(void);
#ifdef PYLON_CAN
#define CAN_INVERTER_SELECTED
#define SELECTED_INVERTER_CLASS PylonInverter
#endif
class PylonInverter : public CanInverterProtocol {
public:
void setup();
void update_values();
void transmit_can(unsigned long currentMillis);
void map_can_frame_to_variable(CAN_frame rx_frame);
private:
void send_system_data();
void send_setup_info();
#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 */
static const int TOTAL_CELL_AMOUNT = 120;
static const int MODULES_IN_SERIES = 4;
static const int CELLS_PER_MODULE = 30;
static const int VOLTAGE_LEVEL = 384;
static const int 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, (uint8_t)(VOLTAGE_LEVEL & 0x00FF), (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, (uint8_t)(VOLTAGE_LEVEL & 0x00FF), (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}};
uint16_t discharge_cutoff_voltage_dV = 0;
uint16_t charge_cutoff_voltage_dV = 0;
static const int VOLTAGE_OFFSET_DV = 20; // Small offset voltage to avoid generating voltage events
};
#endif

View file

@ -1,53 +1,17 @@
#include "../include.h"
#ifdef PYLON_LV_CAN
#include "../datalayer/datalayer.h"
#include "PYLON-LV-CAN.h"
/* Do not change code below unless you are sure what you are doing */
static unsigned long previousMillis1000ms = 0;
CAN_frame PYLON_351 = {.FD = false,
.ext_ID = false,
.DLC = 6,
.ID = 0x351,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame PYLON_355 = {.FD = false, .ext_ID = false, .DLC = 4, .ID = 0x355, .data = {0x00, 0x00, 0x00, 0x00}};
CAN_frame PYLON_356 = {.FD = false,
.ext_ID = false,
.DLC = 6,
.ID = 0x356,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame PYLON_359 = {.FD = false,
.ext_ID = false,
.DLC = 7,
.ID = 0x359,
.data = {0x00, 0x00, 0x00, 0x00, PACK_NUMBER, 'P', 'N'}};
CAN_frame PYLON_35C = {.FD = false, .ext_ID = false, .DLC = 2, .ID = 0x35C, .data = {0x00, 0x00}};
CAN_frame PYLON_35E = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x35E,
.data = {
MANUFACTURER_NAME[0],
MANUFACTURER_NAME[1],
MANUFACTURER_NAME[2],
MANUFACTURER_NAME[3],
MANUFACTURER_NAME[4],
MANUFACTURER_NAME[5],
MANUFACTURER_NAME[6],
MANUFACTURER_NAME[7],
}};
#include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h"
#include "../include.h"
// 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 PylonLvInverter::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() {
void PylonLvInverter::update_values() {
// This function maps all the values fetched from battery CAN to the correct CAN messages
// Set "battery charge voltage" to volts + 1 or user supplied value
@ -133,7 +97,7 @@ void update_values_can_inverter() {
// PYLON_35E is pre-filled with the manufacturer name
}
void map_can_frame_to_variable_inverter(CAN_frame rx_frame) {
void PylonLvInverter::map_can_frame_to_variable(CAN_frame rx_frame) {
switch (rx_frame.ID) {
case 0x305: //Message originating from inverter.
// according to the spec, this message includes only 0-bytes
@ -158,7 +122,7 @@ void dump_frame(CAN_frame* frame) {
}
#endif
void transmit_can_inverter(unsigned long currentMillis) {
void PylonLvInverter::transmit_can(unsigned long currentMillis) {
if (currentMillis - previousMillis1000ms >= 1000) {
previousMillis1000ms = currentMillis;
@ -180,8 +144,8 @@ void transmit_can_inverter(unsigned long currentMillis) {
transmit_can_frame(&PYLON_35E, can_config.inverter);
}
}
void setup_inverter(void) { // Performs one time setup at startup over CAN bus
void PylonLvInverter::setup(void) { // Performs one time setup at startup over CAN bus
strncpy(datalayer.system.info.inverter_protocol, "Pylontech LV battery over CAN bus", 63);
datalayer.system.info.inverter_protocol[63] = '\0';
}
#endif

View file

@ -2,16 +2,65 @@
#define PYLON_LV_CAN_H
#include "../include.h"
#include "CanInverterProtocol.h"
#ifdef PYLON_LV_CAN
#define CAN_INVERTER_SELECTED
#define SELECTED_INVERTER_CLASS PylonLvInverter
#endif
#define MANUFACTURER_NAME "BatEmuLV"
#define PACK_NUMBER 0x01
// 80 means after reaching 80% of a nominal value a warning is produced (e.g. 80% of max current)
#define WARNINGS_PERCENT 80
class PylonLvInverter : public CanInverterProtocol {
public:
void setup();
void update_values();
void transmit_can(unsigned long currentMillis);
void map_can_frame_to_variable(CAN_frame rx_frame);
void send_system_data();
void send_setup_info();
void transmit_can_frame(CAN_frame* tx_frame, int interface);
void setup_inverter(void);
private:
void send_system_data();
void send_setup_info();
int16_t warning_threshold_of_min(int16_t min_val, int16_t max_val);
static const int PACK_NUMBER = 0x01;
// 80 means after reaching 80% of a nominal value a warning is produced (e.g. 80% of max current)
static const int WARNINGS_PERCENT = 80;
static constexpr const char* MANUFACTURER_NAME = "BatEmuLV";
unsigned long previousMillis1000ms = 0;
CAN_frame PYLON_351 = {.FD = false,
.ext_ID = false,
.DLC = 6,
.ID = 0x351,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame PYLON_355 = {.FD = false, .ext_ID = false, .DLC = 4, .ID = 0x355, .data = {0x00, 0x00, 0x00, 0x00}};
CAN_frame PYLON_356 = {.FD = false,
.ext_ID = false,
.DLC = 6,
.ID = 0x356,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame PYLON_359 = {.FD = false,
.ext_ID = false,
.DLC = 7,
.ID = 0x359,
.data = {0x00, 0x00, 0x00, 0x00, PACK_NUMBER, 'P', 'N'}};
CAN_frame PYLON_35C = {.FD = false, .ext_ID = false, .DLC = 2, .ID = 0x35C, .data = {0x00, 0x00}};
CAN_frame PYLON_35E = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x35E,
.data = {
MANUFACTURER_NAME[0],
MANUFACTURER_NAME[1],
MANUFACTURER_NAME[2],
MANUFACTURER_NAME[3],
MANUFACTURER_NAME[4],
MANUFACTURER_NAME[5],
MANUFACTURER_NAME[6],
MANUFACTURER_NAME[7],
}};
};
#endif

View file

@ -5,6 +5,7 @@
class Rs485InverterProtocol : public InverterProtocol {
public:
virtual const char* interface_name() { return "RS485"; }
virtual void receive_RS485() = 0;
virtual int baud_rate() = 0;
};

View file

@ -1,7 +1,7 @@
#include "../include.h"
#ifdef SCHNEIDER_CAN
#include "../datalayer/datalayer.h"
#include "SCHNEIDER-CAN.h"
#include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h"
#include "../include.h"
/* Version 2: SE BMS Communication Protocol
Protocol: CAN 2.0 Specification
@ -16,78 +16,8 @@ Endian: Big Endian (MSB, most significant byte of a value received first)*/
- We will need CAN logs from existing battery OR contact Schneider for one free number
*/
/* Do not change code below unless you are sure what you are doing */
static unsigned long previousMillis10s = 0; // will store last time a 10s CAN Message was send
static unsigned long previousMillis2s = 0; // will store last time a 2s CAN Message was send
static unsigned long previousMillis500ms = 0; // will store last time a 500ms CAN Message was send
CAN_frame SE_320 = {.FD = false, //SE BMS Protocol Version
.ext_ID = true,
.DLC = 2,
.ID = 0x320,
.data = {0x00, 0x02}}; //TODO: How do we reply with Protocol Version: 0x0002 ?
CAN_frame SE_321 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x321,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SE_322 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x322,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SE_323 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x323,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SE_324 = {.FD = false, .ext_ID = true, .DLC = 4, .ID = 0x324, .data = {0x00, 0x00, 0x00, 0x00}};
CAN_frame SE_325 = {.FD = false, .ext_ID = true, .DLC = 6, .ID = 0x325, .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SE_326 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x326,
.data = {0x00, STATE_STARTING, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SE_327 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x327,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SE_328 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x328,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SE_330 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x330,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SE_331 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x331,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SE_332 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x332,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SE_333 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x333,
.data = {0x53, 0x45, 0x42, 0x4D, 0x53, 0x00, 0x00, 0x00}}; //SEBMS
static int16_t temperature_average = 0;
static uint16_t remaining_capacity_ah = 0;
static uint16_t fully_charged_capacity_ah = 0;
static uint16_t commands = 0;
static uint16_t warnings = 0;
static uint16_t faults = 0;
static uint16_t state = 0;
void update_values_can_inverter() { //This function maps all the values fetched from battery CAN to the correct CAN messages
void SchneiderInverter::
update_values() { //This function maps all the values fetched from battery CAN to the correct CAN messages
/* Calculate temperature */
temperature_average =
@ -255,7 +185,7 @@ void update_values_can_inverter() { //This function maps all the values fetched
SE_320.data.u8[1] = 0x02;
}
void map_can_frame_to_variable_inverter(CAN_frame rx_frame) {
void SchneiderInverter::map_can_frame_to_variable(CAN_frame rx_frame) {
switch (rx_frame.ID) {
case 0x310: // Still alive message from inverter, every 1s
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
@ -265,7 +195,7 @@ void map_can_frame_to_variable_inverter(CAN_frame rx_frame) {
}
}
void transmit_can_inverter(unsigned long currentMillis) {
void SchneiderInverter::transmit_can(unsigned long currentMillis) {
// Send 500ms CAN Message
if (currentMillis - previousMillis500ms >= INTERVAL_500_MS) {
@ -296,9 +226,7 @@ void transmit_can_inverter(unsigned long currentMillis) {
}
}
void setup_inverter(void) { // Performs one time setup
void SchneiderInverter::setup(void) { // Performs one time setup
strncpy(datalayer.system.info.inverter_protocol, "Schneider V2 SE BMS CAN", 63);
datalayer.system.info.inverter_protocol[63] = '\0';
}
#endif

View file

@ -2,32 +2,113 @@
#define SCHNEIDER_CAN_H
#include "../include.h"
#include "CanInverterProtocol.h"
#ifdef SCHNEIDER_CAN
#define CAN_INVERTER_SELECTED
#define SELECTED_INVERTER_CLASS SchneiderInverter
#endif
#define STATE_OFFLINE 0
#define STATE_STANDBY 1
#define STATE_STARTING 2
#define STATE_ONLINE 3
#define STATE_FAULTED 4
class SchneiderInverter : public CanInverterProtocol {
public:
void setup();
void update_values();
void transmit_can(unsigned long currentMillis);
void map_can_frame_to_variable(CAN_frame rx_frame);
// Same enumerations used for Fault and Warning
#define FAULTS_CHARGE_OVERCURRENT 0
#define FAULTS_DISCHARGE_OVERCURRENT 1
#define FAULTS_OVER_TEMPERATURE 2
#define FAULTS_UNDER_TEMPERATURE 3
#define FAULTS_OVER_VOLTAGE 4
#define FAULTS_UNDER_VOLTAGE 5
#define FAULTS_CELL_IMBALANCE 6
#define FAULTS_INTERNAL_COM_ERROR 7
#define FAULTS_SYSTEM_ERROR 8
private:
static const int STATE_OFFLINE = 0;
static const int STATE_STANDBY = 1;
static const int STATE_STARTING = 2;
static const int STATE_ONLINE = 3;
static const int STATE_FAULTED = 4;
// Commands. Bit0 forced charge request. Bit1 charge permitted. Bit2 discharge permitted. Bit3 Stop
#define COMMAND_ONLY_CHARGE_ALLOWED 0x02
#define COMMAND_ONLY_DISCHARGE_ALLOWED 0x04
#define COMMAND_CHARGE_AND_DISCHARGE_ALLOWED 0x06
#define COMMAND_STOP 0x08
// Same enumerations used for Fault and Warning
static const int FAULTS_CHARGE_OVERCURRENT = 0;
static const int FAULTS_DISCHARGE_OVERCURRENT = 1;
static const int FAULTS_OVER_TEMPERATURE = 2;
static const int FAULTS_UNDER_TEMPERATURE = 3;
static const int FAULTS_OVER_VOLTAGE = 4;
static const int FAULTS_UNDER_VOLTAGE = 5;
static const int FAULTS_CELL_IMBALANCE = 6;
static const int FAULTS_INTERNAL_COM_ERROR = 7;
static const int FAULTS_SYSTEM_ERROR = 8;
void transmit_can_frame(CAN_frame* tx_frame, int interface);
void setup_inverter(void);
// Commands. Bit0 forced charge request. Bit1 charge permitted. Bit2 discharge permitted. Bit3 Stop
static const int COMMAND_ONLY_CHARGE_ALLOWED = 0x02;
static const int COMMAND_ONLY_DISCHARGE_ALLOWED = 0x04;
static const int COMMAND_CHARGE_AND_DISCHARGE_ALLOWED = 0x06;
static const int COMMAND_STOP = 0x08;
unsigned long previousMillis10s = 0; // will store last time a 10s CAN Message was send
unsigned long previousMillis2s = 0; // will store last time a 2s CAN Message was send
unsigned long previousMillis500ms = 0; // will store last time a 500ms CAN Message was send
CAN_frame SE_320 = {.FD = false, //SE BMS Protocol Version
.ext_ID = true,
.DLC = 2,
.ID = 0x320,
.data = {0x00, 0x02}}; //TODO: How do we reply with Protocol Version: 0x0002 ?
CAN_frame SE_321 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x321,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SE_322 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x322,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SE_323 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x323,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SE_324 = {.FD = false, .ext_ID = true, .DLC = 4, .ID = 0x324, .data = {0x00, 0x00, 0x00, 0x00}};
CAN_frame SE_325 = {.FD = false, .ext_ID = true, .DLC = 6, .ID = 0x325, .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SE_326 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x326,
.data = {0x00, STATE_STARTING, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SE_327 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x327,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SE_328 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x328,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SE_330 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x330,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SE_331 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x331,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SE_332 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x332,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SE_333 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x333,
.data = {0x53, 0x45, 0x42, 0x4D, 0x53, 0x00, 0x00, 0x00}}; //SEBMS
int16_t temperature_average = 0;
uint16_t remaining_capacity_ah = 0;
uint16_t fully_charged_capacity_ah = 0;
uint16_t commands = 0;
uint16_t warnings = 0;
uint16_t faults = 0;
uint16_t state = 0;
};
#endif

View file

@ -1,86 +1,15 @@
#include "../include.h"
#ifdef SMA_BYD_H_CAN
#include "SMA-BYD-H-CAN.h"
#include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h"
#include "../devboard/utils/events.h"
#include "SMA-BYD-H-CAN.h"
#include "../include.h"
/* TODO: Map error bits in 0x158 */
/* Do not change code below unless you are sure what you are doing */
static unsigned long previousMillis100ms = 0;
static uint32_t inverter_time = 0;
static uint16_t inverter_voltage = 0;
static int16_t inverter_current = 0;
static uint16_t timeWithoutInverterAllowsContactorClosing = 0;
#define THIRTY_MINUTES 1200
//Actual content messages
CAN_frame SMA_158 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x158,
.data = {0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x6A, 0xAA, 0xAA}};
CAN_frame SMA_358 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x358,
.data = {0x0F, 0x6C, 0x06, 0x20, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SMA_3D8 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x3D8,
.data = {0x04, 0x10, 0x27, 0x10, 0x00, 0x18, 0xF9, 0x00}};
CAN_frame SMA_458 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x458,
.data = {0x00, 0x00, 0x06, 0x75, 0x00, 0x00, 0x05, 0xD6}};
CAN_frame SMA_4D8 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x4D8,
.data = {0x09, 0xFD, 0x00, 0x00, 0x00, 0xA8, 0x02, 0x08}};
CAN_frame SMA_518 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x518,
.data = {0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF}};
CAN_frame SMA_558 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x558,
.data = {0x03, 0x12, 0x00, 0x04, 0x00, 0x59, 0x07, 0x07}}; //7x BYD modules, Vendor ID 7 BYD
CAN_frame SMA_598 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x598,
.data = {0x00, 0x00, 0x12, 0x34, 0x5A, 0xDE, 0x07, 0x4F}}; //B0-4 Serial, rest unknown
CAN_frame SMA_5D8 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x5D8,
.data = {0x00, 0x42, 0x59, 0x44, 0x00, 0x00, 0x00, 0x00}}; //B Y D
CAN_frame SMA_618_1 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x618,
.data = {0x00, 0x42, 0x61, 0x74, 0x74, 0x65, 0x72, 0x79}}; //0 B A T T E R Y
CAN_frame SMA_618_2 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x618,
.data = {0x01, 0x2D, 0x42, 0x6F, 0x78, 0x20, 0x48, 0x39}}; //1 - B O X H
CAN_frame SMA_618_3 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x618,
.data = {0x02, 0x2E, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00}}; //2 - 0
static int16_t temperature_average = 0;
static uint16_t ampere_hours_remaining = 0;
void update_values_can_inverter() { //This function maps all the values fetched from battery CAN to the inverter CAN
void SmaBydHInverter::
update_values() { //This function maps all the values fetched from battery CAN to the inverter CAN
// Update values
temperature_average =
((datalayer.battery.status.temperature_max_dC + datalayer.battery.status.temperature_min_dC) / 2);
@ -210,7 +139,7 @@ void update_values_can_inverter() { //This function maps all the values fetched
*/
}
void map_can_frame_to_variable_inverter(CAN_frame rx_frame) {
void SmaBydHInverter::map_can_frame_to_variable(CAN_frame rx_frame) {
switch (rx_frame.ID) {
case 0x360: //Message originating from SMA inverter - Voltage and current
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
@ -250,7 +179,7 @@ void map_can_frame_to_variable_inverter(CAN_frame rx_frame) {
}
}
void transmit_can_inverter(unsigned long currentMillis) {
void SmaBydHInverter::transmit_can(unsigned long currentMillis) {
// Send CAN Message every 100ms if inverter allows contactor closing
if (datalayer.system.status.inverter_allows_contactor_closing) {
@ -267,7 +196,7 @@ void transmit_can_inverter(unsigned long currentMillis) {
}
}
void transmit_can_init() {
void SmaBydHInverter::transmit_can_init() {
transmit_can_frame(&SMA_558, can_config.inverter);
transmit_can_frame(&SMA_598, can_config.inverter);
transmit_can_frame(&SMA_5D8, can_config.inverter);
@ -282,7 +211,7 @@ void transmit_can_init() {
transmit_can_frame(&SMA_4D8, can_config.inverter);
}
void setup_inverter(void) { // Performs one time setup at startup over CAN bus
void SmaBydHInverter::setup(void) { // Performs one time setup at startup over CAN bus
strncpy(datalayer.system.info.inverter_protocol, "SMA CAN", 63);
datalayer.system.info.inverter_protocol[63] = '\0';
datalayer.system.status.inverter_allows_contactor_closing = false; // The inverter needs to allow first
@ -292,4 +221,3 @@ void setup_inverter(void) { // Performs one time setup at startup over CAN bus
digitalWrite(INVERTER_CONTACTOR_ENABLE_LED_PIN, LOW); // Turn LED off, until inverter allows contactor closing
#endif // INVERTER_CONTACTOR_ENABLE_LED_PIN
}
#endif

View file

@ -2,13 +2,98 @@
#define SMA_BYD_H_CAN_H
#include "../include.h"
#include "CanInverterProtocol.h"
#ifdef SMA_BYD_H_CAN
#define CAN_INVERTER_SELECTED
#define SELECTED_INVERTER_CLASS SmaBydHInverter
#endif
#define READY_STATE 0x03
#define STOP_STATE 0x02
class SmaBydHInverter : public CanInverterProtocol {
public:
void setup();
void update_values();
void transmit_can(unsigned long currentMillis);
void map_can_frame_to_variable(CAN_frame rx_frame);
void transmit_can_frame(CAN_frame* tx_frame, int interface);
void transmit_can_init();
void setup_inverter(void);
private:
static const int READY_STATE = 0x03;
static const int STOP_STATE = 0x02;
void transmit_can_init();
unsigned long previousMillis100ms = 0;
uint32_t inverter_time = 0;
uint16_t inverter_voltage = 0;
int16_t inverter_current = 0;
uint16_t timeWithoutInverterAllowsContactorClosing = 0;
static const int THIRTY_MINUTES = 1200;
//Actual content messages
CAN_frame SMA_158 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x158,
.data = {0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x6A, 0xAA, 0xAA}};
CAN_frame SMA_358 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x358,
.data = {0x0F, 0x6C, 0x06, 0x20, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SMA_3D8 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x3D8,
.data = {0x04, 0x10, 0x27, 0x10, 0x00, 0x18, 0xF9, 0x00}};
CAN_frame SMA_458 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x458,
.data = {0x00, 0x00, 0x06, 0x75, 0x00, 0x00, 0x05, 0xD6}};
CAN_frame SMA_4D8 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x4D8,
.data = {0x09, 0xFD, 0x00, 0x00, 0x00, 0xA8, 0x02, 0x08}};
CAN_frame SMA_518 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x518,
.data = {0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF}};
CAN_frame SMA_558 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x558,
.data = {0x03, 0x12, 0x00, 0x04, 0x00, 0x59, 0x07, 0x07}}; //7x BYD modules, Vendor ID 7 BYD
CAN_frame SMA_598 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x598,
.data = {0x00, 0x00, 0x12, 0x34, 0x5A, 0xDE, 0x07, 0x4F}}; //B0-4 Serial, rest unknown
CAN_frame SMA_5D8 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x5D8,
.data = {0x00, 0x42, 0x59, 0x44, 0x00, 0x00, 0x00, 0x00}}; //B Y D
CAN_frame SMA_618_1 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x618,
.data = {0x00, 0x42, 0x61, 0x74, 0x74, 0x65, 0x72, 0x79}}; //0 B A T T E R Y
CAN_frame SMA_618_2 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x618,
.data = {0x01, 0x2D, 0x42, 0x6F, 0x78, 0x20, 0x48, 0x39}}; //1 - B O X H
CAN_frame SMA_618_3 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x618,
.data = {0x02, 0x2E, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00}}; //2 - 0
int16_t temperature_average = 0;
uint16_t ampere_hours_remaining = 0;
};
#endif

View file

@ -1,91 +1,15 @@
#include "../include.h"
#ifdef SMA_BYD_HVS_CAN
#include "SMA-BYD-HVS-CAN.h"
#include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h"
#include "../devboard/utils/events.h"
#include "SMA-BYD-HVS-CAN.h"
#include "../include.h"
/* TODO: Map error bits in 0x158 */
/* Do not change code below unless you are sure what you are doing */
static unsigned long previousMillis100ms = 0;
static uint32_t inverter_time = 0;
static uint16_t inverter_voltage = 0;
static int16_t inverter_current = 0;
static uint16_t timeWithoutInverterAllowsContactorClosing = 0;
#define THIRTY_MINUTES 1200
//Actual content messages
CAN_frame SMA_158 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x158, // All 0xAA, no faults active
.data = {0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}};
CAN_frame SMA_358 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x358,
.data = {0x11, 0xA0, 0x07, 0x00, 0x01, 0x5E, 0x00, 0xC8}};
CAN_frame SMA_3D8 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x3D8,
.data = {0x13, 0x2E, 0x27, 0x10, 0x00, 0x45, 0xF9, 0x00}};
CAN_frame SMA_458 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x458,
.data = {0x00, 0x00, 0x11, 0xC8, 0x00, 0x00, 0x0E, 0xF4}};
CAN_frame SMA_4D8 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x4D8,
.data = {0x10, 0x62, 0x00, 0x16, 0x01, 0x68, 0x03, 0x08}};
CAN_frame SMA_518 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x518,
.data = {0x01, 0x4A, 0x01, 0x25, 0xFF, 0xFF, 0xFF, 0xFF}};
// Pairing/Battery setup information
CAN_frame SMA_558 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x558,
.data = {0x03, 0x13, 0x00, 0x03, 0x00, 0x66, 0x04, 0x07}}; //4x BYD modules, Vendor ID 7 BYD
CAN_frame SMA_598 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x598,
.data = {0x00, 0x01, 0x0F, 0x2C, 0x5C, 0x98, 0xB6, 0xEE}};
CAN_frame SMA_5D8 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x5D8,
.data = {0x00, 0x42, 0x59, 0x44, 0x00, 0x00, 0x00, 0x00}}; //B Y D
CAN_frame SMA_618_1 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x618,
.data = {0x00, 0x42, 0x61, 0x74, 0x74, 0x65, 0x72, 0x79}}; //0 B A T T E R Y
CAN_frame SMA_618_2 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x618,
.data = {0x01, 0x2D, 0x42, 0x6F, 0x78, 0x20, 0x48, 0x31}}; //- B o x H 1
CAN_frame SMA_618_3 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x618,
.data = {0x02, 0x30, 0x2E, 0x32, 0x00, 0x00, 0x00, 0x00}}; // 0 . 2
static int16_t discharge_current = 0;
static int16_t charge_current = 0;
static int16_t temperature_average = 0;
static uint16_t ampere_hours_remaining = 0;
void update_values_can_inverter() { //This function maps all the values fetched from battery CAN to the inverter CAN
void SmaBydHvsInverter::
update_values() { //This function maps all the values fetched from battery CAN to the inverter CAN
// Update values
temperature_average =
((datalayer.battery.status.temperature_max_dC + datalayer.battery.status.temperature_min_dC) / 2);
@ -217,7 +141,7 @@ void update_values_can_inverter() { //This function maps all the values fetched
*/
}
void map_can_frame_to_variable_inverter(CAN_frame rx_frame) {
void SmaBydHvsInverter::map_can_frame_to_variable(CAN_frame rx_frame) {
switch (rx_frame.ID) {
case 0x360: //Message originating from SMA inverter - Voltage and current
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
@ -302,7 +226,7 @@ void map_can_frame_to_variable_inverter(CAN_frame rx_frame) {
}
}
void transmit_can_inverter(unsigned long currentMillis) {
void SmaBydHvsInverter::transmit_can(unsigned long currentMillis) {
// Send CAN Message every 100ms if inverter allows contactor closing
if (datalayer.system.status.inverter_allows_contactor_closing) {
if (currentMillis - previousMillis100ms >= 100) {
@ -317,7 +241,7 @@ void transmit_can_inverter(unsigned long currentMillis) {
}
}
void transmit_can_init() {
void SmaBydHvsInverter::transmit_can_init() {
transmit_can_frame(&SMA_558, can_config.inverter);
transmit_can_frame(&SMA_598, can_config.inverter);
transmit_can_frame(&SMA_5D8, can_config.inverter);
@ -332,7 +256,7 @@ void transmit_can_init() {
transmit_can_frame(&SMA_4D8, can_config.inverter);
}
void setup_inverter(void) { // Performs one time setup at startup over CAN bus
void SmaBydHvsInverter::setup(void) { // Performs one time setup at startup over CAN bus
strncpy(datalayer.system.info.inverter_protocol, "BYD Battery-Box HVS over SMA CAN", 63);
datalayer.system.info.inverter_protocol[63] = '\0';
datalayer.system.status.inverter_allows_contactor_closing = false; // The inverter needs to allow first
@ -342,4 +266,3 @@ void setup_inverter(void) { // Performs one time setup at startup over CAN bus
digitalWrite(INVERTER_CONTACTOR_ENABLE_LED_PIN, LOW); // Turn LED off, until inverter allows contactor closing
#endif // INVERTER_CONTACTOR_ENABLE_LED_PIN
}
#endif

View file

@ -2,13 +2,102 @@
#define SMA_BYD_HVS_CAN_H
#include "../include.h"
#include "CanInverterProtocol.h"
#ifdef SMA_BYD_HVS_CAN
#define CAN_INVERTER_SELECTED
#define SELECTED_INVERTER_CLASS SmaBydHvsInverter
#endif
#define READY_STATE 0x03
#define STOP_STATE 0x02
class SmaBydHvsInverter : public CanInverterProtocol {
public:
void setup();
void update_values();
void transmit_can(unsigned long currentMillis);
void map_can_frame_to_variable(CAN_frame rx_frame);
void transmit_can_frame(CAN_frame* tx_frame, int interface);
void transmit_can_init();
void setup_inverter(void);
private:
static const int READY_STATE = 0x03;
static const int STOP_STATE = 0x02;
static const int THIRTY_MINUTES = 1200;
void transmit_can_init();
unsigned long previousMillis100ms = 0;
uint32_t inverter_time = 0;
uint16_t inverter_voltage = 0;
int16_t inverter_current = 0;
uint16_t timeWithoutInverterAllowsContactorClosing = 0;
//Actual content messages
CAN_frame SMA_158 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x158, // All 0xAA, no faults active
.data = {0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}};
CAN_frame SMA_358 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x358,
.data = {0x11, 0xA0, 0x07, 0x00, 0x01, 0x5E, 0x00, 0xC8}};
CAN_frame SMA_3D8 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x3D8,
.data = {0x13, 0x2E, 0x27, 0x10, 0x00, 0x45, 0xF9, 0x00}};
CAN_frame SMA_458 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x458,
.data = {0x00, 0x00, 0x11, 0xC8, 0x00, 0x00, 0x0E, 0xF4}};
CAN_frame SMA_4D8 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x4D8,
.data = {0x10, 0x62, 0x00, 0x16, 0x01, 0x68, 0x03, 0x08}};
CAN_frame SMA_518 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x518,
.data = {0x01, 0x4A, 0x01, 0x25, 0xFF, 0xFF, 0xFF, 0xFF}};
// Pairing/Battery setup information
CAN_frame SMA_558 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x558,
.data = {0x03, 0x13, 0x00, 0x03, 0x00, 0x66, 0x04, 0x07}}; //4x BYD modules, Vendor ID 7 BYD
CAN_frame SMA_598 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x598,
.data = {0x00, 0x01, 0x0F, 0x2C, 0x5C, 0x98, 0xB6, 0xEE}};
CAN_frame SMA_5D8 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x5D8,
.data = {0x00, 0x42, 0x59, 0x44, 0x00, 0x00, 0x00, 0x00}}; //B Y D
CAN_frame SMA_618_1 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x618,
.data = {0x00, 0x42, 0x61, 0x74, 0x74, 0x65, 0x72, 0x79}}; //0 B A T T E R Y
CAN_frame SMA_618_2 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x618,
.data = {0x01, 0x2D, 0x42, 0x6F, 0x78, 0x20, 0x48, 0x31}}; //- B o x H 1
CAN_frame SMA_618_3 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x618,
.data = {0x02, 0x30, 0x2E, 0x32, 0x00, 0x00, 0x00, 0x00}}; // 0 . 2
int16_t discharge_current = 0;
int16_t charge_current = 0;
int16_t temperature_average = 0;
uint16_t ampere_hours_remaining = 0;
};
#endif

View file

@ -1,65 +1,15 @@
#include "../include.h"
#ifdef SMA_LV_CAN
#include "../datalayer/datalayer.h"
#include "SMA-LV-CAN.h"
#include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h"
#include "../include.h"
/* SMA Sunny Island Low Voltage (48V) CAN protocol:
CAN 2.0A
500kBit/sec
11-Bit Identifiers */
/* Do not change code below unless you are sure what you are doing */
static unsigned long previousMillis100ms = 0;
#define VOLTAGE_OFFSET_DV 40 //Offset in deciVolt from max charge voltage and min discharge voltage
#define MAX_VOLTAGE_DV 630
#define MIN_VOLTAGE_DV 41
//Actual content messages
CAN_frame SMA_00F = {.FD = false, // Emergency stop message
.ext_ID = false,
.DLC = 8, //Documentation unclear, should message even have any content?
.ID = 0x00F,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SMA_351 = {.FD = false, // Battery charge voltage, charge/discharge limit, min discharge voltage
.ext_ID = false,
.DLC = 8,
.ID = 0x351,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SMA_355 = {.FD = false, // SOC, SOH, HiResSOC
.ext_ID = false,
.DLC = 8,
.ID = 0x355,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SMA_356 = {.FD = false, // Battery voltage, current, temperature
.ext_ID = false,
.DLC = 8,
.ID = 0x356,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SMA_35A = {.FD = false, // Alarms & Warnings
.ext_ID = false,
.DLC = 8,
.ID = 0x35A,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SMA_35B = {.FD = false, // Events
.ext_ID = false,
.DLC = 8,
.ID = 0x35B,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SMA_35E = {.FD = false, // Manufacturer ASCII
.ext_ID = false,
.DLC = 8,
.ID = 0x35E,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SMA_35F = {.FD = false, // Battery Type, version, capacity, ID
.ext_ID = false,
.DLC = 8,
.ID = 0x35F,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
static int16_t temperature_average = 0;
void update_values_can_inverter() { //This function maps all the values fetched from battery CAN to the correct CAN messages
void SmaLvInverter::
update_values() { //This function maps all the values fetched from battery CAN to the correct CAN messages
// Update values
temperature_average =
((datalayer.battery.status.temperature_max_dC + datalayer.battery.status.temperature_min_dC) / 2);
@ -114,7 +64,7 @@ void update_values_can_inverter() { //This function maps all the values fetched
//TODO: Map error/warnings in 0x35A
}
void map_can_frame_to_variable_inverter(CAN_frame rx_frame) {
void SmaLvInverter::map_can_frame_to_variable(CAN_frame rx_frame) {
switch (rx_frame.ID) {
case 0x305:
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
@ -136,7 +86,7 @@ void map_can_frame_to_variable_inverter(CAN_frame rx_frame) {
}
}
void transmit_can_inverter(unsigned long currentMillis) {
void SmaLvInverter::transmit_can(unsigned long currentMillis) {
if (currentMillis - previousMillis100ms >= INTERVAL_100_MS) {
previousMillis100ms = currentMillis;
@ -158,8 +108,7 @@ void transmit_can_inverter(unsigned long currentMillis) {
}
}
void setup_inverter(void) { // Performs one time setup at startup over CAN bus
void SmaLvInverter::setup(void) { // Performs one time setup at startup over CAN bus
strncpy(datalayer.system.info.inverter_protocol, "SMA Low Voltage (48V) protocol via CAN", 63);
datalayer.system.info.inverter_protocol[63] = '\0';
}
#endif

View file

@ -2,12 +2,73 @@
#define SMA_LV_CAN_H
#include "../include.h"
#include "CanInverterProtocol.h"
#ifdef SMA_LV_CAN
#define CAN_INVERTER_SELECTED
#define SELECTED_INVERTER_CLASS SmaLvInverter
#endif
#define READY_STATE 0x03
#define STOP_STATE 0x02
class SmaLvInverter : public CanInverterProtocol {
public:
void setup();
void update_values();
void transmit_can(unsigned long currentMillis);
void map_can_frame_to_variable(CAN_frame rx_frame);
void transmit_can_frame(CAN_frame* tx_frame, int interface);
void setup_inverter(void);
private:
static const int READY_STATE = 0x03;
static const int STOP_STATE = 0x02;
unsigned long previousMillis100ms = 0;
static const int VOLTAGE_OFFSET_DV = 40; //Offset in deciVolt from max charge voltage and min discharge voltage
static const int MAX_VOLTAGE_DV = 630;
static const int MIN_VOLTAGE_DV = 41;
//Actual content messages
CAN_frame SMA_00F = {.FD = false, // Emergency stop message
.ext_ID = false,
.DLC = 8, //Documentation unclear, should message even have any content?
.ID = 0x00F,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SMA_351 = {.FD = false, // Battery charge voltage, charge/discharge limit, min discharge voltage
.ext_ID = false,
.DLC = 8,
.ID = 0x351,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SMA_355 = {.FD = false, // SOC, SOH, HiResSOC
.ext_ID = false,
.DLC = 8,
.ID = 0x355,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SMA_356 = {.FD = false, // Battery voltage, current, temperature
.ext_ID = false,
.DLC = 8,
.ID = 0x356,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SMA_35A = {.FD = false, // Alarms & Warnings
.ext_ID = false,
.DLC = 8,
.ID = 0x35A,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SMA_35B = {.FD = false, // Events
.ext_ID = false,
.DLC = 8,
.ID = 0x35B,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SMA_35E = {.FD = false, // Manufacturer ASCII
.ext_ID = false,
.DLC = 8,
.ID = 0x35E,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SMA_35F = {.FD = false, // Battery Type, version, capacity, ID
.ext_ID = false,
.DLC = 8,
.ID = 0x35F,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
int16_t temperature_average = 0;
};
#endif

View file

@ -1,8 +1,8 @@
#include "../include.h"
#ifdef SMA_TRIPOWER_CAN
#include "SMA-TRIPOWER-CAN.h"
#include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h"
#include "../devboard/utils/events.h"
#include "SMA-TRIPOWER-CAN.h"
#include "../include.h"
/* TODO:
- Figure out the manufacturer info needed in transmit_can_init() CAN messages
@ -12,93 +12,8 @@
- Figure out how to send the non-cyclic messages when needed
*/
/* Do not change code below unless you are sure what you are doing */
static unsigned long previousMillis250ms = 0; // will store last time a 250ms CAN Message was send
static unsigned long previousMillis500ms = 0; // will store last time a 500ms CAN Message was send
static unsigned long previousMillis2s = 0; // will store last time a 2s CAN Message was send
static unsigned long previousMillis10s = 0; // will store last time a 10s CAN Message was send
static unsigned long previousMillis60s = 0; // will store last time a 60s CAN Message was send
typedef struct {
CAN_frame* frame;
void (*callback)();
} Frame;
static unsigned short listLength = 0;
static Frame framesToSend[20];
static uint32_t inverter_time = 0;
static uint16_t inverter_voltage = 0;
static int16_t inverter_current = 0;
static bool pairing_completed = false;
static int16_t temperature_average = 0;
static uint16_t ampere_hours_remaining = 0;
static uint16_t timeWithoutInverterAllowsContactorClosing = 0;
#define THIRTY_MINUTES 1200
//Actual content messages
CAN_frame SMA_358 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x358,
.data = {0x12, 0x40, 0x0C, 0x80, 0x01, 0x00, 0x01, 0x00}};
CAN_frame SMA_3D8 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x3D8,
.data = {0x04, 0x06, 0x27, 0x10, 0x00, 0x19, 0x00, 0xFA}};
CAN_frame SMA_458 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x458,
.data = {0x00, 0x00, 0x73, 0xAE, 0x00, 0x00, 0x64, 0x64}};
CAN_frame SMA_4D8 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x4D8,
.data = {0x10, 0x62, 0x00, 0x00, 0x00, 0x78, 0x02, 0x08}};
CAN_frame SMA_518 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x518,
.data = {0x00, 0x96, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SMA_558 = {.FD = false, //Pairing first message
.ext_ID = false,
.DLC = 8,
.ID = 0x558, // BYD HVS 10.2 kWh (0x66 might be kWh)
.data = {0x03, 0x24, 0x00, 0x04, 0x00, 0x66, 0x04, 0x09}}; //Amount of modules? Vendor ID?
CAN_frame SMA_598 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x598,
.data = {0x12, 0xD6, 0x43, 0xA4, 0x00, 0x00, 0x00, 0x00}}; //B0-4 Serial 301100932
CAN_frame SMA_5D8 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x5D8,
.data = {0x00, 0x42, 0x59, 0x44, 0x00, 0x00, 0x00, 0x00}}; //B Y D
CAN_frame SMA_618_0 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x618,
.data = {0x00, 0x42, 0x61, 0x74, 0x74, 0x65, 0x72, 0x79}}; //BATTERY
CAN_frame SMA_618_1 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x618,
.data = {0x01, 0x2D, 0x42, 0x6F, 0x78, 0x20, 0x50, 0x72}}; //-Box Pr
CAN_frame SMA_618_2 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x618,
.data = {0x02, 0x65, 0x6D, 0x69, 0x75, 0x6D, 0x20, 0x48}}; //emium H
CAN_frame SMA_618_3 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x618,
.data = {0x03, 0x56, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00}}; //VS
void update_values_can_inverter() { //This function maps all the values fetched from battery CAN to the inverter CAN
void SmaTripowerInverter::
update_values() { //This function maps all the values fetched from battery CAN to the inverter CAN
// Update values
temperature_average =
((datalayer.battery.status.temperature_max_dC + datalayer.battery.status.temperature_min_dC) / 2);
@ -162,7 +77,7 @@ void update_values_can_inverter() { //This function maps all the values fetched
}
}
void map_can_frame_to_variable_inverter(CAN_frame rx_frame) {
void SmaTripowerInverter::map_can_frame_to_variable(CAN_frame rx_frame) {
switch (rx_frame.ID) {
case 0x360: //Message originating from SMA inverter - Voltage and current
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
@ -193,7 +108,7 @@ void map_can_frame_to_variable_inverter(CAN_frame rx_frame) {
}
}
void pushFrame(CAN_frame* frame, void (*callback)() = NULL) {
void SmaTripowerInverter::pushFrame(CAN_frame* frame, std::function<void(void)> callback) {
if (listLength >= 20) {
return; //TODO: scream.
}
@ -204,7 +119,7 @@ void pushFrame(CAN_frame* frame, void (*callback)() = NULL) {
listLength++;
}
void transmit_can_inverter(unsigned long currentMillis) {
void SmaTripowerInverter::transmit_can(unsigned long currentMillis) {
// Send CAN Message only if we're enabled by inverter
if (!datalayer.system.status.inverter_allows_contactor_closing) {
@ -216,9 +131,7 @@ void transmit_can_inverter(unsigned long currentMillis) {
// Send next frame.
Frame frame = framesToSend[0];
transmit_can_frame(frame.frame, can_config.inverter);
if (frame.callback != NULL) {
frame.callback();
}
frame.callback();
for (int i = 0; i < listLength - 1; i++) {
framesToSend[i] = framesToSend[i + 1];
}
@ -243,11 +156,11 @@ void transmit_can_inverter(unsigned long currentMillis) {
}
}
void completePairing() {
void SmaTripowerInverter::completePairing() {
pairing_completed = true;
}
void transmit_can_init() {
void SmaTripowerInverter::transmit_can_init() {
listLength = 0; // clear all frames
pushFrame(&SMA_558); //Pairing start - Vendor
@ -261,10 +174,10 @@ void transmit_can_init() {
pushFrame(&SMA_3D8);
pushFrame(&SMA_458);
pushFrame(&SMA_4D8);
pushFrame(&SMA_518, completePairing);
pushFrame(&SMA_518, [this]() { this->completePairing(); });
}
void setup_inverter(void) { // Performs one time setup at startup over CAN bus
void SmaTripowerInverter::setup(void) { // Performs one time setup at startup over CAN bus
strncpy(datalayer.system.info.inverter_protocol, "SMA Tripower CAN", 63);
datalayer.system.info.inverter_protocol[63] = '\0';
datalayer.system.status.inverter_allows_contactor_closing = false; // The inverter needs to allow first
@ -274,5 +187,3 @@ void setup_inverter(void) { // Performs one time setup at startup over CAN bus
digitalWrite(INVERTER_CONTACTOR_ENABLE_LED_PIN, LOW); // Turn LED off, until inverter allows contactor closing
#endif // INVERTER_CONTACTOR_ENABLE_LED_PIN
}
#endif

View file

@ -2,13 +2,112 @@
#define SMA_CAN_TRIPOWER_H
#include "../include.h"
#include "CanInverterProtocol.h"
#ifdef SMA_TRIPOWER_CAN
#define CAN_INVERTER_SELECTED
#define SELECTED_INVERTER_CLASS SmaTripowerInverter
#endif
#define READY_STATE 0x03
#define STOP_STATE 0x02
class SmaTripowerInverter : public CanInverterProtocol {
public:
void setup();
void update_values();
void transmit_can(unsigned long currentMillis);
void map_can_frame_to_variable(CAN_frame rx_frame);
void transmit_can_frame(CAN_frame* tx_frame, int interface);
void transmit_can_init();
void setup_inverter(void);
private:
const int READY_STATE = 0x03;
const int STOP_STATE = 0x02;
const int THIRTY_MINUTES = 1200;
void transmit_can_init();
void pushFrame(CAN_frame* frame, std::function<void(void)> callback = []() {});
void completePairing();
unsigned long previousMillis250ms = 0; // will store last time a 250ms CAN Message was send
unsigned long previousMillis500ms = 0; // will store last time a 500ms CAN Message was send
unsigned long previousMillis2s = 0; // will store last time a 2s CAN Message was send
unsigned long previousMillis10s = 0; // will store last time a 10s CAN Message was send
unsigned long previousMillis60s = 0; // will store last time a 60s CAN Message was send
typedef struct {
CAN_frame* frame;
std::function<void(void)> callback;
} Frame;
unsigned short listLength = 0;
Frame framesToSend[20];
uint32_t inverter_time = 0;
uint16_t inverter_voltage = 0;
int16_t inverter_current = 0;
bool pairing_completed = false;
int16_t temperature_average = 0;
uint16_t ampere_hours_remaining = 0;
uint16_t timeWithoutInverterAllowsContactorClosing = 0;
//Actual content messages
CAN_frame SMA_358 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x358,
.data = {0x12, 0x40, 0x0C, 0x80, 0x01, 0x00, 0x01, 0x00}};
CAN_frame SMA_3D8 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x3D8,
.data = {0x04, 0x06, 0x27, 0x10, 0x00, 0x19, 0x00, 0xFA}};
CAN_frame SMA_458 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x458,
.data = {0x00, 0x00, 0x73, 0xAE, 0x00, 0x00, 0x64, 0x64}};
CAN_frame SMA_4D8 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x4D8,
.data = {0x10, 0x62, 0x00, 0x00, 0x00, 0x78, 0x02, 0x08}};
CAN_frame SMA_518 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x518,
.data = {0x00, 0x96, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SMA_558 = {.FD = false, //Pairing first message
.ext_ID = false,
.DLC = 8,
.ID = 0x558, // BYD HVS 10.2 kWh (0x66 might be kWh)
.data = {0x03, 0x24, 0x00, 0x04, 0x00, 0x66, 0x04, 0x09}}; //Amount of modules? Vendor ID?
CAN_frame SMA_598 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x598,
.data = {0x12, 0xD6, 0x43, 0xA4, 0x00, 0x00, 0x00, 0x00}}; //B0-4 Serial 301100932
CAN_frame SMA_5D8 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x5D8,
.data = {0x00, 0x42, 0x59, 0x44, 0x00, 0x00, 0x00, 0x00}}; //B Y D
CAN_frame SMA_618_0 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x618,
.data = {0x00, 0x42, 0x61, 0x74, 0x74, 0x65, 0x72, 0x79}}; //BATTERY
CAN_frame SMA_618_1 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x618,
.data = {0x01, 0x2D, 0x42, 0x6F, 0x78, 0x20, 0x50, 0x72}}; //-Box Pr
CAN_frame SMA_618_2 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x618,
.data = {0x02, 0x65, 0x6D, 0x69, 0x75, 0x6D, 0x20, 0x48}}; //emium H
CAN_frame SMA_618_3 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x618,
.data = {0x03, 0x56, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00}}; //VS
};
#endif

View file

@ -1,208 +1,12 @@
#include "../include.h"
#ifdef SOFAR_CAN
#include "../datalayer/datalayer.h"
#include "SOFAR-CAN.h"
#include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h"
#include "../include.h"
/* This implementation of the SOFAR can protocol is halfway done. What's missing is implementing the inverter replies, all the CAN messages are listed, but the can sending is missing. */
/* Do not change code below unless you are sure what you are doing */
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
//Actual content messages
//Note that these are technically extended frames. If more batteries are put in parallel,the first battery sends 0x351 the next battery sends 0x1351 etc. 16 batteries in parallel supported
CAN_frame SOFAR_351 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x351,
.data = {0xC6, 0x08, 0xFA, 0x00, 0xFA, 0x00, 0x80, 0x07}};
CAN_frame SOFAR_355 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x355,
.data = {0x31, 0x00, 0x64, 0x00, 0xFF, 0xFF, 0xF6, 0x00}};
CAN_frame SOFAR_356 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x356,
.data = {0x36, 0x08, 0x10, 0x00, 0xD0, 0x00, 0x01, 0x00}};
CAN_frame SOFAR_30F = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x30F,
.data = {0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SOFAR_359 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x359,
.data = {0x00, 0x00, 0x00, 0x00, 0x04, 0x10, 0x27, 0x10}};
CAN_frame SOFAR_35E = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x35E,
.data = {0x41, 0x4D, 0x41, 0x53, 0x53, 0x00, 0x00, 0x00}};
CAN_frame SOFAR_35F = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x35F,
.data = {0x00, 0x00, 0x24, 0x4E, 0x32, 0x00, 0x00, 0x00}};
CAN_frame SOFAR_35A = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x35A,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SOFAR_670 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x670,
.data = {0x00, 0x8A, 0x33, 0x11, 0x59, 0x1A, 0x00, 0x00}};
CAN_frame SOFAR_671 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x671,
.data = {0x00, 0x42, 0x48, 0x55, 0x35, 0x31, 0x32, 0x30}};
CAN_frame SOFAR_672 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x672,
.data = {0x00, 0x32, 0x35, 0x45, 0x50, 0x43, 0x32, 0x31}};
CAN_frame SOFAR_673 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x673,
.data = {0x00, 0x34, 0x32, 0x36, 0x31, 0x36, 0x32, 0x00}};
CAN_frame SOFAR_680 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x680,
.data = {0x00, 0xB7, 0x0C, 0xB3, 0x0C, 0xB4, 0x0C, 0x00}};
CAN_frame SOFAR_681 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x681,
.data = {0x00, 0xB6, 0x0C, 0xB3, 0x0C, 0xB4, 0x0C, 0x00}};
CAN_frame SOFAR_682 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x682,
.data = {0x00, 0xB6, 0x0C, 0xB3, 0x0C, 0xB4, 0x0C, 0x00}};
CAN_frame SOFAR_683 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x683,
.data = {0x00, 0xB6, 0x0C, 0xB3, 0x0C, 0xB4, 0x0C, 0x00}};
CAN_frame SOFAR_684 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x684,
.data = {0x00, 0xB6, 0x0C, 0xB3, 0x0C, 0xB4, 0x0C, 0x00}};
CAN_frame SOFAR_685 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x685,
.data = {0x00, 0xB3, 0x0C, 0xBB, 0x0C, 0xB3, 0x0C, 0x00}};
CAN_frame SOFAR_690 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x690,
.data = {0x00, 0xD7, 0x00, 0xD4, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SOFAR_691 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x691,
.data = {0x00, 0xD4, 0x00, 0xD1, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SOFAR_6A0 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x6A0,
.data = {0x00, 0xFA, 0x00, 0xDD, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SOFAR_6B0 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x6B0,
.data = {0x00, 0xF6, 0x00, 0x06, 0x02, 0x01, 0x00, 0x00}};
CAN_frame SOFAR_6C0 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x6C0,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SOFAR_770 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x770,
.data = {0x00, 0x56, 0x0B, 0xF0, 0x58, 0x00, 0x00, 0x00}};
CAN_frame SOFAR_771 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x771,
.data = {0x00, 0x42, 0x48, 0x55, 0x35, 0x31, 0x32, 0x30}};
CAN_frame SOFAR_772 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x772,
.data = {0x00, 0x32, 0x35, 0x45, 0x50, 0x43, 0x32, 0x31}};
CAN_frame SOFAR_773 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x773,
.data = {0x00, 0x34, 0x32, 0x36, 0x31, 0x36, 0x32, 0x00}};
CAN_frame SOFAR_780 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x780,
.data = {0x00, 0xEB, 0x0C, 0xED, 0x0C, 0xED, 0x0C, 0x00}};
CAN_frame SOFAR_781 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x781,
.data = {0x00, 0xEB, 0x0C, 0xED, 0x0C, 0xED, 0x0C, 0x00}};
CAN_frame SOFAR_782 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x782,
.data = {0x00, 0xEB, 0x0C, 0xED, 0x0C, 0xED, 0x0C, 0x00}};
CAN_frame SOFAR_783 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x783,
.data = {0x00, 0xEB, 0x0C, 0xED, 0x0C, 0xED, 0x0C, 0x00}};
CAN_frame SOFAR_784 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x784,
.data = {0x00, 0xEB, 0x0C, 0xED, 0x0C, 0xED, 0x0C, 0x00}};
CAN_frame SOFAR_785 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x785,
.data = {0x00, 0xEB, 0x0C, 0xED, 0x0C, 0xED, 0x0C, 0x00}};
CAN_frame SOFAR_790 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x790,
.data = {0x00, 0xCD, 0x00, 0xCF, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SOFAR_791 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x791,
.data = {0x00, 0xCD, 0x00, 0xCF, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SOFAR_7A0 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x7A0,
.data = {0x00, 0xFA, 0x00, 0xE1, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SOFAR_7B0 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x7B0,
.data = {0x00, 0xF9, 0x00, 0x06, 0x02, 0xE9, 0x5D, 0x00}};
CAN_frame SOFAR_7C0 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x7C0,
.data = {0x00, 0x00, 0x00, 0x04, 0x00, 0x04, 0x80, 0x00}};
void update_values_can_inverter() { //This function maps all the values fetched from battery CAN to the correct CAN messages
void SofarInverter::
update_values() { //This function maps all the values fetched from battery CAN to the correct CAN messages
//Maxvoltage (eg 400.0V = 4000 , 16bits long) Charge Cutoff Voltage
SOFAR_351.data.u8[0] = (datalayer.battery.info.max_design_voltage_dV >> 8);
@ -230,7 +34,7 @@ void update_values_can_inverter() { //This function maps all the values fetched
SOFAR_356.data.u8[3] = (datalayer.battery.status.temperature_max_dC & 0x00FF);
}
void map_can_frame_to_variable_inverter(CAN_frame rx_frame) {
void SofarInverter::map_can_frame_to_variable(CAN_frame rx_frame) {
switch (rx_frame.ID) { //In here we need to respond to the inverter. TODO: make logic
case 0x605:
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
@ -247,7 +51,7 @@ void map_can_frame_to_variable_inverter(CAN_frame rx_frame) {
}
}
void transmit_can_inverter(unsigned long currentMillis) {
void SofarInverter::transmit_can(unsigned long currentMillis) {
// Send 100ms CAN Message
if (currentMillis - previousMillis100 >= INTERVAL_100_MS) {
previousMillis100 = currentMillis;
@ -263,8 +67,7 @@ void transmit_can_inverter(unsigned long currentMillis) {
}
}
void setup_inverter(void) { // Performs one time setup at startup over CAN bus
void SofarInverter::setup(void) { // Performs one time setup at startup over CAN bus
strncpy(datalayer.system.info.inverter_protocol, "Sofar BMS (Extended Frame) over CAN bus", 63);
datalayer.system.info.inverter_protocol[63] = '\0';
}
#endif

View file

@ -2,9 +2,216 @@
#define SOFAR_CAN_H
#include "../include.h"
#define CAN_INVERTER_SELECTED
#include "CanInverterProtocol.h"
void transmit_can_frame(CAN_frame* tx_frame, int interface);
void setup_inverter(void);
#ifdef SOFAR_CAN
#define CAN_INVERTER_SELECTED
#define SELECTED_INVERTER_CLASS SofarInverter
#endif
class SofarInverter : public CanInverterProtocol {
public:
void setup();
void update_values();
void transmit_can(unsigned long currentMillis);
void map_can_frame_to_variable(CAN_frame rx_frame);
private:
unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
//Actual content messages
//Note that these are technically extended frames. If more batteries are put in parallel,the first battery sends 0x351 the next battery sends 0x1351 etc. 16 batteries in parallel supported
CAN_frame SOFAR_351 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x351,
.data = {0xC6, 0x08, 0xFA, 0x00, 0xFA, 0x00, 0x80, 0x07}};
CAN_frame SOFAR_355 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x355,
.data = {0x31, 0x00, 0x64, 0x00, 0xFF, 0xFF, 0xF6, 0x00}};
CAN_frame SOFAR_356 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x356,
.data = {0x36, 0x08, 0x10, 0x00, 0xD0, 0x00, 0x01, 0x00}};
CAN_frame SOFAR_30F = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x30F,
.data = {0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SOFAR_359 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x359,
.data = {0x00, 0x00, 0x00, 0x00, 0x04, 0x10, 0x27, 0x10}};
CAN_frame SOFAR_35E = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x35E,
.data = {0x41, 0x4D, 0x41, 0x53, 0x53, 0x00, 0x00, 0x00}};
CAN_frame SOFAR_35F = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x35F,
.data = {0x00, 0x00, 0x24, 0x4E, 0x32, 0x00, 0x00, 0x00}};
CAN_frame SOFAR_35A = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x35A,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SOFAR_670 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x670,
.data = {0x00, 0x8A, 0x33, 0x11, 0x59, 0x1A, 0x00, 0x00}};
CAN_frame SOFAR_671 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x671,
.data = {0x00, 0x42, 0x48, 0x55, 0x35, 0x31, 0x32, 0x30}};
CAN_frame SOFAR_672 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x672,
.data = {0x00, 0x32, 0x35, 0x45, 0x50, 0x43, 0x32, 0x31}};
CAN_frame SOFAR_673 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x673,
.data = {0x00, 0x34, 0x32, 0x36, 0x31, 0x36, 0x32, 0x00}};
CAN_frame SOFAR_680 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x680,
.data = {0x00, 0xB7, 0x0C, 0xB3, 0x0C, 0xB4, 0x0C, 0x00}};
CAN_frame SOFAR_681 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x681,
.data = {0x00, 0xB6, 0x0C, 0xB3, 0x0C, 0xB4, 0x0C, 0x00}};
CAN_frame SOFAR_682 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x682,
.data = {0x00, 0xB6, 0x0C, 0xB3, 0x0C, 0xB4, 0x0C, 0x00}};
CAN_frame SOFAR_683 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x683,
.data = {0x00, 0xB6, 0x0C, 0xB3, 0x0C, 0xB4, 0x0C, 0x00}};
CAN_frame SOFAR_684 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x684,
.data = {0x00, 0xB6, 0x0C, 0xB3, 0x0C, 0xB4, 0x0C, 0x00}};
CAN_frame SOFAR_685 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x685,
.data = {0x00, 0xB3, 0x0C, 0xBB, 0x0C, 0xB3, 0x0C, 0x00}};
CAN_frame SOFAR_690 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x690,
.data = {0x00, 0xD7, 0x00, 0xD4, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SOFAR_691 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x691,
.data = {0x00, 0xD4, 0x00, 0xD1, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SOFAR_6A0 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x6A0,
.data = {0x00, 0xFA, 0x00, 0xDD, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SOFAR_6B0 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x6B0,
.data = {0x00, 0xF6, 0x00, 0x06, 0x02, 0x01, 0x00, 0x00}};
CAN_frame SOFAR_6C0 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x6C0,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SOFAR_770 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x770,
.data = {0x00, 0x56, 0x0B, 0xF0, 0x58, 0x00, 0x00, 0x00}};
CAN_frame SOFAR_771 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x771,
.data = {0x00, 0x42, 0x48, 0x55, 0x35, 0x31, 0x32, 0x30}};
CAN_frame SOFAR_772 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x772,
.data = {0x00, 0x32, 0x35, 0x45, 0x50, 0x43, 0x32, 0x31}};
CAN_frame SOFAR_773 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x773,
.data = {0x00, 0x34, 0x32, 0x36, 0x31, 0x36, 0x32, 0x00}};
CAN_frame SOFAR_780 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x780,
.data = {0x00, 0xEB, 0x0C, 0xED, 0x0C, 0xED, 0x0C, 0x00}};
CAN_frame SOFAR_781 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x781,
.data = {0x00, 0xEB, 0x0C, 0xED, 0x0C, 0xED, 0x0C, 0x00}};
CAN_frame SOFAR_782 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x782,
.data = {0x00, 0xEB, 0x0C, 0xED, 0x0C, 0xED, 0x0C, 0x00}};
CAN_frame SOFAR_783 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x783,
.data = {0x00, 0xEB, 0x0C, 0xED, 0x0C, 0xED, 0x0C, 0x00}};
CAN_frame SOFAR_784 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x784,
.data = {0x00, 0xEB, 0x0C, 0xED, 0x0C, 0xED, 0x0C, 0x00}};
CAN_frame SOFAR_785 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x785,
.data = {0x00, 0xEB, 0x0C, 0xED, 0x0C, 0xED, 0x0C, 0x00}};
CAN_frame SOFAR_790 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x790,
.data = {0x00, 0xCD, 0x00, 0xCF, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SOFAR_791 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x791,
.data = {0x00, 0xCD, 0x00, 0xCF, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SOFAR_7A0 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x7A0,
.data = {0x00, 0xFA, 0x00, 0xE1, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SOFAR_7B0 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x7B0,
.data = {0x00, 0xF9, 0x00, 0x06, 0x02, 0xE9, 0x5D, 0x00}};
CAN_frame SOFAR_7C0 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x7C0,
.data = {0x00, 0x00, 0x00, 0x04, 0x00, 0x04, 0x80, 0x00}};
};
#endif

View file

@ -1,112 +1,21 @@
#include "../include.h"
#ifdef SOLAX_CAN
#include "SOLAX-CAN.h"
#include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h"
#include "../devboard/utils/events.h"
#include "SOLAX-CAN.h"
#include "../include.h"
#define NUMBER_OF_MODULES 0
#define BATTERY_TYPE 0x50
// If you are having BattVoltFault issues, configure the above values according to wiki page
// https://github.com/dalathegreat/Battery-Emulator/wiki/Solax-inverters
/* Do not change code below unless you are sure what you are doing */
static int16_t temperature_average = 0;
static uint8_t STATE = BATTERY_ANNOUNCE;
static unsigned long LastFrameTime = 0;
static uint8_t number_of_batteries = 1;
static uint16_t capped_capacity_Wh;
static uint16_t capped_remaining_capacity_Wh;
//CAN message translations from this amazing repository: https://github.com/rand12345/solax_can_bus
CAN_frame SOLAX_1801 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x1801,
.data = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}};
CAN_frame SOLAX_1872 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x1872,
.data = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}; //BMS_Limits
CAN_frame SOLAX_1873 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x1873,
.data = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}; //BMS_PackData
CAN_frame SOLAX_1874 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x1874,
.data = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}; //BMS_CellData
CAN_frame SOLAX_1875 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x1875,
.data = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}; //BMS_Status
CAN_frame SOLAX_1876 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x1876,
.data = {0x0, 0x0, 0xE2, 0x0C, 0x0, 0x0, 0xD7, 0x0C}}; //BMS_PackTemps
CAN_frame SOLAX_1877 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x1877,
.data = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}};
CAN_frame SOLAX_1878 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x1878,
.data = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}; //BMS_PackStats
CAN_frame SOLAX_1879 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x1879,
.data = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}};
CAN_frame SOLAX_187E = {.FD = false, //Needed for Ultra
.ext_ID = true,
.DLC = 8,
.ID = 0x187E,
.data = {0x60, 0xEA, 0x0, 0x0, 0x64, 0x0, 0x0, 0x0}};
CAN_frame SOLAX_187D = {.FD = false, //Needed for Ultra
.ext_ID = true,
.DLC = 8,
.ID = 0x187D,
.data = {0x8B, 0x01, 0x0, 0x0, 0x8B, 0x1, 0x0, 0x0}};
CAN_frame SOLAX_187C = {.FD = false, //Needed for Ultra
.ext_ID = true,
.DLC = 8,
.ID = 0x187C,
.data = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}};
CAN_frame SOLAX_187B = {.FD = false, //Needed for Ultra
.ext_ID = true,
.DLC = 8,
.ID = 0x187B,
.data = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}};
CAN_frame SOLAX_187A = {.FD = false, //Needed for Ultra
.ext_ID = true,
.DLC = 8,
.ID = 0x187A,
.data = {0x01, 0x50, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}};
CAN_frame SOLAX_1881 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x1881,
.data = {0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}; // E.g.: 0 6 S B M S F A
CAN_frame SOLAX_1882 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x1882,
.data = {0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}; // E.g.: 0 2 3 A B 0 5 2
CAN_frame SOLAX_100A001 = {.FD = false, .ext_ID = true, .DLC = 0, .ID = 0x100A001, .data = {}};
// __builtin_bswap64 needed to convert to ESP32 little endian format
// Byte[4] defines the requested contactor state: 1 = Closed , 0 = Open
#define Contactor_Open_Payload __builtin_bswap64(0x0200010000000000)
#define Contactor_Close_Payload __builtin_bswap64(0x0200010001000000)
void update_values_can_inverter() { //This function maps all the values fetched from battery CAN to the correct CAN messages
void SolaxInverter::
update_values() { //This function maps all the values fetched from battery CAN to the correct CAN messages
// If not receiveing any communication from the inverter, open contactors and return to battery announce state
if (millis() - LastFrameTime >= SolaxTimeout) {
datalayer.system.status.inverter_allows_contactor_closing = false;
@ -206,11 +115,11 @@ void update_values_can_inverter() { //This function maps all the values fetched
SOLAX_187E.data.u8[5] = (uint8_t)(datalayer.battery.status.reported_soc / 100);
}
void transmit_can_inverter(unsigned long currentMillis) {
void SolaxInverter::transmit_can(unsigned long currentMillis) {
// No periodic sending used on this protocol, we react only on incoming CAN messages!
}
void map_can_frame_to_variable_inverter(CAN_frame rx_frame) {
void SolaxInverter::map_can_frame_to_variable(CAN_frame rx_frame) {
if (rx_frame.ID == 0x1871) {
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
@ -297,9 +206,9 @@ void map_can_frame_to_variable_inverter(CAN_frame rx_frame) {
#endif
}
}
void setup_inverter(void) { // Performs one time setup at startup
void SolaxInverter::setup(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.inverter_protocol, "SolaX Triple Power LFP over CAN bus", 63);
datalayer.system.info.inverter_protocol[63] = '\0';
datalayer.system.status.inverter_allows_contactor_closing = false; // The inverter needs to allow first
}
#endif

View file

@ -2,19 +2,121 @@
#define SOLAX_CAN_H
#include "../include.h"
#include "CanInverterProtocol.h"
#ifdef SOLAX_CAN
#define CAN_INVERTER_SELECTED
#define SELECTED_INVERTER_CLASS SolaxInverter
#endif
// Timeout in milliseconds
#define SolaxTimeout 2000
class SolaxInverter : public CanInverterProtocol {
public:
void setup();
void update_values();
void transmit_can(unsigned long currentMillis);
void map_can_frame_to_variable(CAN_frame rx_frame);
//SOLAX BMS States Definition
#define BATTERY_ANNOUNCE 0
#define WAITING_FOR_CONTACTOR 1
#define CONTACTOR_CLOSED 2
#define FAULT_SOLAX 3
#define UPDATING_FW 4
private:
// Timeout in milliseconds
static const int SolaxTimeout = 2000;
void transmit_can_frame(CAN_frame* tx_frame, int interface);
void setup_inverter(void);
//SOLAX BMS States Definition
static const int BATTERY_ANNOUNCE = 0;
static const int WAITING_FOR_CONTACTOR = 1;
static const int CONTACTOR_CLOSED = 2;
static const int FAULT_SOLAX = 3;
static const int UPDATING_FW = 4;
int16_t temperature_average = 0;
uint8_t STATE = BATTERY_ANNOUNCE;
unsigned long LastFrameTime = 0;
uint8_t number_of_batteries = 1;
uint16_t capped_capacity_Wh;
uint16_t capped_remaining_capacity_Wh;
//CAN message translations from this amazing repository: https://github.com/rand12345/solax_can_bus
CAN_frame SOLAX_1801 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x1801,
.data = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}};
CAN_frame SOLAX_1872 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x1872,
.data = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}; //BMS_Limits
CAN_frame SOLAX_1873 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x1873,
.data = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}; //BMS_PackData
CAN_frame SOLAX_1874 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x1874,
.data = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}; //BMS_CellData
CAN_frame SOLAX_1875 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x1875,
.data = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}; //BMS_Status
CAN_frame SOLAX_1876 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x1876,
.data = {0x0, 0x0, 0xE2, 0x0C, 0x0, 0x0, 0xD7, 0x0C}}; //BMS_PackTemps
CAN_frame SOLAX_1877 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x1877,
.data = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}};
CAN_frame SOLAX_1878 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x1878,
.data = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}; //BMS_PackStats
CAN_frame SOLAX_1879 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x1879,
.data = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}};
CAN_frame SOLAX_187E = {.FD = false, //Needed for Ultra
.ext_ID = true,
.DLC = 8,
.ID = 0x187E,
.data = {0x60, 0xEA, 0x0, 0x0, 0x64, 0x0, 0x0, 0x0}};
CAN_frame SOLAX_187D = {.FD = false, //Needed for Ultra
.ext_ID = true,
.DLC = 8,
.ID = 0x187D,
.data = {0x8B, 0x01, 0x0, 0x0, 0x8B, 0x1, 0x0, 0x0}};
CAN_frame SOLAX_187C = {.FD = false, //Needed for Ultra
.ext_ID = true,
.DLC = 8,
.ID = 0x187C,
.data = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}};
CAN_frame SOLAX_187B = {.FD = false, //Needed for Ultra
.ext_ID = true,
.DLC = 8,
.ID = 0x187B,
.data = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}};
CAN_frame SOLAX_187A = {.FD = false, //Needed for Ultra
.ext_ID = true,
.DLC = 8,
.ID = 0x187A,
.data = {0x01, 0x50, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}};
CAN_frame SOLAX_1881 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x1881,
.data = {0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}; // E.g.: 0 6 S B M S F A
CAN_frame SOLAX_1882 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x1882,
.data = {0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}; // E.g.: 0 2 3 A B 0 5 2
CAN_frame SOLAX_100A001 = {.FD = false, .ext_ID = true, .DLC = 0, .ID = 0x100A001, .data = {}};
};
#endif

View file

@ -1,259 +1,14 @@
#include "../include.h"
#ifdef SUNGROW_CAN
#include "../datalayer/datalayer.h"
#include "SUNGROW-CAN.h"
#include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h"
#include "../include.h"
/* TODO:
This protocol is still under development. It can not be used yet for Sungrow inverters,
see the Wiki for more info on how to use your Sungrow inverter */
/* Do not change code below unless you are sure what you are doing */
static unsigned long previousMillis500ms = 0;
static bool alternate = false;
static uint8_t mux = 0;
static uint8_t version_char[14] = {0};
static uint8_t manufacturer_char[14] = {0};
static uint8_t model_char[14] = {0};
static bool inverter_sends_000 = false;
//Actual content messages
CAN_frame SUNGROW_000 = {.FD = false, // Sent by inv or BMS?
.ext_ID = false,
.DLC = 8,
.ID = 0x000,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SUNGROW_001 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x001,
.data = {0xF0, 0x05, 0x20, 0x03, 0x2C, 0x01, 0x2C, 0x01}};
CAN_frame SUNGROW_002 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x002,
.data = {0xA2, 0x05, 0x10, 0x27, 0x9B, 0x03, 0x00, 0x19}};
CAN_frame SUNGROW_003 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x003,
.data = {0x2A, 0x1D, 0x00, 0x00, 0xBF, 0x18, 0x00, 0x00}};
CAN_frame SUNGROW_004 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x004,
.data = {0x27, 0x05, 0x00, 0x00, 0x24, 0x05, 0x08, 0x01}};
CAN_frame SUNGROW_005 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x005,
.data = {0x02, 0x00, 0x01, 0xE6, 0x20, 0x24, 0x05, 0x00}};
CAN_frame SUNGROW_006 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x006,
.data = {0x0E, 0x01, 0x01, 0x01, 0xDE, 0x0C, 0xD5, 0x0C}};
CAN_frame SUNGROW_013 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x013,
.data = {0x01, 0x01, 0x01, 0x02, 0x01, 0x02, 0x0E, 0x01}};
CAN_frame SUNGROW_014 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x014,
.data = {0x05, 0x01, 0xAC, 0x80, 0x10, 0x02, 0x57, 0x80}};
CAN_frame SUNGROW_015 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x015,
.data = {0x93, 0x80, 0xAC, 0x80, 0x57, 0x80, 0x93, 0x80}};
CAN_frame SUNGROW_016 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x016,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SUNGROW_017 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x017,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SUNGROW_018 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x018,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SUNGROW_019 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x019,
.data = {0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SUNGROW_01A = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x01A,
.data = {0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SUNGROW_01B = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x01B,
.data = {0xBE, 0x8F, 0x61, 0x01, 0xBE, 0x8F, 0x61, 0x01}};
CAN_frame SUNGROW_01C = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x01C,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SUNGROW_01D = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x01D,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SUNGROW_01E = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x01E,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SUNGROW_400 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x400,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SUNGROW_500 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x500,
.data = {0x01, 0x01, 0x00, 0xFF, 0x00, 0x01, 0x00, 0x32}};
CAN_frame SUNGROW_501 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x501,
.data = {0xF0, 0x05, 0x20, 0x03, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SUNGROW_502 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x502,
.data = {0xA2, 0x05, 0x00, 0x00, 0x9B, 0x03, 0x00, 0x19}};
CAN_frame SUNGROW_503 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x503,
.data = {0x2A, 0x1D, 0x00, 0x00, 0xBF, 0x18, 0x00, 0x00}};
CAN_frame SUNGROW_504 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x504,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SUNGROW_505 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x505,
.data = {0x00, 0x02, 0x01, 0xE6, 0x20, 0x00, 0x00, 0x00}};
CAN_frame SUNGROW_506 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x506,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SUNGROW_512 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x512,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SUNGROW_700 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x700,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SUNGROW_701 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x701,
.data = {0xF0, 0x05, 0x20, 0x03, 0x2C, 0x01, 0x2C, 0x01}};
CAN_frame SUNGROW_702 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x702,
.data = {0xA2, 0x05, 0x10, 0x27, 0x9B, 0x03, 0x00, 0x19}};
CAN_frame SUNGROW_703 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x703,
.data = {0x2A, 0x1D, 0x00, 0x00, 0xBF, 0x18, 0x00, 0x00}};
CAN_frame SUNGROW_704 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x704,
.data = {0x27, 0x05, 0x00, 0x00, 0x24, 0x05, 0x08, 0x01}};
CAN_frame SUNGROW_705 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x705,
.data = {0x02, 0x00, 0x01, 0xE6, 0x20, 0x24, 0x05, 0x00}};
CAN_frame SUNGROW_706 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x706,
.data = {0x0E, 0x01, 0x01, 0x01, 0xDE, 0x0C, 0xD5, 0x0C}};
CAN_frame SUNGROW_713 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x713,
.data = {0x01, 0x01, 0x01, 0x02, 0x01, 0x02, 0x0E, 0x01}};
CAN_frame SUNGROW_714 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x714,
.data = {0x05, 0x01, 0xAC, 0x80, 0x10, 0x02, 0x57, 0x80}};
CAN_frame SUNGROW_715 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x715,
.data = {0x93, 0x80, 0xAC, 0x80, 0x57, 0x80, 0x93, 0x80}};
CAN_frame SUNGROW_716 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x716,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SUNGROW_717 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x717,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SUNGROW_718 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x718,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SUNGROW_719 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x719,
.data = {0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SUNGROW_71A = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x71A,
.data = {0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SUNGROW_71B = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x71B,
.data = {0xBE, 0x8F, 0x61, 0x01, 0xBE, 0x8F, 0x61, 0x01}};
CAN_frame SUNGROW_71C = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x71C,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SUNGROW_71D = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x71D,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SUNGROW_71E = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x71E,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
void update_values_can_inverter() { //This function maps all the values fetched from battery CAN to the inverter CAN messages
void SungrowInverter::
update_values() { //This function maps all the values fetched from battery CAN to the inverter CAN messages
//Maxvoltage (eg 400.0V = 4000 , 16bits long)
SUNGROW_701.data.u8[0] = (datalayer.battery.info.max_design_voltage_dV & 0x00FF);
@ -411,7 +166,7 @@ void update_values_can_inverter() { //This function maps all the values fetched
#endif
}
void map_can_frame_to_variable_inverter(CAN_frame rx_frame) {
void SungrowInverter::map_can_frame_to_variable(CAN_frame rx_frame) {
switch (rx_frame.ID) { //In here we need to respond to the inverter
case 0x000:
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
@ -557,7 +312,7 @@ void map_can_frame_to_variable_inverter(CAN_frame rx_frame) {
}
}
void transmit_can_inverter(unsigned long currentMillis) {
void SungrowInverter::transmit_can(unsigned long currentMillis) {
// Send 1s CAN Message
if (currentMillis - previousMillis500ms >= INTERVAL_500_MS) {
previousMillis500ms = currentMillis;
@ -597,8 +352,8 @@ void transmit_can_inverter(unsigned long currentMillis) {
}
}
}
void setup_inverter(void) { // Performs one time setup at startup over CAN bus
void SungrowInverter::setup(void) { // Performs one time setup at startup over CAN bus
strncpy(datalayer.system.info.inverter_protocol, "Sungrow SBR064 battery over CAN bus", 63);
datalayer.system.info.inverter_protocol[63] = '\0';
}
#endif

View file

@ -2,9 +2,265 @@
#define SUNGROW_CAN_H
#include "../include.h"
#define CAN_INVERTER_SELECTED
#include "CanInverterProtocol.h"
void transmit_can_frame(CAN_frame* tx_frame, int interface);
void setup_inverter(void);
#ifdef SUNGROW_CAN
#define CAN_INVERTER_SELECTED
#define SELECTED_INVERTER_CLASS SungrowInverter
#endif
class SungrowInverter : public CanInverterProtocol {
public:
void setup();
void update_values();
void transmit_can(unsigned long currentMillis);
void map_can_frame_to_variable(CAN_frame rx_frame);
private:
unsigned long previousMillis500ms = 0;
bool alternate = false;
uint8_t mux = 0;
uint8_t version_char[14] = {0};
uint8_t manufacturer_char[14] = {0};
uint8_t model_char[14] = {0};
bool inverter_sends_000 = false;
//Actual content messages
CAN_frame SUNGROW_000 = {.FD = false, // Sent by inv or BMS?
.ext_ID = false,
.DLC = 8,
.ID = 0x000,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SUNGROW_001 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x001,
.data = {0xF0, 0x05, 0x20, 0x03, 0x2C, 0x01, 0x2C, 0x01}};
CAN_frame SUNGROW_002 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x002,
.data = {0xA2, 0x05, 0x10, 0x27, 0x9B, 0x03, 0x00, 0x19}};
CAN_frame SUNGROW_003 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x003,
.data = {0x2A, 0x1D, 0x00, 0x00, 0xBF, 0x18, 0x00, 0x00}};
CAN_frame SUNGROW_004 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x004,
.data = {0x27, 0x05, 0x00, 0x00, 0x24, 0x05, 0x08, 0x01}};
CAN_frame SUNGROW_005 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x005,
.data = {0x02, 0x00, 0x01, 0xE6, 0x20, 0x24, 0x05, 0x00}};
CAN_frame SUNGROW_006 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x006,
.data = {0x0E, 0x01, 0x01, 0x01, 0xDE, 0x0C, 0xD5, 0x0C}};
CAN_frame SUNGROW_013 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x013,
.data = {0x01, 0x01, 0x01, 0x02, 0x01, 0x02, 0x0E, 0x01}};
CAN_frame SUNGROW_014 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x014,
.data = {0x05, 0x01, 0xAC, 0x80, 0x10, 0x02, 0x57, 0x80}};
CAN_frame SUNGROW_015 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x015,
.data = {0x93, 0x80, 0xAC, 0x80, 0x57, 0x80, 0x93, 0x80}};
CAN_frame SUNGROW_016 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x016,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SUNGROW_017 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x017,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SUNGROW_018 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x018,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SUNGROW_019 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x019,
.data = {0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SUNGROW_01A = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x01A,
.data = {0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SUNGROW_01B = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x01B,
.data = {0xBE, 0x8F, 0x61, 0x01, 0xBE, 0x8F, 0x61, 0x01}};
CAN_frame SUNGROW_01C = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x01C,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SUNGROW_01D = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x01D,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SUNGROW_01E = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x01E,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SUNGROW_400 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x400,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SUNGROW_500 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x500,
.data = {0x01, 0x01, 0x00, 0xFF, 0x00, 0x01, 0x00, 0x32}};
CAN_frame SUNGROW_501 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x501,
.data = {0xF0, 0x05, 0x20, 0x03, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SUNGROW_502 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x502,
.data = {0xA2, 0x05, 0x00, 0x00, 0x9B, 0x03, 0x00, 0x19}};
CAN_frame SUNGROW_503 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x503,
.data = {0x2A, 0x1D, 0x00, 0x00, 0xBF, 0x18, 0x00, 0x00}};
CAN_frame SUNGROW_504 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x504,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SUNGROW_505 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x505,
.data = {0x00, 0x02, 0x01, 0xE6, 0x20, 0x00, 0x00, 0x00}};
CAN_frame SUNGROW_506 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x506,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SUNGROW_512 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x512,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SUNGROW_700 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x700,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SUNGROW_701 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x701,
.data = {0xF0, 0x05, 0x20, 0x03, 0x2C, 0x01, 0x2C, 0x01}};
CAN_frame SUNGROW_702 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x702,
.data = {0xA2, 0x05, 0x10, 0x27, 0x9B, 0x03, 0x00, 0x19}};
CAN_frame SUNGROW_703 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x703,
.data = {0x2A, 0x1D, 0x00, 0x00, 0xBF, 0x18, 0x00, 0x00}};
CAN_frame SUNGROW_704 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x704,
.data = {0x27, 0x05, 0x00, 0x00, 0x24, 0x05, 0x08, 0x01}};
CAN_frame SUNGROW_705 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x705,
.data = {0x02, 0x00, 0x01, 0xE6, 0x20, 0x24, 0x05, 0x00}};
CAN_frame SUNGROW_706 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x706,
.data = {0x0E, 0x01, 0x01, 0x01, 0xDE, 0x0C, 0xD5, 0x0C}};
CAN_frame SUNGROW_713 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x713,
.data = {0x01, 0x01, 0x01, 0x02, 0x01, 0x02, 0x0E, 0x01}};
CAN_frame SUNGROW_714 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x714,
.data = {0x05, 0x01, 0xAC, 0x80, 0x10, 0x02, 0x57, 0x80}};
CAN_frame SUNGROW_715 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x715,
.data = {0x93, 0x80, 0xAC, 0x80, 0x57, 0x80, 0x93, 0x80}};
CAN_frame SUNGROW_716 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x716,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SUNGROW_717 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x717,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SUNGROW_718 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x718,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SUNGROW_719 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x719,
.data = {0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SUNGROW_71A = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x71A,
.data = {0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SUNGROW_71B = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x71B,
.data = {0xBE, 0x8F, 0x61, 0x01, 0xBE, 0x8F, 0x61, 0x01}};
CAN_frame SUNGROW_71C = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x71C,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SUNGROW_71D = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x71D,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SUNGROW_71E = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x71E,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
};
#endif

View file

@ -239,11 +239,11 @@
#define ARDUINOJSON_BIN2ALPHA_1111() P
#define ARDUINOJSON_BIN2ALPHA_(A, B, C, D) ARDUINOJSON_BIN2ALPHA_##A##B##C##D()
#define ARDUINOJSON_BIN2ALPHA(A, B, C, D) ARDUINOJSON_BIN2ALPHA_(A, B, C, D)
#define ARDUINOJSON_VERSION "7.3.1"
#define ARDUINOJSON_VERSION "7.4.1"
#define ARDUINOJSON_VERSION_MAJOR 7
#define ARDUINOJSON_VERSION_MINOR 3
#define ARDUINOJSON_VERSION_MINOR 4
#define ARDUINOJSON_VERSION_REVISION 1
#define ARDUINOJSON_VERSION_MACRO V731
#define ARDUINOJSON_VERSION_MACRO V741
#ifndef ARDUINOJSON_VERSION_NAMESPACE
# define ARDUINOJSON_VERSION_NAMESPACE \
ARDUINOJSON_CONCAT5( \
@ -1018,6 +1018,14 @@ struct StringAdapter<TChar*, enable_if_t<IsChar<TChar>::value>> {
return AdaptedString(str, str ? ::strlen(str) : 0);
}
};
template <typename TChar>
struct StringAdapter<TChar[], enable_if_t<IsChar<TChar>::value>> {
using AdaptedString = RamString;
static AdaptedString adapt(const TChar* p) {
auto str = reinterpret_cast<const char*>(p);
return AdaptedString(str, str ? ::strlen(str) : 0);
}
};
template <size_t N>
struct StringAdapter<const char (&)[N]> {
using AdaptedString = RamString;
@ -2004,8 +2012,9 @@ ARDUINOJSON_END_PUBLIC_NAMESPACE
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
class ObjectData : public CollectionData {
public:
template <typename TAdaptedString> // also works with StringNode*
template <typename TAdaptedString>
VariantData* addMember(TAdaptedString key, ResourceManager* resources);
VariantData* addPair(VariantData** value, ResourceManager* resources);
template <typename TAdaptedString>
VariantData* getOrAddMember(TAdaptedString key, ResourceManager* resources);
template <typename TAdaptedString>
@ -2058,6 +2067,7 @@ enum class VariantTypeBits : uint8_t {
};
enum class VariantType : uint8_t {
Null = 0, // 0000 0000
TinyString = 0x02, // 0000 0010
RawString = 0x03, // 0000 0011
LinkedString = 0x04, // 0000 0100
OwnedString = 0x05, // 0000 0101
@ -2078,6 +2088,7 @@ enum class VariantType : uint8_t {
inline bool operator&(VariantType type, VariantTypeBits bit) {
return (uint8_t(type) & uint8_t(bit)) != 0;
}
const size_t tinyStringMaxLength = 3;
union VariantContent {
VariantContent() {}
float asFloat;
@ -2092,6 +2103,7 @@ union VariantContent {
CollectionData asCollection;
const char* asLinkedString;
struct StringNode* asOwnedString;
char asTinyString[tinyStringMaxLength + 1];
};
#if ARDUINOJSON_USE_EXTENSIONS
union VariantExtension {
@ -2106,6 +2118,15 @@ union VariantExtension {
#endif
template <typename T>
T parseNumber(const char* s);
template <typename T>
static bool isTinyString(const T& s, size_t n) {
if (n > tinyStringMaxLength)
return false;
bool containsNul = false;
for (uint8_t i = 0; i < uint8_t(n); i++)
containsNul |= !s[i];
return !containsNul;
}
class VariantData {
VariantContent content_; // must be first to allow cast from array to variant
VariantType type_;
@ -2141,6 +2162,8 @@ class VariantData {
return visit.visit(content_.asArray);
case VariantType::Object:
return visit.visit(content_.asObject);
case VariantType::TinyString:
return visit.visit(JsonString(content_.asTinyString));
case VariantType::LinkedString:
return visit.visit(JsonString(content_.asLinkedString, true));
case VariantType::OwnedString:
@ -2258,6 +2281,9 @@ class VariantData {
case VariantType::Int64:
return static_cast<T>(extension->asInt64);
#endif
case VariantType::TinyString:
str = content_.asTinyString;
break;
case VariantType::LinkedString:
str = content_.asLinkedString;
break;
@ -2298,6 +2324,9 @@ class VariantData {
case VariantType::Int64:
return convertNumber<T>(extension->asInt64);
#endif
case VariantType::TinyString:
str = content_.asTinyString;
break;
case VariantType::LinkedString:
str = content_.asLinkedString;
break;
@ -2333,6 +2362,8 @@ class VariantData {
}
JsonString asString() const {
switch (type_) {
case VariantType::TinyString:
return JsonString(content_.asTinyString);
case VariantType::LinkedString:
return JsonString(content_.asLinkedString, true);
case VariantType::OwnedString:
@ -2427,7 +2458,8 @@ class VariantData {
}
bool isString() const {
return type_ == VariantType::LinkedString ||
type_ == VariantType::OwnedString;
type_ == VariantType::OwnedString ||
type_ == VariantType::TinyString;
}
size_t nesting(const ResourceManager* resources) const {
auto collection = asCollection();
@ -2503,10 +2535,6 @@ class VariantData {
}
template <typename TAdaptedString>
bool setString(TAdaptedString value, ResourceManager* resources);
bool setString(StringNode* s, ResourceManager*) {
setOwnedString(s);
return true;
}
template <typename TAdaptedString>
static void setString(VariantData* var, TAdaptedString value,
ResourceManager* resources) {
@ -2521,6 +2549,19 @@ class VariantData {
type_ = VariantType::LinkedString;
content_.asLinkedString = s;
}
template <typename TAdaptedString>
void setTinyString(const TAdaptedString& s) {
ARDUINOJSON_ASSERT(type_ == VariantType::Null); // must call clear() first
ARDUINOJSON_ASSERT(s.size() <= tinyStringMaxLength);
type_ = VariantType::TinyString;
auto n = uint8_t(s.size());
for (uint8_t i = 0; i < n; i++) {
char c = s[i];
ARDUINOJSON_ASSERT(c != 0); // no NUL in tiny string
content_.asTinyString[i] = c;
}
content_.asTinyString[n] = 0;
}
void setOwnedString(StringNode* s) {
ARDUINOJSON_ASSERT(type_ == VariantType::Null); // must call clear() first
ARDUINOJSON_ASSERT(s);
@ -4824,6 +4865,18 @@ inline VariantData* ObjectData::addMember(TAdaptedString key,
CollectionData::appendPair(keySlot, valueSlot, resources);
return valueSlot.ptr();
}
inline VariantData* ObjectData::addPair(VariantData** value,
ResourceManager* resources) {
auto keySlot = resources->allocVariant();
if (!keySlot)
return nullptr;
auto valueSlot = resources->allocVariant();
if (!valueSlot)
return nullptr;
*value = valueSlot.ptr();
CollectionData::appendPair(keySlot, valueSlot, resources);
return keySlot.ptr();
}
constexpr size_t sizeofObject(size_t n) {
return 2 * n * ResourceManager::slotSize;
}
@ -5370,10 +5423,16 @@ class StringBuilder {
if (!node_)
node_ = resources_->createString(initialCapacity);
}
StringNode* save() {
void save(VariantData* variant) {
ARDUINOJSON_ASSERT(variant != nullptr);
ARDUINOJSON_ASSERT(node_ != nullptr);
node_->data[size_] = 0;
StringNode* node = resources_->getString(adaptString(node_->data, size_));
char* p = node_->data;
if (isTinyString(p, size_)) {
variant->setTinyString(adaptString(p, size_));
return;
}
p[size_] = 0;
StringNode* node = resources_->getString(adaptString(p, size_));
if (!node) {
node = resources_->resizeString(node_, size_);
ARDUINOJSON_ASSERT(node != nullptr); // realloc to smaller can't fail
@ -5382,7 +5441,7 @@ class StringBuilder {
} else {
node->references++;
}
return node;
variant->setOwnedString(node);
}
void append(const char* s) {
while (*s)
@ -5595,9 +5654,9 @@ class StringBuilderPrint : public Print {
StringBuilderPrint(ResourceManager* resources) : copier_(resources) {
copier_.startString();
}
StringNode* save() {
void save(VariantData* data) {
ARDUINOJSON_ASSERT(!overflowed());
return copier_.save();
copier_.save(data);
}
size_t write(uint8_t c) {
copier_.append(char(c));
@ -5628,7 +5687,7 @@ inline void convertToJson(const ::Printable& src, JsonVariant dst) {
src.printTo(print);
if (print.overflowed())
return;
data->setOwnedString(print.save());
print.save(data);
}
#endif
#if ARDUINOJSON_ENABLE_ARDUINO_STRING
@ -5774,6 +5833,10 @@ inline bool VariantData::setString(TAdaptedString value,
setLinkedString(value.data());
return true;
}
if (isTinyString(value, value.size())) {
setTinyString(value);
return true;
}
auto dup = resources->saveString(value);
if (dup) {
setOwnedString(dup);
@ -6895,10 +6958,10 @@ class JsonDeserializer {
if (memberFilter.allow()) {
auto member = object.getMember(adaptString(key), resources_);
if (!member) {
auto savedKey = stringBuilder_.save();
member = object.addMember(savedKey, resources_);
if (!member)
auto keyVariant = object.addPair(&member, resources_);
if (!keyVariant)
return DeserializationError::NoMemory;
stringBuilder_.save(keyVariant);
} else {
member->clear(resources_);
}
@ -6972,7 +7035,7 @@ class JsonDeserializer {
err = parseQuotedString();
if (err)
return err;
variant.setOwnedString(stringBuilder_.save());
stringBuilder_.save(&variant);
return DeserializationError::Ok;
}
DeserializationError::Code parseQuotedString() {
@ -7424,7 +7487,23 @@ class StringBuffer {
node_->data[capacity] = 0; // null-terminate the string
return node_->data;
}
StringNode* save() {
JsonString str() const {
ARDUINOJSON_ASSERT(node_ != nullptr);
return JsonString(node_->data, node_->length);
}
void save(VariantData* data) {
ARDUINOJSON_ASSERT(node_ != nullptr);
const char* s = node_->data;
if (isTinyString(s, size_))
data->setTinyString(adaptString(s, size_));
else
data->setOwnedString(commitStringNode());
}
void saveRaw(VariantData* data) {
data->setRawString(commitStringNode());
}
private:
StringNode* commitStringNode() {
ARDUINOJSON_ASSERT(node_ != nullptr);
node_->data[size_] = 0;
auto node = resources_->getString(adaptString(node_->data, size_));
@ -7442,11 +7521,6 @@ class StringBuffer {
resources_->saveString(node);
return node;
}
JsonString str() const {
ARDUINOJSON_ASSERT(node_ != nullptr);
return JsonString(node_->data, node_->length);
}
private:
ResourceManager* resources_;
StringNode* node_ = nullptr;
size_t size_ = 0;
@ -7716,7 +7790,7 @@ class MsgPackDeserializer {
err = readString(n);
if (err)
return err;
variant->setOwnedString(stringBuffer_.save());
stringBuffer_.save(variant);
return DeserializationError::Ok;
}
DeserializationError::Code readString(size_t n) {
@ -7738,7 +7812,7 @@ class MsgPackDeserializer {
auto err = readBytes(p + headerSize, n);
if (err)
return err;
variant->setRawString(stringBuffer_.save());
stringBuffer_.saveRaw(variant);
return DeserializationError::Ok;
}
template <typename TFilter>
@ -7793,15 +7867,13 @@ class MsgPackDeserializer {
return err;
JsonString key = stringBuffer_.str();
TFilter memberFilter = filter[key.c_str()];
VariantData* member;
VariantData* member = 0;
if (memberFilter.allow()) {
ARDUINOJSON_ASSERT(object != 0);
auto savedKey = stringBuffer_.save();
member = object->addMember(savedKey, resources_);
if (!member)
auto keyVariant = object->addPair(&member, resources_);
if (!keyVariant)
return DeserializationError::NoMemory;
} else {
member = 0;
stringBuffer_.save(keyVariant);
}
err = parseVariant(member, memberFilter, nestingLimit.decrement());
if (err)