mirror of
https://github.com/dalathegreat/Battery-Emulator.git
synced 2025-10-03 09:49:32 +02:00
Merge pull request #1425 from jonny5532/feature/more-inverter-settings
Make more inverter settings configurable
This commit is contained in:
commit
4a24f3558f
14 changed files with 321 additions and 48 deletions
|
@ -96,6 +96,13 @@ void init_stored_settings() {
|
|||
user_selected_min_pack_voltage_dV = settings.getUInt("BATTPVMIN", 0);
|
||||
user_selected_max_cell_voltage_mV = settings.getUInt("BATTCVMAX", 0);
|
||||
user_selected_min_cell_voltage_mV = settings.getUInt("BATTCVMIN", 0);
|
||||
user_selected_inverter_cells = settings.getUInt("INVCELLS", 0);
|
||||
user_selected_inverter_modules = settings.getUInt("INVMODULES", 0);
|
||||
user_selected_inverter_cells_per_module = settings.getUInt("INVCELLSPER", 0);
|
||||
user_selected_inverter_voltage_level = settings.getUInt("INVVLEVEL", 0);
|
||||
user_selected_inverter_ah_capacity = settings.getUInt("INVAHCAPACITY", 0);
|
||||
user_selected_inverter_battery_type = settings.getUInt("INVBTYPE", 0);
|
||||
user_selected_inverter_ignore_contactors = settings.getBool("INVICNT", false);
|
||||
|
||||
auto readIf = [](const char* settingName) {
|
||||
auto batt1If = (comm_interface)settings.getUInt(settingName, (int)comm_interface::CanNative);
|
||||
|
@ -193,10 +200,7 @@ void store_settings() {
|
|||
if (!settings.putUInt("TARGETDISCHVOLT", datalayer.battery.settings.max_user_set_discharge_voltage_dV)) {
|
||||
set_event(EVENT_PERSISTENT_SAVE_INFO, 11);
|
||||
}
|
||||
if (!settings.putUInt("SOFAR_ID", datalayer.battery.settings.sofar_user_specified_battery_id)) {
|
||||
set_event(EVENT_PERSISTENT_SAVE_INFO, 12);
|
||||
}
|
||||
if (!settings.putUInt("BMSRESETDUR", datalayer.battery.settings.sofar_user_specified_battery_id)) {
|
||||
if (!settings.putUInt("BMSRESETDUR", datalayer.battery.settings.user_set_bms_reset_duration_ms)) {
|
||||
set_event(EVENT_PERSISTENT_SAVE_INFO, 13);
|
||||
}
|
||||
|
||||
|
|
|
@ -468,6 +468,38 @@ String settings_processor(const String& var, BatteryEmulatorSettingsStore& setti
|
|||
return String(datalayer.charger.charger_setpoint_HV_IDC, 1);
|
||||
}
|
||||
|
||||
if (var == "SOFAR_ID") {
|
||||
return String(settings.getUInt("SOFAR_ID", 0));
|
||||
}
|
||||
|
||||
if (var == "INVCELLS") {
|
||||
return String(settings.getUInt("INVCELLS", 0));
|
||||
}
|
||||
|
||||
if (var == "INVMODULES") {
|
||||
return String(settings.getUInt("INVMODULES", 0));
|
||||
}
|
||||
|
||||
if (var == "INVCELLSPER") {
|
||||
return String(settings.getUInt("INVCELLSPER", 0));
|
||||
}
|
||||
|
||||
if (var == "INVVLEVEL") {
|
||||
return String(settings.getUInt("INVVLEVEL", 0));
|
||||
}
|
||||
|
||||
if (var == "INVCAPACITY") {
|
||||
return String(settings.getUInt("INVCAPACITY", 0));
|
||||
}
|
||||
|
||||
if (var == "INVBTYPE") {
|
||||
return String(settings.getUInt("INVBTYPE", 0));
|
||||
}
|
||||
|
||||
if (var == "INVICNT") {
|
||||
return settings.getBool("INVICNT") ? "checked" : "";
|
||||
}
|
||||
|
||||
return String();
|
||||
}
|
||||
|
||||
|
@ -527,10 +559,6 @@ const char* getCANInterfaceName(CAN_Interface interface) {
|
|||
function editPassword(){var value=prompt('Enter new password:');if(value!==null){var xhr=new
|
||||
XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/updatePassword?value='+encodeURIComponent(value),true);xhr.send();}}
|
||||
|
||||
function editSofarID(){var value=prompt('For double battery setups. Which battery ID should this emulator send? Remember to reboot after configuring this! Enter new value between (0-15):');
|
||||
if(value!==null){if(value>=0&&value<=15){var xhr=new XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/updateSofarID?value='+value,true);xhr.send();}
|
||||
else {alert('Invalid value. Please enter a value between 0 and 15.');}}}
|
||||
|
||||
function editWh(){var value=prompt('How much energy the battery can store. Enter new Wh value (1-400000):');
|
||||
if(value!==null){if(value>=1&&value<=400000){var xhr=new
|
||||
XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/updateBatterySize?value='+value,true);xhr.send();}else{
|
||||
|
@ -663,6 +691,21 @@ const char* getCANInterfaceName(CAN_Interface interface) {
|
|||
display: contents;
|
||||
}
|
||||
|
||||
form .if-sofar { display: none; }
|
||||
form[data-inverter="17"] .if-sofar {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
form .if-pylonish { display: none; }
|
||||
form[data-inverter="4"] .if-pylonish, form[data-inverter="10"] .if-pylonish, form[data-inverter="19"] .if-pylonish {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
form .if-solax { display: none; }
|
||||
form[data-inverter="18"] .if-solax {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
form .if-mqtt { display: none; }
|
||||
form[data-mqttenabled="true"] .if-mqtt {
|
||||
display: contents;
|
||||
|
@ -726,6 +769,40 @@ const char* getCANInterfaceName(CAN_Interface interface) {
|
|||
</select>
|
||||
</div>
|
||||
|
||||
<div class="if-sofar">
|
||||
<label>Sofar Battery ID (0-15): </label>
|
||||
<input name='SOFAR_ID' type='text' value="%SOFAR_ID%" pattern="^[0-9]{1,2}$" />
|
||||
</div>
|
||||
|
||||
<div class="if-pylonish">
|
||||
<label>Reported cell count (0 for default): </label>
|
||||
<input name='INVCELLS' type='text' value="%INVCELLS%" pattern="^[0-9]+$" />
|
||||
</div>
|
||||
|
||||
<div class="if-pylonish if-solax">
|
||||
<label>Reported module count (0 for default): </label>
|
||||
<input name='INVMODULES' type='text' value="%INVMODULES%" pattern="^[0-9]+$" />
|
||||
</div>
|
||||
|
||||
<div class="if-pylonish">
|
||||
<label>Reported cells per module (0 for default): </label>
|
||||
<input name='INVCELLSPER' type='text' value="%INVCELLSPER%" pattern="^[0-9]+$" />
|
||||
|
||||
<label>Reported voltage level (0 for default): </label>
|
||||
<input name='INVVLEVEL' type='text' value="%INVVLEVEL%" pattern="^[0-9]+$" />
|
||||
|
||||
<label>Reported Ah capacity (0 for default): </label>
|
||||
<input name='INVCAPACITY' type='text' value="%INVCAPACITY%" pattern="^[0-9]+$" />
|
||||
</div>
|
||||
|
||||
<div class="if-solax">
|
||||
<label>Reported battery type (in decimal): </label>
|
||||
<input name='INVBTYPE' type='text' value="%INVBTYPE%" pattern="^[0-9]+$" />
|
||||
|
||||
<label>Inverter should ignore contactors: </label>
|
||||
<input type='checkbox' name='INVICNT' value='on' style='margin-left: 0;' %INVICNT% />
|
||||
</div>
|
||||
|
||||
<label>Charger: </label><select name='charger'>
|
||||
%CHGTYPE%
|
||||
</select>
|
||||
|
@ -827,8 +904,6 @@ const char* getCANInterfaceName(CAN_Interface interface) {
|
|||
|
||||
<h4 style='color: white;' class="%INVCLASS%">Inverter interface: <span id='Inverter'>%INVINTF%</span></h4>
|
||||
|
||||
<h4 style='color: white;' class="%INVBIDCLASS%">Battery ID: <span>%INVBID%</span> <button onclick='editSofarID()'>Edit</button></h4>
|
||||
|
||||
<h4 style='color: white;' class="%SHUNTCLASS%">Shunt interface: <span id='Inverter'>%SHUNTINTF%</span></h4>
|
||||
|
||||
</div>
|
||||
|
|
|
@ -414,7 +414,7 @@ void init_webserver() {
|
|||
|
||||
const char* boolSettingNames[] = {
|
||||
"DBLBTR", "CNTCTRL", "CNTCTRLDBL", "PWMCNTCTRL", "PERBMSRESET", "REMBMSRESET",
|
||||
"CANFDASCAN", "WIFIAPENABLED", "MQTTENABLED", "HADISC", "MQTTTOPICS",
|
||||
"CANFDASCAN", "WIFIAPENABLED", "MQTTENABLED", "HADISC", "MQTTTOPICS", "INVICNT",
|
||||
};
|
||||
|
||||
// Handles the form POST from UI to save settings of the common image
|
||||
|
@ -494,6 +494,27 @@ void init_webserver() {
|
|||
settings.saveString("MQTTDEVICENAME", p->value().c_str());
|
||||
} else if (p->name() == "HADEVICEID") {
|
||||
settings.saveString("HADEVICEID", p->value().c_str());
|
||||
} else if (p->name() == "SOFAR_ID") {
|
||||
auto type = atoi(p->value().c_str());
|
||||
settings.saveUInt("SOFAR_ID", type);
|
||||
} else if (p->name() == "INVCELLS") {
|
||||
auto type = atoi(p->value().c_str());
|
||||
settings.saveUInt("INVCELLS", type);
|
||||
} else if (p->name() == "INVMODULES") {
|
||||
auto type = atoi(p->value().c_str());
|
||||
settings.saveUInt("INVMODULES", type);
|
||||
} else if (p->name() == "INVCELLSPER") {
|
||||
auto type = atoi(p->value().c_str());
|
||||
settings.saveUInt("INVCELLSPER", type);
|
||||
} else if (p->name() == "INVVLEVEL") {
|
||||
auto type = atoi(p->value().c_str());
|
||||
settings.saveUInt("INVVLEVEL", type);
|
||||
} else if (p->name() == "INVCAPACITY") {
|
||||
auto type = atoi(p->value().c_str());
|
||||
settings.saveUInt("INVCAPACITY", type);
|
||||
} else if (p->name() == "INVBTYPE") {
|
||||
auto type = atoi(p->value().c_str());
|
||||
settings.saveUInt("INVBTYPE", (int)type);
|
||||
}
|
||||
|
||||
for (auto& boolSetting : boolSettings) {
|
||||
|
@ -579,10 +600,6 @@ void init_webserver() {
|
|||
update_string_setting(route, [setter](String value) { setter(value.toInt()); });
|
||||
};
|
||||
|
||||
// Route for editing Sofar ID
|
||||
update_int_setting("/updateSofarID",
|
||||
[](int value) { datalayer.battery.settings.sofar_user_specified_battery_id = value; });
|
||||
|
||||
// Route for editing Wh
|
||||
update_int_setting("/updateBatterySize", [](int value) { datalayer.battery.info.total_capacity_Wh = value; });
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#include "FERROAMP-CAN.h"
|
||||
#include "../communication/can/comm_can.h"
|
||||
#include "../datalayer/datalayer.h"
|
||||
#include "../inverter/INVERTERS.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)
|
||||
|
@ -364,3 +365,38 @@ void FerroampCanInverter::send_system_data() { //System equipment information
|
|||
transmit_can_frame(&PYLON_4291);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool FerroampCanInverter::setup() {
|
||||
if (user_selected_inverter_cells > 0) {
|
||||
PYLON_7320.data.u8[0] = user_selected_inverter_cells & 0xff;
|
||||
PYLON_7320.data.u8[1] = (uint8_t)(user_selected_inverter_cells >> 8);
|
||||
PYLON_7321.data.u8[0] = user_selected_inverter_cells & 0xff;
|
||||
PYLON_7321.data.u8[1] = (uint8_t)(user_selected_inverter_cells >> 8);
|
||||
}
|
||||
|
||||
if (user_selected_inverter_modules > 0) {
|
||||
PYLON_7320.data.u8[2] = user_selected_inverter_modules;
|
||||
PYLON_7321.data.u8[2] = user_selected_inverter_modules;
|
||||
}
|
||||
|
||||
if (user_selected_inverter_cells_per_module > 0) {
|
||||
PYLON_7320.data.u8[3] = user_selected_inverter_cells_per_module;
|
||||
PYLON_7321.data.u8[3] = user_selected_inverter_cells_per_module;
|
||||
}
|
||||
|
||||
if (user_selected_inverter_voltage_level > 0) {
|
||||
PYLON_7320.data.u8[4] = user_selected_inverter_voltage_level & 0xff;
|
||||
PYLON_7320.data.u8[5] = (uint8_t)(user_selected_inverter_voltage_level >> 8);
|
||||
PYLON_7321.data.u8[4] = user_selected_inverter_voltage_level & 0xff;
|
||||
PYLON_7321.data.u8[5] = (uint8_t)(user_selected_inverter_voltage_level >> 8);
|
||||
}
|
||||
|
||||
if (user_selected_inverter_ah_capacity > 0) {
|
||||
PYLON_7320.data.u8[6] = user_selected_inverter_ah_capacity & 0xff;
|
||||
PYLON_7320.data.u8[7] = (uint8_t)(user_selected_inverter_ah_capacity >> 8);
|
||||
PYLON_7321.data.u8[6] = user_selected_inverter_ah_capacity & 0xff;
|
||||
PYLON_7321.data.u8[7] = (uint8_t)(user_selected_inverter_ah_capacity >> 8);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
class FerroampCanInverter : public CanInverterProtocol {
|
||||
public:
|
||||
const char* name() override { return Name; }
|
||||
bool setup() override;
|
||||
void update_values();
|
||||
void transmit_can(unsigned long currentMillis);
|
||||
void map_can_frame_to_variable(CAN_frame rx_frame);
|
||||
|
|
|
@ -4,6 +4,17 @@ InverterProtocol* inverter = nullptr;
|
|||
|
||||
InverterProtocolType user_selected_inverter_protocol = InverterProtocolType::BydModbus;
|
||||
|
||||
// Some user-configurable settings that can be used by inverters. These
|
||||
// inverters should use sensible defaults if the corresponding user_selected
|
||||
// value is zero.
|
||||
uint16_t user_selected_inverter_cells = 0;
|
||||
uint16_t user_selected_inverter_modules = 0;
|
||||
uint16_t user_selected_inverter_cells_per_module = 0;
|
||||
uint16_t user_selected_inverter_voltage_level = 0;
|
||||
uint16_t user_selected_inverter_ah_capacity = 0;
|
||||
uint16_t user_selected_inverter_battery_type = 0;
|
||||
bool user_selected_inverter_ignore_contactors = false;
|
||||
|
||||
std::vector<InverterProtocolType> supported_inverter_protocols() {
|
||||
std::vector<InverterProtocolType> types;
|
||||
|
||||
|
|
|
@ -36,4 +36,12 @@ extern InverterProtocol* inverter;
|
|||
// Call to initialize the build-time selected inverter. Safe to call even though inverter was not selected.
|
||||
bool setup_inverter();
|
||||
|
||||
extern uint16_t user_selected_inverter_cells;
|
||||
extern uint16_t user_selected_inverter_modules;
|
||||
extern uint16_t user_selected_inverter_cells_per_module;
|
||||
extern uint16_t user_selected_inverter_voltage_level;
|
||||
extern uint16_t user_selected_inverter_ah_capacity;
|
||||
extern uint16_t user_selected_inverter_battery_type;
|
||||
extern bool user_selected_inverter_ignore_contactors;
|
||||
|
||||
#endif
|
||||
|
|
|
@ -5,27 +5,27 @@
|
|||
|
||||
enum class InverterProtocolType {
|
||||
None = 0,
|
||||
AforeCan,
|
||||
BydCan,
|
||||
BydModbus,
|
||||
FerroampCan,
|
||||
Foxess,
|
||||
GrowattHv,
|
||||
GrowattLv,
|
||||
GrowattWit,
|
||||
Kostal,
|
||||
Pylon,
|
||||
PylonLv,
|
||||
Schneider,
|
||||
SmaBydH,
|
||||
SmaBydHvs,
|
||||
SmaLv,
|
||||
SmaTripower,
|
||||
Sofar,
|
||||
Solax,
|
||||
Solxpow,
|
||||
SolArkLv,
|
||||
Sungrow,
|
||||
AforeCan = 1,
|
||||
BydCan = 2,
|
||||
BydModbus = 3,
|
||||
FerroampCan = 4,
|
||||
Foxess = 5,
|
||||
GrowattHv = 6,
|
||||
GrowattLv = 7,
|
||||
GrowattWit = 8,
|
||||
Kostal = 9,
|
||||
Pylon = 10,
|
||||
PylonLv = 11,
|
||||
Schneider = 12,
|
||||
SmaBydH = 13,
|
||||
SmaBydHvs = 14,
|
||||
SmaLv = 15,
|
||||
SmaTripower = 16,
|
||||
Sofar = 17,
|
||||
Solax = 18,
|
||||
Solxpow = 19,
|
||||
SolArkLv = 20,
|
||||
Sungrow = 21,
|
||||
Highest
|
||||
};
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#include "PYLON-CAN.h"
|
||||
#include "../communication/can/comm_can.h"
|
||||
#include "../datalayer/datalayer.h"
|
||||
#include "../inverter/INVERTERS.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)
|
||||
|
@ -351,3 +352,38 @@ void PylonInverter::send_system_data() { //System equipment information
|
|||
transmit_can_frame(&PYLON_4291);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool PylonInverter::setup() {
|
||||
if (user_selected_inverter_cells > 0) {
|
||||
PYLON_7320.data.u8[0] = user_selected_inverter_cells & 0xff;
|
||||
PYLON_7320.data.u8[1] = (uint8_t)(user_selected_inverter_cells >> 8);
|
||||
PYLON_7321.data.u8[0] = user_selected_inverter_cells & 0xff;
|
||||
PYLON_7321.data.u8[1] = (uint8_t)(user_selected_inverter_cells >> 8);
|
||||
}
|
||||
|
||||
if (user_selected_inverter_modules > 0) {
|
||||
PYLON_7320.data.u8[2] = user_selected_inverter_modules;
|
||||
PYLON_7321.data.u8[2] = user_selected_inverter_modules;
|
||||
}
|
||||
|
||||
if (user_selected_inverter_cells_per_module > 0) {
|
||||
PYLON_7320.data.u8[3] = user_selected_inverter_cells_per_module;
|
||||
PYLON_7321.data.u8[3] = user_selected_inverter_cells_per_module;
|
||||
}
|
||||
|
||||
if (user_selected_inverter_voltage_level > 0) {
|
||||
PYLON_7320.data.u8[4] = user_selected_inverter_voltage_level & 0xff;
|
||||
PYLON_7320.data.u8[5] = (uint8_t)(user_selected_inverter_voltage_level >> 8);
|
||||
PYLON_7321.data.u8[4] = user_selected_inverter_voltage_level & 0xff;
|
||||
PYLON_7321.data.u8[5] = (uint8_t)(user_selected_inverter_voltage_level >> 8);
|
||||
}
|
||||
|
||||
if (user_selected_inverter_ah_capacity > 0) {
|
||||
PYLON_7320.data.u8[6] = user_selected_inverter_ah_capacity & 0xff;
|
||||
PYLON_7320.data.u8[7] = (uint8_t)(user_selected_inverter_ah_capacity >> 8);
|
||||
PYLON_7321.data.u8[6] = user_selected_inverter_ah_capacity & 0xff;
|
||||
PYLON_7321.data.u8[7] = (uint8_t)(user_selected_inverter_ah_capacity >> 8);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
class PylonInverter : public CanInverterProtocol {
|
||||
public:
|
||||
const char* name() override { return Name; }
|
||||
bool setup() override;
|
||||
void update_values();
|
||||
void transmit_can(unsigned long currentMillis);
|
||||
void map_can_frame_to_variable(CAN_frame rx_frame);
|
||||
|
|
|
@ -4,11 +4,7 @@
|
|||
#include "../datalayer/datalayer.h"
|
||||
#include "../devboard/utils/events.h"
|
||||
#include "../devboard/utils/logging.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
|
||||
#include "../inverter/INVERTERS.h"
|
||||
|
||||
// __builtin_bswap64 needed to convert to ESP32 little endian format
|
||||
// Byte[4] defines the requested contactor state: 1 = Closed , 0 = Open
|
||||
|
@ -18,7 +14,7 @@
|
|||
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) {
|
||||
if (millis() - LastFrameTime >= SolaxTimeout && !configured_ignore_contactors) {
|
||||
datalayer.system.status.inverter_allows_contactor_closing = false;
|
||||
STATE = BATTERY_ANNOUNCE;
|
||||
}
|
||||
|
@ -73,7 +69,7 @@ void SolaxInverter::
|
|||
//BMS_Status
|
||||
SOLAX_1875.data.u8[0] = (uint8_t)temperature_average;
|
||||
SOLAX_1875.data.u8[1] = (temperature_average >> 8);
|
||||
SOLAX_1875.data.u8[2] = (uint8_t)NUMBER_OF_MODULES; // Number of slave batteries
|
||||
SOLAX_1875.data.u8[2] = (uint8_t)configured_number_of_modules; // Number of slave batteries
|
||||
SOLAX_1875.data.u8[4] = (uint8_t)0; // Contactor Status 0=off, 1=on.
|
||||
|
||||
//BMS_PackTemps (strange name, since it has voltages?)
|
||||
|
@ -88,7 +84,7 @@ void SolaxInverter::
|
|||
SOLAX_1876.data.u8[7] = (datalayer.battery.status.cell_min_voltage_mV >> 8);
|
||||
|
||||
//Unknown
|
||||
SOLAX_1877.data.u8[4] = (uint8_t)BATTERY_TYPE; // Battery type (Default 0x50)
|
||||
SOLAX_1877.data.u8[4] = (uint8_t)configured_battery_type; // Battery type (Default 0x50)
|
||||
SOLAX_1877.data.u8[6] = (uint8_t)0x22; // Firmware version?
|
||||
SOLAX_1877.data.u8[7] =
|
||||
(uint8_t)0x02; // The above firmware version applies to:02 = Master BMS, 10 = S1, 20 = S2, 30 = S3, 40 = S4
|
||||
|
@ -129,6 +125,26 @@ void SolaxInverter::map_can_frame_to_variable(CAN_frame rx_frame) {
|
|||
if (rx_frame.ID == 0x1871 && rx_frame.data.u8[0] == (0x01) ||
|
||||
rx_frame.ID == 0x1871 && rx_frame.data.u8[0] == (0x02)) {
|
||||
LastFrameTime = millis();
|
||||
|
||||
if (configured_ignore_contactors) {
|
||||
// Skip the state machine since we're not going to open/close contactors,
|
||||
// and the Solax would otherwise wait forever for us to do so.
|
||||
|
||||
datalayer.system.status.inverter_allows_contactor_closing = true;
|
||||
SOLAX_1875.data.u8[4] = (0x01); // Inform Inverter: Contactor 0=off, 1=on.
|
||||
transmit_can_frame(&SOLAX_187E);
|
||||
transmit_can_frame(&SOLAX_187A);
|
||||
transmit_can_frame(&SOLAX_1872);
|
||||
transmit_can_frame(&SOLAX_1873);
|
||||
transmit_can_frame(&SOLAX_1874);
|
||||
transmit_can_frame(&SOLAX_1875);
|
||||
transmit_can_frame(&SOLAX_1876);
|
||||
transmit_can_frame(&SOLAX_1877);
|
||||
transmit_can_frame(&SOLAX_1878);
|
||||
transmit_can_frame(&SOLAX_100A001);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (STATE) {
|
||||
case (BATTERY_ANNOUNCE):
|
||||
#ifdef DEBUG_LOG
|
||||
|
@ -209,6 +225,25 @@ void SolaxInverter::map_can_frame_to_variable(CAN_frame rx_frame) {
|
|||
}
|
||||
|
||||
bool SolaxInverter::setup(void) { // Performs one time setup at startup
|
||||
// Use user selected values if nonzero, otherwise use defaults
|
||||
if (user_selected_inverter_modules > 0) {
|
||||
configured_number_of_modules = user_selected_inverter_modules;
|
||||
} else {
|
||||
configured_number_of_modules = NUMBER_OF_MODULES;
|
||||
}
|
||||
|
||||
if (user_selected_inverter_battery_type > 0) {
|
||||
configured_battery_type = user_selected_inverter_battery_type;
|
||||
} else {
|
||||
configured_battery_type = BATTERY_TYPE;
|
||||
}
|
||||
|
||||
configured_ignore_contactors = user_selected_inverter_ignore_contactors;
|
||||
|
||||
if (!configured_ignore_contactors) {
|
||||
// Only prevent closing if we're not ignoring contactors
|
||||
datalayer.system.status.inverter_allows_contactor_closing = false; // The inverter needs to allow first
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -17,6 +17,11 @@ class SolaxInverter : public CanInverterProtocol {
|
|||
static constexpr const char* Name = "SolaX Triple Power LFP over CAN bus";
|
||||
|
||||
private:
|
||||
static const int NUMBER_OF_MODULES = 0;
|
||||
static const int 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
|
||||
|
||||
// Timeout in milliseconds
|
||||
static const int SolaxTimeout = 2000;
|
||||
|
||||
|
@ -34,6 +39,13 @@ class SolaxInverter : public CanInverterProtocol {
|
|||
uint16_t capped_capacity_Wh;
|
||||
uint16_t capped_remaining_capacity_Wh;
|
||||
|
||||
int configured_number_of_modules = 0;
|
||||
int configured_battery_type = 0;
|
||||
// If true, the integration will ignore the inverter's requests to open the
|
||||
// battery contactors. Useful for batteries that can't open contactors on
|
||||
// request.
|
||||
bool configured_ignore_contactors = false;
|
||||
|
||||
//CAN message translations from this amazing repository: https://github.com/rand12345/solax_can_bus
|
||||
|
||||
CAN_frame SOLAX_1801 = {.FD = false,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#include "SOLXPOW-CAN.h"
|
||||
#include "../communication/can/comm_can.h"
|
||||
#include "../datalayer/datalayer.h"
|
||||
#include "../inverter/INVERTERS.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)
|
||||
|
@ -353,3 +354,38 @@ void SolxpowInverter::send_system_data() { //System equipment information
|
|||
transmit_can_frame(&SOLXPOW_4291);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool SolxpowInverter::setup() {
|
||||
if (user_selected_inverter_cells > 0) {
|
||||
SOLXPOW_7320.data.u8[0] = user_selected_inverter_cells & 0xff;
|
||||
SOLXPOW_7320.data.u8[1] = (uint8_t)(user_selected_inverter_cells >> 8);
|
||||
SOLXPOW_7321.data.u8[0] = user_selected_inverter_cells & 0xff;
|
||||
SOLXPOW_7321.data.u8[1] = (uint8_t)(user_selected_inverter_cells >> 8);
|
||||
}
|
||||
|
||||
if (user_selected_inverter_modules > 0) {
|
||||
SOLXPOW_7320.data.u8[2] = user_selected_inverter_modules;
|
||||
SOLXPOW_7321.data.u8[2] = user_selected_inverter_modules;
|
||||
}
|
||||
|
||||
if (user_selected_inverter_cells_per_module > 0) {
|
||||
SOLXPOW_7320.data.u8[3] = user_selected_inverter_cells_per_module;
|
||||
SOLXPOW_7321.data.u8[3] = user_selected_inverter_cells_per_module;
|
||||
}
|
||||
|
||||
if (user_selected_inverter_voltage_level > 0) {
|
||||
SOLXPOW_7320.data.u8[4] = user_selected_inverter_voltage_level & 0xff;
|
||||
SOLXPOW_7320.data.u8[5] = (uint8_t)(user_selected_inverter_voltage_level >> 8);
|
||||
SOLXPOW_7321.data.u8[4] = user_selected_inverter_voltage_level & 0xff;
|
||||
SOLXPOW_7321.data.u8[5] = (uint8_t)(user_selected_inverter_voltage_level >> 8);
|
||||
}
|
||||
|
||||
if (user_selected_inverter_ah_capacity > 0) {
|
||||
SOLXPOW_7320.data.u8[6] = user_selected_inverter_ah_capacity & 0xff;
|
||||
SOLXPOW_7320.data.u8[7] = (uint8_t)(user_selected_inverter_ah_capacity >> 8);
|
||||
SOLXPOW_7321.data.u8[6] = user_selected_inverter_ah_capacity & 0xff;
|
||||
SOLXPOW_7321.data.u8[7] = (uint8_t)(user_selected_inverter_ah_capacity >> 8);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
class SolxpowInverter : public CanInverterProtocol {
|
||||
public:
|
||||
const char* name() override { return Name; }
|
||||
bool setup() override;
|
||||
void update_values();
|
||||
void transmit_can(unsigned long currentMillis);
|
||||
void map_can_frame_to_variable(CAN_frame rx_frame);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue