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_min_pack_voltage_dV = settings.getUInt("BATTPVMIN", 0);
|
||||||
user_selected_max_cell_voltage_mV = settings.getUInt("BATTCVMAX", 0);
|
user_selected_max_cell_voltage_mV = settings.getUInt("BATTCVMAX", 0);
|
||||||
user_selected_min_cell_voltage_mV = settings.getUInt("BATTCVMIN", 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 readIf = [](const char* settingName) {
|
||||||
auto batt1If = (comm_interface)settings.getUInt(settingName, (int)comm_interface::CanNative);
|
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)) {
|
if (!settings.putUInt("TARGETDISCHVOLT", datalayer.battery.settings.max_user_set_discharge_voltage_dV)) {
|
||||||
set_event(EVENT_PERSISTENT_SAVE_INFO, 11);
|
set_event(EVENT_PERSISTENT_SAVE_INFO, 11);
|
||||||
}
|
}
|
||||||
if (!settings.putUInt("SOFAR_ID", 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, 12);
|
|
||||||
}
|
|
||||||
if (!settings.putUInt("BMSRESETDUR", datalayer.battery.settings.sofar_user_specified_battery_id)) {
|
|
||||||
set_event(EVENT_PERSISTENT_SAVE_INFO, 13);
|
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);
|
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();
|
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
|
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();}}
|
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):');
|
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
|
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{
|
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;
|
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 .if-mqtt { display: none; }
|
||||||
form[data-mqttenabled="true"] .if-mqtt {
|
form[data-mqttenabled="true"] .if-mqtt {
|
||||||
display: contents;
|
display: contents;
|
||||||
|
@ -726,6 +769,40 @@ const char* getCANInterfaceName(CAN_Interface interface) {
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</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'>
|
<label>Charger: </label><select name='charger'>
|
||||||
%CHGTYPE%
|
%CHGTYPE%
|
||||||
</select>
|
</select>
|
||||||
|
@ -826,8 +903,6 @@ const char* getCANInterfaceName(CAN_Interface interface) {
|
||||||
<h4 style='color: white;' class="%BATTERY2CLASS%">Battery interface: <span id='Battery2'>%BATTERY2INTF%</span></h4>
|
<h4 style='color: white;' class="%BATTERY2CLASS%">Battery interface: <span id='Battery2'>%BATTERY2INTF%</span></h4>
|
||||||
|
|
||||||
<h4 style='color: white;' class="%INVCLASS%">Inverter interface: <span id='Inverter'>%INVINTF%</span></h4>
|
<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>
|
<h4 style='color: white;' class="%SHUNTCLASS%">Shunt interface: <span id='Inverter'>%SHUNTINTF%</span></h4>
|
||||||
|
|
||||||
|
|
|
@ -414,7 +414,7 @@ void init_webserver() {
|
||||||
|
|
||||||
const char* boolSettingNames[] = {
|
const char* boolSettingNames[] = {
|
||||||
"DBLBTR", "CNTCTRL", "CNTCTRLDBL", "PWMCNTCTRL", "PERBMSRESET", "REMBMSRESET",
|
"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
|
// 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());
|
settings.saveString("MQTTDEVICENAME", p->value().c_str());
|
||||||
} else if (p->name() == "HADEVICEID") {
|
} else if (p->name() == "HADEVICEID") {
|
||||||
settings.saveString("HADEVICEID", p->value().c_str());
|
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) {
|
for (auto& boolSetting : boolSettings) {
|
||||||
|
@ -579,10 +600,6 @@ void init_webserver() {
|
||||||
update_string_setting(route, [setter](String value) { setter(value.toInt()); });
|
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
|
// Route for editing Wh
|
||||||
update_int_setting("/updateBatterySize", [](int value) { datalayer.battery.info.total_capacity_Wh = value; });
|
update_int_setting("/updateBatterySize", [](int value) { datalayer.battery.info.total_capacity_Wh = value; });
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#include "FERROAMP-CAN.h"
|
#include "FERROAMP-CAN.h"
|
||||||
#include "../communication/can/comm_can.h"
|
#include "../communication/can/comm_can.h"
|
||||||
#include "../datalayer/datalayer.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_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 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);
|
transmit_can_frame(&PYLON_4291);
|
||||||
#endif
|
#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 {
|
class FerroampCanInverter : public CanInverterProtocol {
|
||||||
public:
|
public:
|
||||||
const char* name() override { return Name; }
|
const char* name() override { return Name; }
|
||||||
|
bool setup() override;
|
||||||
void update_values();
|
void update_values();
|
||||||
void transmit_can(unsigned long currentMillis);
|
void transmit_can(unsigned long currentMillis);
|
||||||
void map_can_frame_to_variable(CAN_frame rx_frame);
|
void map_can_frame_to_variable(CAN_frame rx_frame);
|
||||||
|
|
|
@ -4,6 +4,17 @@ InverterProtocol* inverter = nullptr;
|
||||||
|
|
||||||
InverterProtocolType user_selected_inverter_protocol = InverterProtocolType::BydModbus;
|
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> supported_inverter_protocols() {
|
||||||
std::vector<InverterProtocolType> types;
|
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.
|
// Call to initialize the build-time selected inverter. Safe to call even though inverter was not selected.
|
||||||
bool setup_inverter();
|
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
|
#endif
|
||||||
|
|
|
@ -5,27 +5,27 @@
|
||||||
|
|
||||||
enum class InverterProtocolType {
|
enum class InverterProtocolType {
|
||||||
None = 0,
|
None = 0,
|
||||||
AforeCan,
|
AforeCan = 1,
|
||||||
BydCan,
|
BydCan = 2,
|
||||||
BydModbus,
|
BydModbus = 3,
|
||||||
FerroampCan,
|
FerroampCan = 4,
|
||||||
Foxess,
|
Foxess = 5,
|
||||||
GrowattHv,
|
GrowattHv = 6,
|
||||||
GrowattLv,
|
GrowattLv = 7,
|
||||||
GrowattWit,
|
GrowattWit = 8,
|
||||||
Kostal,
|
Kostal = 9,
|
||||||
Pylon,
|
Pylon = 10,
|
||||||
PylonLv,
|
PylonLv = 11,
|
||||||
Schneider,
|
Schneider = 12,
|
||||||
SmaBydH,
|
SmaBydH = 13,
|
||||||
SmaBydHvs,
|
SmaBydHvs = 14,
|
||||||
SmaLv,
|
SmaLv = 15,
|
||||||
SmaTripower,
|
SmaTripower = 16,
|
||||||
Sofar,
|
Sofar = 17,
|
||||||
Solax,
|
Solax = 18,
|
||||||
Solxpow,
|
Solxpow = 19,
|
||||||
SolArkLv,
|
SolArkLv = 20,
|
||||||
Sungrow,
|
Sungrow = 21,
|
||||||
Highest
|
Highest
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#include "PYLON-CAN.h"
|
#include "PYLON-CAN.h"
|
||||||
#include "../communication/can/comm_can.h"
|
#include "../communication/can/comm_can.h"
|
||||||
#include "../datalayer/datalayer.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_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 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);
|
transmit_can_frame(&PYLON_4291);
|
||||||
#endif
|
#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 {
|
class PylonInverter : public CanInverterProtocol {
|
||||||
public:
|
public:
|
||||||
const char* name() override { return Name; }
|
const char* name() override { return Name; }
|
||||||
|
bool setup() override;
|
||||||
void update_values();
|
void update_values();
|
||||||
void transmit_can(unsigned long currentMillis);
|
void transmit_can(unsigned long currentMillis);
|
||||||
void map_can_frame_to_variable(CAN_frame rx_frame);
|
void map_can_frame_to_variable(CAN_frame rx_frame);
|
||||||
|
|
|
@ -4,11 +4,7 @@
|
||||||
#include "../datalayer/datalayer.h"
|
#include "../datalayer/datalayer.h"
|
||||||
#include "../devboard/utils/events.h"
|
#include "../devboard/utils/events.h"
|
||||||
#include "../devboard/utils/logging.h"
|
#include "../devboard/utils/logging.h"
|
||||||
|
#include "../inverter/INVERTERS.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
|
|
||||||
|
|
||||||
// __builtin_bswap64 needed to convert to ESP32 little endian format
|
// __builtin_bswap64 needed to convert to ESP32 little endian format
|
||||||
// Byte[4] defines the requested contactor state: 1 = Closed , 0 = Open
|
// Byte[4] defines the requested contactor state: 1 = Closed , 0 = Open
|
||||||
|
@ -18,7 +14,7 @@
|
||||||
void SolaxInverter::
|
void SolaxInverter::
|
||||||
update_values() { //This function maps all the values fetched from battery CAN to the correct CAN messages
|
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 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;
|
datalayer.system.status.inverter_allows_contactor_closing = false;
|
||||||
STATE = BATTERY_ANNOUNCE;
|
STATE = BATTERY_ANNOUNCE;
|
||||||
}
|
}
|
||||||
|
@ -73,8 +69,8 @@ void SolaxInverter::
|
||||||
//BMS_Status
|
//BMS_Status
|
||||||
SOLAX_1875.data.u8[0] = (uint8_t)temperature_average;
|
SOLAX_1875.data.u8[0] = (uint8_t)temperature_average;
|
||||||
SOLAX_1875.data.u8[1] = (temperature_average >> 8);
|
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.
|
SOLAX_1875.data.u8[4] = (uint8_t)0; // Contactor Status 0=off, 1=on.
|
||||||
|
|
||||||
//BMS_PackTemps (strange name, since it has voltages?)
|
//BMS_PackTemps (strange name, since it has voltages?)
|
||||||
SOLAX_1876.data.u8[0] = (int8_t)datalayer.battery.status.temperature_max_dC;
|
SOLAX_1876.data.u8[0] = (int8_t)datalayer.battery.status.temperature_max_dC;
|
||||||
|
@ -88,8 +84,8 @@ void SolaxInverter::
|
||||||
SOLAX_1876.data.u8[7] = (datalayer.battery.status.cell_min_voltage_mV >> 8);
|
SOLAX_1876.data.u8[7] = (datalayer.battery.status.cell_min_voltage_mV >> 8);
|
||||||
|
|
||||||
//Unknown
|
//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[6] = (uint8_t)0x22; // Firmware version?
|
||||||
SOLAX_1877.data.u8[7] =
|
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
|
(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) ||
|
if (rx_frame.ID == 0x1871 && rx_frame.data.u8[0] == (0x01) ||
|
||||||
rx_frame.ID == 0x1871 && rx_frame.data.u8[0] == (0x02)) {
|
rx_frame.ID == 0x1871 && rx_frame.data.u8[0] == (0x02)) {
|
||||||
LastFrameTime = millis();
|
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) {
|
switch (STATE) {
|
||||||
case (BATTERY_ANNOUNCE):
|
case (BATTERY_ANNOUNCE):
|
||||||
#ifdef DEBUG_LOG
|
#ifdef DEBUG_LOG
|
||||||
|
@ -208,7 +224,26 @@ void SolaxInverter::map_can_frame_to_variable(CAN_frame rx_frame) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SolaxInverter::setup(void) { // Performs one time setup at startup
|
bool SolaxInverter::setup(void) { // Performs one time setup at startup
|
||||||
datalayer.system.status.inverter_allows_contactor_closing = false; // The inverter needs to allow first
|
// 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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,11 @@ class SolaxInverter : public CanInverterProtocol {
|
||||||
static constexpr const char* Name = "SolaX Triple Power LFP over CAN bus";
|
static constexpr const char* Name = "SolaX Triple Power LFP over CAN bus";
|
||||||
|
|
||||||
private:
|
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
|
// Timeout in milliseconds
|
||||||
static const int SolaxTimeout = 2000;
|
static const int SolaxTimeout = 2000;
|
||||||
|
|
||||||
|
@ -34,6 +39,13 @@ class SolaxInverter : public CanInverterProtocol {
|
||||||
uint16_t capped_capacity_Wh;
|
uint16_t capped_capacity_Wh;
|
||||||
uint16_t capped_remaining_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 message translations from this amazing repository: https://github.com/rand12345/solax_can_bus
|
||||||
|
|
||||||
CAN_frame SOLAX_1801 = {.FD = false,
|
CAN_frame SOLAX_1801 = {.FD = false,
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#include "SOLXPOW-CAN.h"
|
#include "SOLXPOW-CAN.h"
|
||||||
#include "../communication/can/comm_can.h"
|
#include "../communication/can/comm_can.h"
|
||||||
#include "../datalayer/datalayer.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_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 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);
|
transmit_can_frame(&SOLXPOW_4291);
|
||||||
#endif
|
#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 {
|
class SolxpowInverter : public CanInverterProtocol {
|
||||||
public:
|
public:
|
||||||
const char* name() override { return Name; }
|
const char* name() override { return Name; }
|
||||||
|
bool setup() override;
|
||||||
void update_values();
|
void update_values();
|
||||||
void transmit_can(unsigned long currentMillis);
|
void transmit_can(unsigned long currentMillis);
|
||||||
void map_can_frame_to_variable(CAN_frame rx_frame);
|
void map_can_frame_to_variable(CAN_frame rx_frame);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue