mirror of
https://github.com/dalathegreat/Battery-Emulator.git
synced 2025-10-03 17:59:27 +02:00
Merge from main and fix conflicts
This commit is contained in:
commit
a131fca7c1
8 changed files with 128 additions and 10 deletions
|
@ -1290,7 +1290,7 @@ void MebBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
|
||||||
|
|
||||||
void MebBattery::transmit_can(unsigned long currentMillis) {
|
void MebBattery::transmit_can(unsigned long currentMillis) {
|
||||||
|
|
||||||
if (currentMillis > last_can_msg_timestamp + 500) {
|
if (currentMillis - last_can_msg_timestamp > 500) {
|
||||||
#ifdef DEBUG_LOG
|
#ifdef DEBUG_LOG
|
||||||
if (first_can_msg)
|
if (first_can_msg)
|
||||||
logging.printf("MEB: No CAN msg received for 500ms\n");
|
logging.printf("MEB: No CAN msg received for 500ms\n");
|
||||||
|
|
|
@ -72,6 +72,10 @@ void init_stored_settings() {
|
||||||
datalayer.battery.settings.max_user_set_discharge_voltage_dV = temp;
|
datalayer.battery.settings.max_user_set_discharge_voltage_dV = temp;
|
||||||
}
|
}
|
||||||
datalayer.battery.settings.user_set_voltage_limits_active = settings.getBool("USEVOLTLIMITS", false);
|
datalayer.battery.settings.user_set_voltage_limits_active = settings.getBool("USEVOLTLIMITS", false);
|
||||||
|
temp = settings.getUInt("SOFAR_ID", false);
|
||||||
|
if (temp < 16) {
|
||||||
|
datalayer.battery.settings.sofar_user_specified_battery_id = temp;
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef COMMON_IMAGE
|
#ifdef COMMON_IMAGE
|
||||||
user_selected_battery_type = (BatteryType)settings.getUInt("BATTTYPE", (int)BatteryType::None);
|
user_selected_battery_type = (BatteryType)settings.getUInt("BATTTYPE", (int)BatteryType::None);
|
||||||
|
@ -177,6 +181,9 @@ 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)) {
|
||||||
|
set_event(EVENT_PERSISTENT_SAVE_INFO, 12);
|
||||||
|
}
|
||||||
|
|
||||||
settings.end(); // Close preferences handle
|
settings.end(); // Close preferences handle
|
||||||
}
|
}
|
||||||
|
|
|
@ -162,6 +162,9 @@ typedef struct {
|
||||||
/* Maximum voltage for entire battery pack during forced balancing */
|
/* Maximum voltage for entire battery pack during forced balancing */
|
||||||
uint16_t balancing_max_pack_voltage_dV = 3940;
|
uint16_t balancing_max_pack_voltage_dV = 3940;
|
||||||
|
|
||||||
|
/** Sofar CAN Battery ID (0-15) used to parallel multiple packs */
|
||||||
|
uint8_t sofar_user_specified_battery_id = 0;
|
||||||
|
|
||||||
} DATALAYER_BATTERY_SETTINGS_TYPE;
|
} DATALAYER_BATTERY_SETTINGS_TYPE;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
|
|
@ -135,6 +135,18 @@ String settings_processor(const String& var, BatteryEmulatorSettingsStore& setti
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (var == "INVBIDCLASS") {
|
||||||
|
if (!inverter || !inverter->supports_battery_id()) {
|
||||||
|
return "hidden";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (var == "INVBID") {
|
||||||
|
if (inverter && inverter->supports_battery_id()) {
|
||||||
|
return String(datalayer.battery.settings.sofar_user_specified_battery_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (var == "INVINTF") {
|
if (var == "INVINTF") {
|
||||||
if (inverter) {
|
if (inverter) {
|
||||||
return inverter->interface_name();
|
return inverter->interface_name();
|
||||||
|
@ -494,6 +506,10 @@ 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-120000):');
|
function editWh(){var value=prompt('How much energy the battery can store. Enter new Wh value (1-120000):');
|
||||||
if(value!==null){if(value>=1&&value<=120000){var xhr=new
|
if(value!==null){if(value>=1&&value<=120000){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{
|
||||||
|
@ -746,6 +762,8 @@ 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="%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>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -566,6 +566,10 @@ 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; });
|
||||||
|
|
||||||
|
|
|
@ -46,6 +46,8 @@ class InverterProtocol {
|
||||||
virtual bool controls_contactor() { return false; }
|
virtual bool controls_contactor() { return false; }
|
||||||
|
|
||||||
virtual bool allows_contactor_closing() { return false; }
|
virtual bool allows_contactor_closing() { return false; }
|
||||||
|
|
||||||
|
virtual bool supports_battery_id() { return false; }
|
||||||
};
|
};
|
||||||
|
|
||||||
extern InverterProtocol* inverter;
|
extern InverterProtocol* inverter;
|
||||||
|
|
|
@ -32,19 +32,67 @@ void SofarInverter::
|
||||||
SOFAR_356.data.u8[3] = (datalayer.battery.status.current_dA >> 8);
|
SOFAR_356.data.u8[3] = (datalayer.battery.status.current_dA >> 8);
|
||||||
SOFAR_356.data.u8[4] = (datalayer.battery.status.temperature_max_dC & 0x00FF);
|
SOFAR_356.data.u8[4] = (datalayer.battery.status.temperature_max_dC & 0x00FF);
|
||||||
SOFAR_356.data.u8[5] = (datalayer.battery.status.temperature_max_dC >> 8);
|
SOFAR_356.data.u8[5] = (datalayer.battery.status.temperature_max_dC >> 8);
|
||||||
|
|
||||||
|
// frame 0x35E – Manufacturer Name ASCII
|
||||||
|
memset(SOFAR_35E.data.u8, 0, 8);
|
||||||
|
strncpy((char*)SOFAR_35E.data.u8, BatteryType, 8);
|
||||||
|
|
||||||
|
//Gets automatically rescaled with SOC scaling. Calculated with max design voltage, better would be to calculate with nominal voltage
|
||||||
|
calculated_capacity_AH =
|
||||||
|
(datalayer.battery.info.reported_total_capacity_Wh / (datalayer.battery.info.max_design_voltage_dV * 0.1));
|
||||||
|
//Battery Nominal Capacity
|
||||||
|
SOFAR_35F.data.u8[4] = calculated_capacity_AH & 0x00FF;
|
||||||
|
SOFAR_35F.data.u8[5] = (calculated_capacity_AH >> 8);
|
||||||
|
|
||||||
|
// Charge and discharge consent dependent on SoC with hysteresis at 99% soc
|
||||||
|
//SoC deception only to CAN (we do not touch datalayer)
|
||||||
|
uint16_t spoofed_soc = datalayer.battery.status.reported_soc;
|
||||||
|
if (spoofed_soc >= 10000) {
|
||||||
|
spoofed_soc = 9900; // limit to 99%
|
||||||
|
}
|
||||||
|
|
||||||
|
// Frame 0x355 – SoC and SoH
|
||||||
|
SOFAR_355.data.u8[0] = spoofed_soc / 100;
|
||||||
|
SOFAR_355.data.u8[2] = datalayer.battery.status.soh_pptt / 100;
|
||||||
|
|
||||||
|
// Set charge and discharge consent flags
|
||||||
|
uint8_t soc_percent = spoofed_soc / 100;
|
||||||
|
uint8_t enable_flags = 0x00;
|
||||||
|
|
||||||
|
if (soc_percent <= 1) {
|
||||||
|
enable_flags = 0x02; // Only charging allowed
|
||||||
|
} else if (soc_percent >= 100) {
|
||||||
|
enable_flags = 0x01; // Only discharge allowed
|
||||||
|
} else {
|
||||||
|
enable_flags = 0x03; // Both charge and discharge allowed
|
||||||
|
}
|
||||||
|
|
||||||
|
// Frame 0x30F – operation mode
|
||||||
|
SOFAR_30F.data.u8[0] = 0x00; // Normal mode
|
||||||
|
SOFAR_30F.data.u8[1] = enable_flags;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SofarInverter::map_can_frame_to_variable(CAN_frame rx_frame) {
|
void SofarInverter::map_can_frame_to_variable(CAN_frame rx_frame) {
|
||||||
switch (rx_frame.ID) { //In here we need to respond to the inverter. TODO: make logic
|
switch (rx_frame.ID) {
|
||||||
case 0x605:
|
case 0x605:
|
||||||
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
|
|
||||||
//frame1_605 = rx_frame.data.u8[1];
|
|
||||||
//frame3_605 = rx_frame.data.u8[3];
|
|
||||||
break;
|
|
||||||
case 0x705:
|
case 0x705:
|
||||||
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
|
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
|
||||||
//frame1_705 = rx_frame.data.u8[1];
|
switch (rx_frame.data.u8[0]) {
|
||||||
//frame3_705 = rx_frame.data.u8[3];
|
case 0x00:
|
||||||
|
transmit_can_frame(&SOFAR_683, can_config.inverter);
|
||||||
|
break;
|
||||||
|
case 0x01:
|
||||||
|
transmit_can_frame(&SOFAR_684, can_config.inverter);
|
||||||
|
break;
|
||||||
|
case 0x02:
|
||||||
|
transmit_can_frame(&SOFAR_685, can_config.inverter);
|
||||||
|
break;
|
||||||
|
case 0x03:
|
||||||
|
transmit_can_frame(&SOFAR_690, can_config.inverter);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
@ -66,3 +114,35 @@ void SofarInverter::transmit_can(unsigned long currentMillis) {
|
||||||
transmit_can_frame(&SOFAR_35A, can_config.inverter);
|
transmit_can_frame(&SOFAR_35A, can_config.inverter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool SofarInverter::setup() { // Performs one time setup at startup over CAN bus
|
||||||
|
// Dymanically set CAN ID according to which battery index we are on
|
||||||
|
uint16_t base_offset = (datalayer.battery.settings.sofar_user_specified_battery_id << 12);
|
||||||
|
auto init_frame = [&](CAN_frame& frame, uint16_t base_id) {
|
||||||
|
frame.FD = false;
|
||||||
|
frame.ext_ID = true;
|
||||||
|
frame.DLC = 8;
|
||||||
|
frame.ID = base_id + base_offset;
|
||||||
|
memset(frame.data.u8, 0, 8);
|
||||||
|
};
|
||||||
|
|
||||||
|
init_frame(SOFAR_351, 0x351);
|
||||||
|
init_frame(SOFAR_355, 0x355);
|
||||||
|
init_frame(SOFAR_356, 0x356);
|
||||||
|
init_frame(SOFAR_30F, 0x30F);
|
||||||
|
init_frame(SOFAR_359, 0x359);
|
||||||
|
init_frame(SOFAR_35E, 0x35E);
|
||||||
|
init_frame(SOFAR_35F, 0x35F);
|
||||||
|
init_frame(SOFAR_35A, 0x35A);
|
||||||
|
|
||||||
|
init_frame(SOFAR_683, 0x683);
|
||||||
|
init_frame(SOFAR_684, 0x684);
|
||||||
|
init_frame(SOFAR_685, 0x685);
|
||||||
|
init_frame(SOFAR_690, 0x690);
|
||||||
|
|
||||||
|
String tempStr(datalayer.battery.settings.sofar_user_specified_battery_id);
|
||||||
|
strncpy(datalayer.system.info.inverter_brand, tempStr.c_str(), 7);
|
||||||
|
datalayer.system.info.inverter_brand[7] = '\0';
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
|
@ -10,14 +10,18 @@
|
||||||
|
|
||||||
class SofarInverter : public CanInverterProtocol {
|
class SofarInverter : public CanInverterProtocol {
|
||||||
public:
|
public:
|
||||||
|
bool setup() override;
|
||||||
const char* name() override { return Name; }
|
const char* name() override { return Name; }
|
||||||
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);
|
||||||
static constexpr const char* Name = "Sofar BMS (Extended Frame) over CAN bus";
|
static constexpr const char* Name = "Sofar BMS (Extended) via CAN, Battery ID";
|
||||||
|
bool supports_battery_id() { return true; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
|
unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
|
||||||
|
uint16_t calculated_capacity_AH = 0; // Pack Capacity in AH (Updates based on battery stats)
|
||||||
|
const char* BatteryType = "BATxEMU"; // Manufacturer name in ASCII
|
||||||
|
|
||||||
//Actual content messages
|
//Actual content messages
|
||||||
//Note that these are technically extended frames. If more batteries are put in parallel,the first battery sends 0x351 the next battery sends 0x1351 etc. 16 batteries in parallel supported
|
//Note that these are technically extended frames. If more batteries are put in parallel,the first battery sends 0x351 the next battery sends 0x1351 etc. 16 batteries in parallel supported
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue