#include "../include.h" #ifdef AFORE_CAN #include "../datalayer/datalayer.h" #include "AFORE-CAN.h" #define SOCMAX 100 #define SOCMIN 1 /* Do not change code below unless you are sure what you are doing */ /* The code is following the Afore 2.3 CAN standard, little-endian, 500kbps, from 2023.08.07 */ static uint8_t inverter_status = 0; //0 = init, 1 = standby, 2 = starting, 3 = grid connected, 4 off-grid, 5 diesel generator, 6 grid connected, but disconnected, 7off grid and disconnected, 8 = power failure processing, 9 = power off, 10 = Failure static bool time_to_send_info = false; static uint8_t char0 = 0; static uint8_t char1 = 0; static uint8_t char2 = 0; static uint8_t char3 = 0; static uint8_t char4 = 0; //Actual content messages CAN_frame AFORE_350 = {.FD = false, // Operation information .ext_ID = false, .DLC = 8, .ID = 0x350, .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; CAN_frame AFORE_351 = {.FD = false, .ext_ID = false, .DLC = 8, .ID = 0x351, .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; CAN_frame AFORE_352 = {.FD = false, .ext_ID = false, .DLC = 8, .ID = 0x352, .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; CAN_frame AFORE_353 = {.FD = false, .ext_ID = false, .DLC = 8, .ID = 0x353, .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; CAN_frame AFORE_354 = {.FD = false, .ext_ID = false, .DLC = 8, .ID = 0x354, .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; CAN_frame AFORE_355 = {.FD = false, .ext_ID = false, .DLC = 8, .ID = 0x355, .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; CAN_frame AFORE_356 = {.FD = false, .ext_ID = false, .DLC = 8, .ID = 0x356, .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; CAN_frame AFORE_357 = {.FD = false, .ext_ID = false, .DLC = 8, .ID = 0x357, .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; CAN_frame AFORE_358 = {.FD = false, .ext_ID = false, .DLC = 8, .ID = 0x358, .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; CAN_frame AFORE_359 = {.FD = false, // Serial number 0-7 .ext_ID = false, .DLC = 8, .ID = 0x359, .data = {0x62, 0x61, 0x74, 0x74, 0x65, 0x72, 0x79, 0x2D}}; // Battery- CAN_frame AFORE_35A = {.FD = false, // Serial number 8-15 .ext_ID = false, .DLC = 8, .ID = 0x35A, .data = {0x65, 0x6D, 0x75, 0x6C, 0x61, 0x74, 0x6F, 0x72}}; // Emulator void update_values_can_inverter() { //This function maps all the values fetched from battery CAN to the correct CAN messages //There are more mappings that could be added, but this should be enough to use as a starting point /*0x350 Operation Information*/ AFORE_350.data.u8[0] = (datalayer.battery.status.voltage_dV & 0x00FF); AFORE_350.data.u8[1] = (datalayer.battery.status.voltage_dV >> 8); //Total battery current unit: 0.1A offset 5000; positive is charging, //Negative means discharge; for example: 1A = (5010-5000)/10 AFORE_350.data.u8[2] = ((datalayer.battery.status.current_dA + 5000) & 0x00FF); AFORE_350.data.u8[3] = ((datalayer.battery.status.current_dA + 5000) >> 8); //Battery temperature unit: 0.1C offset 1000; for example: 20C //= (1200 -1000)/10; when all temperatures are greater than or equal to 0 AFORE_350.data.u8[4] = ((datalayer.battery.status.temperature_max_dC + 1000) & 0x00FF); AFORE_350.data.u8[5] = ((datalayer.battery.status.temperature_max_dC + 1000) >> 8); /*0x351 - Battery information*/ AFORE_351.data.u8[0] = (datalayer.battery.status.reported_soc / 100); //Remove decimals, 0-100 AFORE_351.data.u8[1] = (datalayer.battery.status.soh_pptt / 100); //Remove decimals, 0-100 AFORE_351.data.u8[2] = SOCMAX; AFORE_351.data.u8[3] = SOCMIN; AFORE_351.data.u8[4] = 0x03; //Bit0 and Bit1 set if ((datalayer.battery.status.max_charge_current_dA == 0) || (datalayer.battery.status.reported_soc == 10000) || (datalayer.battery.status.bms_status == FAULT)) { AFORE_351.data.u8[4] &= ~0x01; // Remove Bit0 (clear) Charge enable flag } if ((datalayer.battery.status.max_discharge_current_dA == 0) || (datalayer.battery.status.reported_soc == 0) || (datalayer.battery.status.bms_status == FAULT)) { AFORE_351.data.u8[4] &= ~0x02; // Remove Bit1 (clear) Discharge enable flag } // Bit5-7 is BMS working status. //A value of 0 here is INIT, 1 = Normal operation, 2 = standby/sleep, 3 = warning, 4 = fault, rest is reserved AFORE_351.data.u8[4] &= ~(0xE0); // Clear bits 5, 6, and 7 (11100000) if (datalayer.battery.status.bms_status == FAULT) { AFORE_351.data.u8[4] |= 0x80; // Set bits 5-7 to 0b100 (Fault = 4) } else { // Normal mode AFORE_351.data.u8[4] |= 0x20; // Set Bit5 to 1 (Normal operation) } AFORE_351.data.u8[6] = (datalayer.battery.info.number_of_cells & 0x00FF); AFORE_351.data.u8[7] = (datalayer.battery.info.number_of_cells >> 8); /*0x352 - Protection parameters*/ AFORE_352.data.u8[0] = (datalayer.battery.status.max_charge_current_dA & 0x00FF); AFORE_352.data.u8[1] = (datalayer.battery.status.max_charge_current_dA >> 8); AFORE_352.data.u8[2] = (datalayer.battery.status.max_discharge_current_dA & 0x00FF); AFORE_352.data.u8[3] = (datalayer.battery.status.max_discharge_current_dA >> 8); AFORE_352.data.u8[4] = (datalayer.battery.info.max_design_voltage_dV & 0x00FF); AFORE_352.data.u8[5] = (datalayer.battery.info.max_design_voltage_dV >> 8); AFORE_352.data.u8[6] = (datalayer.battery.info.min_design_voltage_dV & 0x00FF); AFORE_352.data.u8[7] = (datalayer.battery.info.min_design_voltage_dV >> 8); /*0x353 - Fault information*/ /* Fault H, bit, definitions 0 ErrCellOverVolt 1 ErrCellUnderVolt 2 ErrOverVolt 3 ErrUnderVolt 4 ErrOverTemperature 5 ErrUnderTemperature 6 ErrBatIsolation 7 ErrChargeCurrentOver 8 ErrDischargeCurrentOver 9 ErrSOCtooLow 10 ErrBatteryInternalCommunicationFailure 11 ErrBMSVoltageSupplyTooHigh 12 ErrBMSVoltageSupplyTooLow 13 ErrRelayFailure 14 ErrPreChargerFailure 15 InterlockOpen AFORE_353.data.u8[0] = Fault H table & 0x00FF AFORE_353.data.u8[1] = Fault H table >> 8); /* Fault L, bit, definitions 8 VoltageInterlockShortCircuit 9 SystemFailure 10 ErrorChargeReferenceOvervoltage 11 ChargingMOSdamaged 12 DischargeMOSdamaged 13 CellOverTemperature 14 CellUnderTemperature 15 CellUnbalance AFORE_353.data.u8[2] = Fault L table & 0x00FF AFORE_353.data.u8[3] = Fault L table >> 8); */ /*0x354 - Single cell voltage parameters*/ AFORE_354.data.u8[0] = (datalayer.battery.status.cell_max_voltage_mV & 0x00FF); AFORE_354.data.u8[1] = (datalayer.battery.status.cell_max_voltage_mV >> 8); AFORE_354.data.u8[2] = (datalayer.battery.status.cell_min_voltage_mV & 0x00FF); AFORE_354.data.u8[3] = (datalayer.battery.status.cell_min_voltage_mV >> 8); AFORE_354.data.u8[4] = (1 & 0x00FF); //Maximum single cell voltage number, not used on emulator AFORE_354.data.u8[5] = (1 >> 8); AFORE_354.data.u8[6] = (2 & 0x00FF); //Minimum single cell voltage number, not used on emulator AFORE_354.data.u8[7] = (2 >> 8); /*0x355 - Single cell temperature parameters*/ AFORE_355.data.u8[0] = ((datalayer.battery.status.temperature_max_dC + 1000) & 0x00FF); AFORE_355.data.u8[1] = ((datalayer.battery.status.temperature_max_dC + 1000) >> 8); AFORE_355.data.u8[2] = ((datalayer.battery.status.temperature_min_dC + 1000) & 0x00FF); AFORE_355.data.u8[3] = ((datalayer.battery.status.temperature_min_dC + 1000) >> 8); AFORE_355.data.u8[4] = (1 & 0x00FF); //Maximum single cell temperature number, not used on emulator AFORE_355.data.u8[5] = (1 >> 8); AFORE_355.data.u8[6] = (2 & 0x00FF); //Minimum single cell temperature number, not used on emulator AFORE_355.data.u8[7] = (2 >> 8); /*0x356 - Single cell protection parameters*/ AFORE_356.data.u8[0] = (datalayer.battery.info.max_cell_voltage_mV & 0x00FF); AFORE_356.data.u8[1] = (datalayer.battery.info.max_cell_voltage_mV >> 8); AFORE_356.data.u8[2] = (datalayer.battery.info.min_cell_voltage_mV & 0x00FF); AFORE_356.data.u8[3] = (datalayer.battery.info.min_cell_voltage_mV >> 8); /*0x357 - Warning information*/ /* Warning, bit, definitions 0 CellOverVolt 1 CellUnderVolt 2 OverVolt 3 UnderVolt 4 CellOverTemperature 5 CellUnderTemperature 6 TempOver 7 TempUnder 8 ChgCurrOver 9 ErrSOCtooLow 10 SocLow AFORE_357.data.u8[0] = Warning table & 0x00FF AFORE_357.data.u8[1] = Warning table >> 8); */ } void receive_can_inverter(CAN_frame rx_frame) { switch (rx_frame.ID) { case 0x305: // Every 1s from inverter datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE; char0 = rx_frame.data.u8[0]; // A char1 = rx_frame.data.u8[0]; // F char2 = rx_frame.data.u8[0]; // O char3 = rx_frame.data.u8[0]; // R char4 = rx_frame.data.u8[0]; // E inverter_status = rx_frame.data.u8[7]; time_to_send_info = true; break; default: break; } } void send_can_inverter() { if (time_to_send_info) { // Set every 1s if we get message from inverter transmit_can(&AFORE_350, can_config.inverter); transmit_can(&AFORE_351, can_config.inverter); transmit_can(&AFORE_352, can_config.inverter); transmit_can(&AFORE_353, can_config.inverter); transmit_can(&AFORE_354, can_config.inverter); transmit_can(&AFORE_355, can_config.inverter); transmit_can(&AFORE_356, can_config.inverter); transmit_can(&AFORE_357, can_config.inverter); transmit_can(&AFORE_358, can_config.inverter); transmit_can(&AFORE_359, can_config.inverter); transmit_can(&AFORE_35A, can_config.inverter); time_to_send_info = false; } } void setup_inverter(void) { // Performs one time setup at startup over CAN bus strncpy(datalayer.system.info.inverter_protocol, "Afore battery over CAN", 63); datalayer.system.info.inverter_protocol[63] = '\0'; } #endif