mirror of
https://github.com/dalathegreat/Battery-Emulator.git
synced 2025-10-05 10:49:42 +02:00
Merge branch 'main' into more-batt
This commit is contained in:
commit
12df312eef
64 changed files with 3145 additions and 2034 deletions
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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:
|
||||
|
|
675
Software/src/battery/GEELY-GEOMETRY-C-BATTERY.cpp
Normal file
675
Software/src/battery/GEELY-GEOMETRY-C-BATTERY.cpp
Normal 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
|
217
Software/src/battery/GEELY-GEOMETRY-C-BATTERY.h
Normal file
217
Software/src/battery/GEELY-GEOMETRY-C-BATTERY.h
Normal 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
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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_
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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]) + " °C</h4>";
|
||||
content +=
|
||||
"<h4>Module 2 temperature: " + String(datalayer_extended.geometryC.ModuleTemperatures[1]) + " °C</h4>";
|
||||
content +=
|
||||
"<h4>Module 3 temperature: " + String(datalayer_extended.geometryC.ModuleTemperatures[2]) + " °C</h4>";
|
||||
content +=
|
||||
"<h4>Module 4 temperature: " + String(datalayer_extended.geometryC.ModuleTemperatures[3]) + " °C</h4>";
|
||||
content +=
|
||||
"<h4>Module 5 temperature: " + String(datalayer_extended.geometryC.ModuleTemperatures[4]) + " °C</h4>";
|
||||
content +=
|
||||
"<h4>Module 6 temperature: " + String(datalayer_extended.geometryC.ModuleTemperatures[5]) + " °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
|
||||
|
||||
|
|
|
@ -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'>" +
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue