diff --git a/Software/USER_SETTINGS.h b/Software/USER_SETTINGS.h index 3c6c6763..e9687df5 100644 --- a/Software/USER_SETTINGS.h +++ b/Software/USER_SETTINGS.h @@ -46,6 +46,7 @@ //#define PYLON_CAN //Enable this line to emulate a "High Voltage Pylontech battery" over CAN bus //#define SCHNEIDER_CAN //Enable this line to emulate a "Schneider Version 2: SE BMS" over CAN bus //#define SMA_CAN //Enable this line to emulate a "BYD Battery-Box H 8.9kWh, 7 mod" over CAN bus +//#define SMA_LV_CAN //Enable this line to emulate a "SMA Sunny Island 48V battery" over CAN bus //#define SMA_TRIPOWER_CAN //Enable this line to emulate a "SMA Home Storage battery" over CAN bus //#define SOFAR_CAN //Enable this line to emulate a "Sofar Energy Storage Inverter High Voltage BMS General Protocol (Extended Frame)" over CAN bus //#define SOLAX_CAN //Enable this line to emulate a "SolaX Triple Power LFP" over CAN bus diff --git a/Software/src/inverter/INVERTERS.h b/Software/src/inverter/INVERTERS.h index f1944ecf..a5c4252d 100644 --- a/Software/src/inverter/INVERTERS.h +++ b/Software/src/inverter/INVERTERS.h @@ -43,6 +43,10 @@ #include "SMA-CAN.h" #endif +#ifdef SMA_LV_CAN +#include "SMA-LV-CAN.h" +#endif + #ifdef SMA_TRIPOWER_CAN #include "SMA-TRIPOWER-CAN.h" #endif diff --git a/Software/src/inverter/SMA-LV-CAN.cpp b/Software/src/inverter/SMA-LV-CAN.cpp new file mode 100644 index 00000000..77d3c429 --- /dev/null +++ b/Software/src/inverter/SMA-LV-CAN.cpp @@ -0,0 +1,166 @@ +#include "../include.h" +#ifdef SMA_LV_CAN +#include "../datalayer/datalayer.h" +#include "SMA-LV-CAN.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_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}}; +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}}; + +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 + //Calculate values + + temperature_average = + ((datalayer.battery.status.temperature_max_dC + datalayer.battery.status.temperature_min_dC) / 2); + + //Map values to CAN messages + //Battery charge voltage (eg 400.0V = 4000 , 16bits long) (MIN 41V, MAX 63V, default 54V) + SMA_351.data.u8[0] = ((datalayer.battery.info.max_design_voltage_dV - VOLTAGE_OFFSET_DV) >> 8); + SMA_351.data.u8[1] = ((datalayer.battery.info.max_design_voltage_dV - VOLTAGE_OFFSET_DV) & 0x00FF); + if (datalayer.battery.info.max_design_voltage_dV > MAX_VOLTAGE_DV) { + //If the battery is designed for more than 63.0V, cap the value + SMA_351.data.u8[0] = (MAX_VOLTAGE_DV >> 8); + SMA_351.data.u8[1] = (MAX_VOLTAGE_DV & 0x00FF); + //TODO; raise event? + } + //Discharge limited current, 500 = 50A, (0.1, A) (MIN 0, MAX 1200) + SMA_351.data.u8[2] = (datalayer.battery.status.max_discharge_current_dA >> 8); + SMA_351.data.u8[3] = (datalayer.battery.status.max_discharge_current_dA & 0x00FF); + //Charge limited current, 125 =12.5A (0.1, A) (MIN 0, MAX 1200) + SMA_351.data.u8[4] = (datalayer.battery.status.max_charge_current_dA >> 8); + SMA_351.data.u8[5] = (datalayer.battery.status.max_charge_current_dA & 0x00FF); + //Discharge voltage (eg 300.0V = 3000 , 16bits long) (MIN 41V, MAX 48V, default 41V) + SMA_351.data.u8[6] = ((datalayer.battery.info.min_design_voltage_dV + VOLTAGE_OFFSET_DV) >> 8); + SMA_351.data.u8[7] = ((datalayer.battery.info.min_design_voltage_dV + VOLTAGE_OFFSET_DV) & 0x00FF); + if (datalayer.battery.info.min_design_voltage_dV < MIN_VOLTAGE_DV) { + //If the battery is designed for discharge voltage below 41.0V, cap the value + SMA_351.data.u8[6] = (MIN_VOLTAGE_DV >> 8); + SMA_351.data.u8[7] = (MIN_VOLTAGE_DV & 0x00FF); + //TODO; raise event? + } + + //SOC (100%) + SMA_355.data.u8[0] = ((datalayer.battery.status.reported_soc / 100) >> 8); + SMA_355.data.u8[1] = ((datalayer.battery.status.reported_soc / 100) & 0x00FF); + //StateOfHealth (100%) + SMA_355.data.u8[2] = ((datalayer.battery.status.soh_pptt / 100) >> 8); + SMA_355.data.u8[3] = ((datalayer.battery.status.soh_pptt / 100) & 0x00FF); + //State of charge High Precision (100.00%) + SMA_355.data.u8[4] = (datalayer.battery.status.reported_soc >> 8); + SMA_355.data.u8[5] = (datalayer.battery.status.reported_soc & 0x00FF); + + //Voltage (370.0) + SMA_356.data.u8[0] = ((datalayer.battery.status.voltage_dV * 10) >> 8); + SMA_356.data.u8[1] = ((datalayer.battery.status.voltage_dV * 10) & 0x00FF); + //Current (S16 dA) + SMA_356.data.u8[2] = (datalayer.battery.status.current_dA >> 8); + SMA_356.data.u8[3] = (datalayer.battery.status.current_dA & 0x00FF); + //Temperature (s16 degC) + SMA_356.data.u8[4] = (temperature_average >> 8); + SMA_356.data.u8[5] = (temperature_average & 0x00FF); + + //TODO: Map error/warnings in 0x35A +} + +void receive_can_inverter(CAN_frame rx_frame) { + switch (rx_frame.ID) { + case 0x305: + datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE; + //Frame0-1 Battery Voltage + //Frame2-3 Battery Current + //Frame4-5 Battery Temperature + //Frame6-7 SOC Battery + break; + case 0x306: + datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE; + //Frame0-1 SOH Battery + //Frame2 Charging procedure + //Frame3 Operating state + //Frame4-5 Active error message + //Frame6-7 Battery charge voltage setpoint + break; + default: + break; + } +} + +void send_can_inverter() { + unsigned long currentMillis = millis(); + + if (currentMillis - previousMillis100ms >= INTERVAL_100_MS) { + previousMillis100ms = currentMillis; + + transmit_can(&SMA_351, can_config.inverter); + transmit_can(&SMA_355, can_config.inverter); + transmit_can(&SMA_356, can_config.inverter); + transmit_can(&SMA_35A, can_config.inverter); + transmit_can(&SMA_35B, can_config.inverter); + transmit_can(&SMA_35E, can_config.inverter); + transmit_can(&SMA_35F, can_config.inverter); + + //Remote quick stop (optional) + if (datalayer.battery.status.bms_status == FAULT) { + transmit_can(&SMA_00F, can_config.inverter); + //After receiving this message, Sunny Island will immediately go into standby. + //Please send start command, to start again. Manual start is also possible. + } + } +} + +void setup_inverter(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 diff --git a/Software/src/inverter/SMA-LV-CAN.h b/Software/src/inverter/SMA-LV-CAN.h new file mode 100644 index 00000000..a1c6e7e9 --- /dev/null +++ b/Software/src/inverter/SMA-LV-CAN.h @@ -0,0 +1,13 @@ +#ifndef SMA_LV_CAN_H +#define SMA_LV_CAN_H +#include "../include.h" + +#define CAN_INVERTER_SELECTED + +#define READY_STATE 0x03 +#define STOP_STATE 0x02 + +void transmit_can(CAN_frame* tx_frame, int interface); +void setup_inverter(void); + +#endif