mirror of
https://github.com/dalathegreat/Battery-Emulator.git
synced 2025-10-03 01:39:30 +02:00
212 lines
8.5 KiB
C++
212 lines
8.5 KiB
C++
#include "SMA-TRIPOWER-CAN.h"
|
|
#include "../communication/can/comm_can.h"
|
|
#include "../datalayer/datalayer.h"
|
|
#include "../devboard/utils/events.h"
|
|
|
|
/* TODO:
|
|
- Figure out the manufacturer info needed in transmit_can_init() CAN messages
|
|
- CAN logs from real system might be needed
|
|
- Figure out how cellvoltages need to be displayed
|
|
- Figure out if sending transmit_can_init() like we do now is OK
|
|
- Figure out how to send the non-cyclic messages when needed
|
|
*/
|
|
|
|
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);
|
|
|
|
if (datalayer.battery.status.voltage_dV > 10) { // Only update value when we have voltage available to avoid div0
|
|
ampere_hours_remaining =
|
|
((datalayer.battery.status.reported_remaining_capacity_Wh / datalayer.battery.status.voltage_dV) *
|
|
100); //(WH[10000] * V+1[3600])*100 = 270 (27.0Ah)
|
|
}
|
|
|
|
//Map values to CAN messages
|
|
|
|
//Maxvoltage (eg 400.0V = 4000 , 16bits long)
|
|
SMA_358.data.u8[0] = (datalayer.battery.info.max_design_voltage_dV >> 8);
|
|
SMA_358.data.u8[1] = (datalayer.battery.info.max_design_voltage_dV & 0x00FF);
|
|
//Minvoltage (eg 300.0V = 3000 , 16bits long)
|
|
SMA_358.data.u8[2] = (datalayer.battery.info.min_design_voltage_dV >> 8);
|
|
SMA_358.data.u8[3] = (datalayer.battery.info.min_design_voltage_dV & 0x00FF);
|
|
//Discharge limited current, 500 = 50A, (0.1, A)
|
|
SMA_358.data.u8[4] = (datalayer.battery.status.max_discharge_current_dA >> 8);
|
|
SMA_358.data.u8[5] = (datalayer.battery.status.max_discharge_current_dA & 0x00FF);
|
|
//Charge limited current, 125 =12.5A (0.1, A)
|
|
SMA_358.data.u8[6] = (datalayer.battery.status.max_charge_current_dA >> 8);
|
|
SMA_358.data.u8[7] = (datalayer.battery.status.max_charge_current_dA & 0x00FF);
|
|
|
|
//SOC (100.00%)
|
|
SMA_3D8.data.u8[0] = (datalayer.battery.status.reported_soc >> 8);
|
|
SMA_3D8.data.u8[1] = (datalayer.battery.status.reported_soc & 0x00FF);
|
|
//StateOfHealth (100.00%)
|
|
SMA_3D8.data.u8[2] = (datalayer.battery.status.soh_pptt >> 8);
|
|
SMA_3D8.data.u8[3] = (datalayer.battery.status.soh_pptt & 0x00FF);
|
|
//State of charge (AH, 0.1)
|
|
SMA_3D8.data.u8[4] = (ampere_hours_remaining >> 8);
|
|
SMA_3D8.data.u8[5] = (ampere_hours_remaining & 0x00FF);
|
|
|
|
//Voltage (370.0)
|
|
SMA_4D8.data.u8[0] = (datalayer.battery.status.voltage_dV >> 8);
|
|
SMA_4D8.data.u8[1] = (datalayer.battery.status.voltage_dV & 0x00FF);
|
|
//Current (TODO: signed OK?)
|
|
SMA_4D8.data.u8[2] = (datalayer.battery.status.current_dA >> 8);
|
|
SMA_4D8.data.u8[3] = (datalayer.battery.status.current_dA & 0x00FF);
|
|
//Temperature average
|
|
SMA_4D8.data.u8[4] = (temperature_average >> 8);
|
|
SMA_4D8.data.u8[5] = (temperature_average & 0x00FF);
|
|
//Battery ready
|
|
if (datalayer.battery.status.bms_status == FAULT) {
|
|
SMA_4D8.data.u8[6] = STOP_STATE;
|
|
} else {
|
|
SMA_4D8.data.u8[6] = READY_STATE;
|
|
}
|
|
|
|
//Highest battery temperature
|
|
SMA_518.data.u8[0] = (datalayer.battery.status.temperature_max_dC >> 8);
|
|
SMA_518.data.u8[1] = (datalayer.battery.status.temperature_max_dC & 0x00FF);
|
|
//Lowest battery temperature
|
|
SMA_518.data.u8[2] = (datalayer.battery.status.temperature_min_dC >> 8);
|
|
SMA_518.data.u8[3] = (datalayer.battery.status.temperature_min_dC & 0x00FF);
|
|
//Sum of all cellvoltages
|
|
SMA_518.data.u8[4] = (datalayer.battery.status.voltage_dV >> 8);
|
|
SMA_518.data.u8[5] = (datalayer.battery.status.voltage_dV & 0x00FF);
|
|
//Cell min/max voltage (mV / 25)
|
|
SMA_518.data.u8[6] = (datalayer.battery.status.cell_min_voltage_mV / 25);
|
|
SMA_518.data.u8[7] = (datalayer.battery.status.cell_max_voltage_mV / 25);
|
|
|
|
//Lifetime charged energy amount
|
|
SMA_458.data.u8[0] = (datalayer.battery.status.total_charged_battery_Wh & 0xFF000000) >> 24;
|
|
SMA_458.data.u8[1] = (datalayer.battery.status.total_charged_battery_Wh & 0x00FF0000) >> 16;
|
|
SMA_458.data.u8[2] = (datalayer.battery.status.total_charged_battery_Wh & 0x0000FF00) >> 8;
|
|
SMA_458.data.u8[3] = (datalayer.battery.status.total_charged_battery_Wh & 0x000000FF);
|
|
//Lifetime discharged energy amount
|
|
SMA_458.data.u8[4] = (datalayer.battery.status.total_discharged_battery_Wh & 0xFF000000) >> 24;
|
|
SMA_458.data.u8[5] = (datalayer.battery.status.total_discharged_battery_Wh & 0x00FF0000) >> 16;
|
|
SMA_458.data.u8[6] = (datalayer.battery.status.total_discharged_battery_Wh & 0x0000FF00) >> 8;
|
|
SMA_458.data.u8[7] = (datalayer.battery.status.total_discharged_battery_Wh & 0x000000FF);
|
|
|
|
control_contactor_led();
|
|
|
|
// Check if Enable line is working. If we go too long without any input, raise an event
|
|
if (!datalayer.system.status.inverter_allows_contactor_closing) {
|
|
timeWithoutInverterAllowsContactorClosing++;
|
|
|
|
if (timeWithoutInverterAllowsContactorClosing > THIRTY_MINUTES) {
|
|
set_event(EVENT_NO_ENABLE_DETECTED, 0);
|
|
}
|
|
} else {
|
|
timeWithoutInverterAllowsContactorClosing = 0;
|
|
}
|
|
}
|
|
|
|
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;
|
|
inverter_voltage = (rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1];
|
|
inverter_current = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3];
|
|
break;
|
|
case 0x3E0: //Message originating from SMA inverter - ?
|
|
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
|
|
break;
|
|
case 0x420: //Message originating from SMA inverter - Timestamp
|
|
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
|
|
inverter_time =
|
|
(rx_frame.data.u8[0] << 24) | (rx_frame.data.u8[1] << 16) | (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3];
|
|
break;
|
|
case 0x560: //Message originating from SMA inverter - Init
|
|
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
|
|
break;
|
|
case 0x5E0: //Message originating from SMA inverter - String
|
|
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
|
|
//Inverter brand (frame1-3 = 0x53 0x4D 0x41) = SMA
|
|
break;
|
|
case 0x5E7: //Message originating from SMA inverter - Pairing request
|
|
case 0x660: //Message originating from SMA inverter - Pairing request
|
|
logging.println("Received SMA pairing request");
|
|
pairing_events++;
|
|
set_event(EVENT_SMA_PAIRING, pairing_events);
|
|
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
|
|
transmit_can_init();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void SmaTripowerInverter::pushFrame(CAN_frame* frame, std::function<void(void)> callback) {
|
|
if (listLength >= 20) {
|
|
return; //TODO: scream.
|
|
}
|
|
framesToSend[listLength] = {
|
|
.frame = frame,
|
|
.callback = callback,
|
|
};
|
|
listLength++;
|
|
}
|
|
|
|
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) {
|
|
return;
|
|
}
|
|
|
|
if (listLength > 0 && currentMillis - previousMillis250ms >= INTERVAL_250_MS) {
|
|
previousMillis250ms = currentMillis;
|
|
// Send next frame.
|
|
Frame frame = framesToSend[0];
|
|
transmit_can_frame(frame.frame);
|
|
frame.callback();
|
|
for (int i = 0; i < listLength - 1; i++) {
|
|
framesToSend[i] = framesToSend[i + 1];
|
|
}
|
|
listLength--;
|
|
}
|
|
|
|
if (!pairing_completed) {
|
|
return;
|
|
}
|
|
|
|
// Send CAN Message every 2s
|
|
if (currentMillis - previousMillis2s >= INTERVAL_2_S) {
|
|
previousMillis2s = currentMillis;
|
|
pushFrame(&SMA_358);
|
|
}
|
|
// Send CAN Message every 10s
|
|
if (currentMillis - previousMillis10s >= INTERVAL_10_S) {
|
|
previousMillis10s = currentMillis;
|
|
pushFrame(&SMA_518);
|
|
pushFrame(&SMA_4D8);
|
|
pushFrame(&SMA_3D8);
|
|
}
|
|
// Send CAN Message every 60s (potentially SMA_458 is not required for stable operation)
|
|
if (currentMillis - previousMillis60s >= INTERVAL_60_S) {
|
|
previousMillis60s = currentMillis;
|
|
pushFrame(&SMA_458);
|
|
}
|
|
}
|
|
|
|
void SmaTripowerInverter::completePairing() {
|
|
pairing_completed = true;
|
|
}
|
|
|
|
void SmaTripowerInverter::transmit_can_init() {
|
|
listLength = 0; // clear all frames
|
|
|
|
pushFrame(&SMA_558); //Pairing start - Vendor
|
|
pushFrame(&SMA_598); //Serial
|
|
pushFrame(&SMA_5D8); //BYD
|
|
pushFrame(&SMA_618_0); //BATTERY
|
|
pushFrame(&SMA_618_1); //-Box Pr
|
|
pushFrame(&SMA_618_2); //emium H
|
|
pushFrame(&SMA_618_3); //VS
|
|
pushFrame(&SMA_358);
|
|
pushFrame(&SMA_3D8);
|
|
pushFrame(&SMA_458);
|
|
pushFrame(&SMA_4D8);
|
|
pushFrame(&SMA_518, [this]() { this->completePairing(); });
|
|
}
|