Merge pull request #1425 from jonny5532/feature/more-inverter-settings

Make more inverter settings configurable
This commit is contained in:
Daniel Öster 2025-08-25 20:01:37 +03:00 committed by GitHub
commit 4a24f3558f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 321 additions and 48 deletions

View file

@ -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);
}

View file

@ -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>

View file

@ -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; });

View file

@ -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;
}

View file

@ -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);

View file

@ -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;

View file

@ -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

View file

@ -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
};

View file

@ -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;
}

View file

@ -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);

View file

@ -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,8 +69,8 @@ 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[4] = (uint8_t)0; // Contactor Status 0=off, 1=on.
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?)
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);
//Unknown
SOLAX_1877.data.u8[4] = (uint8_t)BATTERY_TYPE; // Battery type (Default 0x50)
SOLAX_1877.data.u8[6] = (uint8_t)0x22; // Firmware version?
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
@ -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
datalayer.system.status.inverter_allows_contactor_closing = false; // The inverter needs to allow first
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;
}

View file

@ -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,

View file

@ -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;
}

View file

@ -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);