mirror of
https://github.com/dalathegreat/Battery-Emulator.git
synced 2025-10-03 17:59:27 +02:00
More batteries to common image
This commit is contained in:
parent
57f8d2618e
commit
02917dcbea
64 changed files with 885 additions and 722 deletions
|
@ -19,6 +19,8 @@ std::vector<BatteryType> supported_battery_types() {
|
|||
|
||||
extern const char* name_for_type(BatteryType type) {
|
||||
switch (type) {
|
||||
case BatteryType::None:
|
||||
return "None";
|
||||
case BatteryType::BmwI3:
|
||||
return BmwI3Battery::Name;
|
||||
case BatteryType::BmwIx:
|
||||
|
@ -31,19 +33,67 @@ extern const char* name_for_type(BatteryType type) {
|
|||
return CellPowerBms::Name;
|
||||
case BatteryType::Chademo:
|
||||
return ChademoBattery::Name;
|
||||
case BatteryType::CmfaEv:
|
||||
return CmfaEvBattery::Name;
|
||||
case BatteryType::Foxess:
|
||||
return FoxessBattery::Name;
|
||||
case BatteryType::GeelyGeometryC:
|
||||
return GeelyGeometryCBattery::Name;
|
||||
case BatteryType::OrionBms:
|
||||
return OrionBms::Name;
|
||||
case BatteryType::Sono:
|
||||
return SonoBattery::Name;
|
||||
case BatteryType::StellantisEcmp:
|
||||
return EcmpBattery::Name;
|
||||
case BatteryType::ImievCZeroIon:
|
||||
return ImievCZeroIonBattery::Name;
|
||||
case BatteryType::JaguarIpace:
|
||||
return JaguarIpaceBattery::Name;
|
||||
case BatteryType::KiaEGmp:
|
||||
return KiaEGmpBattery::Name;
|
||||
case BatteryType::KiaHyundai64:
|
||||
return KiaHyundai64Battery::Name;
|
||||
case BatteryType::KiaHyundaiHybrid:
|
||||
return KiaHyundaiHybridBattery::Name;
|
||||
case BatteryType::Meb:
|
||||
return MebBattery::Name;
|
||||
case BatteryType::Mg5:
|
||||
return Mg5Battery::Name;
|
||||
case BatteryType::NissanLeaf:
|
||||
return NissanLeafBattery::Name;
|
||||
case BatteryType::TestFake:
|
||||
return TestFakeBattery::Name;
|
||||
case BatteryType::Pylon:
|
||||
return PylonBattery::Name;
|
||||
case BatteryType::DalyBms:
|
||||
return DalyBms::Name;
|
||||
case BatteryType::RjxzsBms:
|
||||
return RjxzsBms::Name;
|
||||
case BatteryType::RangeRoverPhev:
|
||||
return RangeRoverPhevBattery::Name;
|
||||
case BatteryType::RenaultKangoo:
|
||||
return RenaultKangooBattery::Name;
|
||||
case BatteryType::RenaultTwizy:
|
||||
return RenaultTwizyBattery::Name;
|
||||
case BatteryType::RenaultZoe1:
|
||||
return RenaultZoeGen1Battery::Name;
|
||||
case BatteryType::RenaultZoe2:
|
||||
return RenaultZoeGen2Battery::Name;
|
||||
case BatteryType::SantaFePhev:
|
||||
return SantaFePhevBattery::Name;
|
||||
case BatteryType::SimpBms:
|
||||
return SimpBmsBattery::Name;
|
||||
case BatteryType::TeslaModel3Y:
|
||||
return TeslaModel3YBattery::Name;
|
||||
case BatteryType::TeslaModelSX:
|
||||
return TeslaModelSXBattery::Name;
|
||||
case BatteryType::None:
|
||||
return "None";
|
||||
case BatteryType::TestFake:
|
||||
return TestFakeBattery::Name;
|
||||
case BatteryType::VolvoSpa:
|
||||
return VolvoSpaBattery::Name;
|
||||
case BatteryType::VolvoSpaHybrid:
|
||||
return VolvoSpaHybridBattery::Name;
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#ifdef COMMON_IMAGE
|
||||
|
@ -54,51 +104,92 @@ extern const char* name_for_type(BatteryType type) {
|
|||
BatteryType user_selected_battery_type = BatteryType::NissanLeaf;
|
||||
bool user_selected_second_battery = false;
|
||||
|
||||
void setup_battery() {
|
||||
Battery* create_battery(BatteryType type) {
|
||||
switch (type) {
|
||||
case BatteryType::None:
|
||||
return nullptr;
|
||||
case BatteryType::BmwI3:
|
||||
return new BmwI3Battery();
|
||||
case BatteryType::BmwIx:
|
||||
return new BmwIXBattery();
|
||||
case BatteryType::BoltAmpera:
|
||||
return new BoltAmperaBattery();
|
||||
case BatteryType::BydAtto3:
|
||||
return new BydAttoBattery();
|
||||
case BatteryType::CellPowerBms:
|
||||
return new CellPowerBms();
|
||||
case BatteryType::Chademo:
|
||||
return new ChademoBattery();
|
||||
case BatteryType::CmfaEv:
|
||||
return new CmfaEvBattery();
|
||||
case BatteryType::Foxess:
|
||||
return new FoxessBattery();
|
||||
case BatteryType::GeelyGeometryC:
|
||||
return new GeelyGeometryCBattery();
|
||||
case BatteryType::OrionBms:
|
||||
return new OrionBms();
|
||||
case BatteryType::Sono:
|
||||
return new SonoBattery();
|
||||
case BatteryType::StellantisEcmp:
|
||||
return new EcmpBattery();
|
||||
case BatteryType::ImievCZeroIon:
|
||||
return new ImievCZeroIonBattery();
|
||||
case BatteryType::JaguarIpace:
|
||||
return new JaguarIpaceBattery();
|
||||
case BatteryType::KiaEGmp:
|
||||
return new KiaEGmpBattery();
|
||||
case BatteryType::KiaHyundai64:
|
||||
return new KiaHyundai64Battery();
|
||||
case BatteryType::KiaHyundaiHybrid:
|
||||
return new KiaHyundaiHybridBattery();
|
||||
case BatteryType::Meb:
|
||||
return new MebBattery();
|
||||
case BatteryType::Mg5:
|
||||
return new Mg5Battery();
|
||||
case BatteryType::NissanLeaf:
|
||||
return new NissanLeafBattery();
|
||||
case BatteryType::Pylon:
|
||||
return new PylonBattery();
|
||||
case BatteryType::DalyBms:
|
||||
return new DalyBms();
|
||||
case BatteryType::RjxzsBms:
|
||||
return new RjxzsBms();
|
||||
case BatteryType::RangeRoverPhev:
|
||||
return new RangeRoverPhevBattery();
|
||||
case BatteryType::RenaultKangoo:
|
||||
return new RenaultKangooBattery();
|
||||
case BatteryType::RenaultTwizy:
|
||||
return new RenaultTwizyBattery();
|
||||
case BatteryType::RenaultZoe1:
|
||||
return new RenaultZoeGen1Battery();
|
||||
case BatteryType::RenaultZoe2:
|
||||
return new RenaultZoeGen2Battery();
|
||||
case BatteryType::SantaFePhev:
|
||||
return new SantaFePhevBattery();
|
||||
case BatteryType::SimpBms:
|
||||
return new SimpBmsBattery();
|
||||
case BatteryType::TeslaModel3Y:
|
||||
return new TeslaModel3YBattery();
|
||||
case BatteryType::TeslaModelSX:
|
||||
return new TeslaModelSXBattery();
|
||||
case BatteryType::TestFake:
|
||||
return new TestFakeBattery();
|
||||
case BatteryType::VolvoSpa:
|
||||
return new VolvoSpaBattery();
|
||||
case BatteryType::VolvoSpaHybrid:
|
||||
return new VolvoSpaHybridBattery();
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void setup_battery() {
|
||||
if (battery) {
|
||||
// Let's not create the battery again.
|
||||
return;
|
||||
}
|
||||
|
||||
switch (user_selected_battery_type) {
|
||||
case BatteryType::BmwI3:
|
||||
battery = new BmwI3Battery();
|
||||
break;
|
||||
case BatteryType::BmwIx:
|
||||
battery = new BmwIXBattery();
|
||||
break;
|
||||
case BatteryType::BoltAmpera:
|
||||
battery = new BoltAmperaBattery();
|
||||
break;
|
||||
case BatteryType::BydAtto3:
|
||||
battery = new BydAttoBattery();
|
||||
break;
|
||||
case BatteryType::CellPowerBms:
|
||||
battery = new CellPowerBms();
|
||||
break;
|
||||
case BatteryType::Chademo:
|
||||
battery = new ChademoBattery();
|
||||
break;
|
||||
case BatteryType::CmfaEv:
|
||||
battery = new CmfaEvBattery();
|
||||
break;
|
||||
case BatteryType::DalyBms:
|
||||
battery = new DalyBms();
|
||||
break;
|
||||
case BatteryType::NissanLeaf:
|
||||
battery = new NissanLeafBattery();
|
||||
break;
|
||||
case BatteryType::TeslaModel3Y:
|
||||
battery = new TeslaModel3YBattery();
|
||||
break;
|
||||
case BatteryType::TeslaModelSX:
|
||||
battery = new TeslaModelSXBattery();
|
||||
break;
|
||||
case BatteryType::TestFake:
|
||||
battery = new TestFakeBattery();
|
||||
break;
|
||||
}
|
||||
battery = create_battery(user_selected_battery_type);
|
||||
|
||||
if (battery) {
|
||||
battery->setup();
|
||||
|
|
|
@ -178,6 +178,6 @@ void BmwSbox::transmit_can(unsigned long currentMillis) {
|
|||
}
|
||||
|
||||
void BmwSbox::setup() {
|
||||
strncpy(datalayer.system.info.shunt_protocol, "BMW SBOX", 63);
|
||||
strncpy(datalayer.system.info.shunt_protocol, Name, 63);
|
||||
datalayer.system.info.shunt_protocol[63] = '\0';
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ class BmwSbox : public CanShunt {
|
|||
void setup();
|
||||
void transmit_can(unsigned long currentMillis);
|
||||
void handle_incoming_can_frame(CAN_frame rx_frame);
|
||||
static constexpr char* Name = "BMW SBOX";
|
||||
|
||||
private:
|
||||
/** Minimum input voltage required to enable relay control **/
|
||||
|
|
6
Software/src/battery/Battery.cpp
Normal file
6
Software/src/battery/Battery.cpp
Normal file
|
@ -0,0 +1,6 @@
|
|||
#include "Battery.h"
|
||||
#include "../datalayer/datalayer.h"
|
||||
|
||||
float Battery::get_voltage() {
|
||||
return static_cast<float>(datalayer.battery.status.voltage_dV) / 10.0;
|
||||
}
|
|
@ -1,47 +1,47 @@
|
|||
#ifndef BATTERY_H
|
||||
#define BATTERY_H
|
||||
|
||||
#include "../datalayer/datalayer.h"
|
||||
#include <vector>
|
||||
#include "src/devboard/webserver/BatteryHtmlRenderer.h"
|
||||
|
||||
enum class BatteryType {
|
||||
None = 0,
|
||||
BmwSbox,
|
||||
BmwI3,
|
||||
BmwIx,
|
||||
BoltAmpera,
|
||||
BydAtto3,
|
||||
CellPowerBms,
|
||||
Chademo,
|
||||
CmfaEv,
|
||||
Foxess,
|
||||
GeelyGeometryC,
|
||||
OrionBms,
|
||||
Sono,
|
||||
StellantisEcmp,
|
||||
ImievCZeroIon,
|
||||
JaguarIpace,
|
||||
KiaEGmp,
|
||||
KiaHyundai64,
|
||||
KiaHyundaiHybrid,
|
||||
Meb,
|
||||
Mg5,
|
||||
NissanLeaf,
|
||||
Pylon,
|
||||
DalyBms,
|
||||
RjxzsBms,
|
||||
RangeRoverPhev,
|
||||
RenaultKangoo,
|
||||
RenaultTwizy,
|
||||
RenaultZoe1,
|
||||
RenaultZoe2,
|
||||
SantaFePhev,
|
||||
SimpBms,
|
||||
TeslaModel3Y,
|
||||
TeslaModelSX,
|
||||
TestFake,
|
||||
VolvoSpa,
|
||||
VolvoSpaHybrid,
|
||||
BmwSbox = 1,
|
||||
BmwI3 = 2,
|
||||
BmwIx = 3,
|
||||
BoltAmpera = 4,
|
||||
BydAtto3 = 5,
|
||||
CellPowerBms = 6,
|
||||
Chademo = 7,
|
||||
CmfaEv = 8,
|
||||
Foxess = 9,
|
||||
GeelyGeometryC = 10,
|
||||
OrionBms = 11,
|
||||
Sono = 12,
|
||||
StellantisEcmp = 13,
|
||||
ImievCZeroIon = 14,
|
||||
JaguarIpace = 15,
|
||||
KiaEGmp = 16,
|
||||
KiaHyundai64 = 17,
|
||||
KiaHyundaiHybrid = 18,
|
||||
Meb = 19,
|
||||
Mg5 = 20,
|
||||
NissanLeaf = 21,
|
||||
Pylon = 22,
|
||||
DalyBms = 23,
|
||||
RjxzsBms = 24,
|
||||
RangeRoverPhev = 25,
|
||||
RenaultKangoo = 26,
|
||||
RenaultTwizy = 27,
|
||||
RenaultZoe1 = 28,
|
||||
RenaultZoe2 = 29,
|
||||
SantaFePhev = 30,
|
||||
SimpBms = 31,
|
||||
TeslaModel3Y = 32,
|
||||
TeslaModelSX = 33,
|
||||
TestFake = 34,
|
||||
VolvoSpa = 35,
|
||||
VolvoSpaHybrid = 36,
|
||||
Highest
|
||||
};
|
||||
|
||||
|
@ -95,7 +95,7 @@ class Battery {
|
|||
virtual void set_factory_mode() {}
|
||||
|
||||
virtual void set_fake_voltage(float v) {}
|
||||
virtual float get_voltage() { return static_cast<float>(datalayer.battery.status.voltage_dV) / 10.0; }
|
||||
virtual float get_voltage();
|
||||
|
||||
// This allows for battery specific SOC plausibility calculations to be performed.
|
||||
virtual bool soc_plausible() { return true; }
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
#ifndef CHADEMO_SHUNTS_H
|
||||
#define CHADEMO_SHUNTS_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include "../devboard/utils/types.h"
|
||||
|
||||
uint16_t get_measured_voltage();
|
||||
uint16_t get_measured_current();
|
||||
void ISA_handleFrame(CAN_frame* frame);
|
||||
|
|
|
@ -940,7 +940,7 @@ void CmfaEvBattery::transmit_can(unsigned long currentMillis) {
|
|||
}
|
||||
|
||||
void CmfaEvBattery::setup(void) { // Performs one time setup at startup
|
||||
strncpy(datalayer.system.info.battery_protocol, "CMFA platform, 27 kWh battery", 63);
|
||||
strncpy(datalayer.system.info.battery_protocol, Name, 63);
|
||||
datalayer.system.info.battery_protocol[63] = '\0';
|
||||
datalayer.system.status.battery_allows_contactor_closing = true;
|
||||
datalayer.battery.info.number_of_cells = 72;
|
||||
|
|
|
@ -15,6 +15,7 @@ class CmfaEvBattery : public CanBattery {
|
|||
virtual void handle_incoming_can_frame(CAN_frame rx_frame);
|
||||
virtual void update_values();
|
||||
virtual void transmit_can(unsigned long currentMillis);
|
||||
static constexpr char* Name = "CMFA platform, 27 kWh battery";
|
||||
|
||||
BatteryHtmlRenderer& get_status_renderer() { return renderer; }
|
||||
|
||||
|
|
8
Software/src/battery/CanBattery.cpp
Normal file
8
Software/src/battery/CanBattery.cpp
Normal file
|
@ -0,0 +1,8 @@
|
|||
#include "CanBattery.h"
|
||||
#include "../../src/include.h"
|
||||
|
||||
CanBattery::CanBattery() {
|
||||
can_interface = can_config.battery;
|
||||
register_transmitter(this);
|
||||
register_can_receiver(this, can_interface);
|
||||
}
|
|
@ -22,11 +22,7 @@ class CanBattery : public Battery, Transmitter, CanReceiver {
|
|||
protected:
|
||||
CAN_Interface can_interface;
|
||||
|
||||
CanBattery() {
|
||||
can_interface = can_config.battery;
|
||||
register_transmitter(this);
|
||||
register_can_receiver(this, can_interface);
|
||||
}
|
||||
CanBattery();
|
||||
|
||||
CanBattery(CAN_Interface interface) {
|
||||
can_interface = interface;
|
||||
|
|
|
@ -60,7 +60,7 @@ void DalyBms::update_values() {
|
|||
}
|
||||
|
||||
void DalyBms::setup(void) { // Performs one time setup at startup
|
||||
strncpy(datalayer.system.info.battery_protocol, "DALY RS485", 63);
|
||||
strncpy(datalayer.system.info.battery_protocol, Name, 63);
|
||||
datalayer.system.info.battery_protocol[63] = '\0';
|
||||
datalayer.battery.info.number_of_cells = CELL_COUNT;
|
||||
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
|
||||
|
|
|
@ -13,6 +13,7 @@ class DalyBms : public RS485Battery {
|
|||
void update_values();
|
||||
void transmit_rs485(unsigned long currentMillis);
|
||||
void receive();
|
||||
static constexpr char* Name = "DALY RS485";
|
||||
|
||||
private:
|
||||
/* Tweak these according to your battery build */
|
||||
|
|
|
@ -1516,7 +1516,7 @@ void EcmpBattery::transmit_can(unsigned long currentMillis) {
|
|||
}
|
||||
|
||||
void EcmpBattery::setup(void) { // Performs one time setup at startup
|
||||
strncpy(datalayer.system.info.battery_protocol, "Stellantis ECMP battery", 63);
|
||||
strncpy(datalayer.system.info.battery_protocol, Name, 63);
|
||||
datalayer.system.info.battery_protocol[63] = '\0';
|
||||
datalayer.battery.info.number_of_cells = 108;
|
||||
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
|
||||
|
|
|
@ -16,6 +16,7 @@ class EcmpBattery : public CanBattery {
|
|||
virtual void handle_incoming_can_frame(CAN_frame rx_frame);
|
||||
virtual void update_values();
|
||||
virtual void transmit_can(unsigned long currentMillis);
|
||||
static constexpr char* Name = "Stellantis ECMP battery";
|
||||
|
||||
bool supports_clear_isolation() { return true; }
|
||||
void clear_isolation() { datalayer_extended.stellantisECMP.UserRequestIsolationReset = true; }
|
||||
|
@ -38,8 +39,8 @@ class EcmpBattery : public CanBattery {
|
|||
static const int MAX_CELL_DEVIATION_MV = 100;
|
||||
static const int MAX_CELL_VOLTAGE_MV = 4250;
|
||||
static const int MIN_CELL_VOLTAGE_MV = 2700;
|
||||
#define NOT_SAMPLED_YET 255
|
||||
#define COMPLETED_STATE 0
|
||||
static const int NOT_SAMPLED_YET = 255;
|
||||
static const int COMPLETED_STATE = 0;
|
||||
bool battery_RelayOpenRequest = false;
|
||||
bool battery_InterlockOpen = false;
|
||||
uint8_t ContactorResetStatemachine = 0;
|
||||
|
@ -143,77 +144,77 @@ class EcmpBattery : public CanBattery {
|
|||
unsigned long previousMillis500 = 0; // will store last time a 500ms CAN Message was sent
|
||||
unsigned long previousMillis1000 = 0; // will store last time a 1000ms CAN Message was sent
|
||||
unsigned long previousMillis5000 = 0; // will store last time a 1000ms CAN Message was sent
|
||||
#define PID_WELD_CHECK 0xD814
|
||||
#define PID_CONT_REASON_OPEN 0xD812
|
||||
#define PID_CONTACTOR_STATUS 0xD813
|
||||
#define PID_NEG_CONT_CONTROL 0xD44F
|
||||
#define PID_NEG_CONT_STATUS 0xD453
|
||||
#define PID_POS_CONT_CONTROL 0xD44E
|
||||
#define PID_POS_CONT_STATUS 0xD452
|
||||
#define PID_CONTACTOR_NEGATIVE 0xD44C
|
||||
#define PID_CONTACTOR_POSITIVE 0xD44D
|
||||
#define PID_PRECHARGE_RELAY_CONTROL 0xD44B
|
||||
#define PID_PRECHARGE_RELAY_STATUS 0xD451
|
||||
#define PID_RECHARGE_STATUS 0xD864
|
||||
#define PID_DELTA_TEMPERATURE 0xD878
|
||||
#define PID_COLDEST_MODULE 0xD446
|
||||
#define PID_LOWEST_TEMPERATURE 0xD87D
|
||||
#define PID_AVERAGE_TEMPERATURE 0xD877
|
||||
#define PID_HIGHEST_TEMPERATURE 0xD817
|
||||
#define PID_HOTTEST_MODULE 0xD445
|
||||
#define PID_AVG_CELL_VOLTAGE 0xD43D
|
||||
#define PID_CURRENT 0xD816
|
||||
#define PID_INSULATION_NEG 0xD87C
|
||||
#define PID_INSULATION_POS 0xD87B
|
||||
#define PID_MAX_CURRENT_10S 0xD876
|
||||
#define PID_MAX_DISCHARGE_10S 0xD873
|
||||
#define PID_MAX_DISCHARGE_30S 0xD874
|
||||
#define PID_MAX_CHARGE_10S 0xD871
|
||||
#define PID_MAX_CHARGE_30S 0xD872
|
||||
#define PID_ENERGY_CAPACITY 0xD860
|
||||
#define PID_HIGH_CELL_NUM 0xD43B
|
||||
#define PID_LOW_CELL_NUM 0xD43C
|
||||
#define PID_SUM_OF_CELLS 0xD438
|
||||
#define PID_CELL_MIN_CAPACITY 0xD413
|
||||
#define PID_CELL_VOLTAGE_MEAS_STATUS 0xD48A
|
||||
#define PID_INSULATION_RES 0xD47A
|
||||
#define PID_PACK_VOLTAGE 0xD815
|
||||
#define PID_HIGH_CELL_VOLTAGE 0xD870
|
||||
#define PID_ALL_CELL_VOLTAGES 0xD440 //Multi-frame
|
||||
#define PID_LOW_CELL_VOLTAGE 0xD86F
|
||||
#define PID_BATTERY_ENERGY 0xD865
|
||||
#define PID_BATTERY_ENERGY 0xD865
|
||||
#define PID_CELLBALANCE_STATUS 0xD46F //Multi-frame?
|
||||
#define PID_CELLBALANCE_HWERR_MASK 0xD470 //Multi-frame
|
||||
#define PID_CRASH_COUNTER 0xD42F
|
||||
#define PID_WIRE_CRASH 0xD87F
|
||||
#define PID_CAN_CRASH 0xD48D
|
||||
#define PID_HISTORY_DATA 0xD465
|
||||
#define PID_LOWSOC_COUNTER 0xD492 //Not supported on all batteris
|
||||
#define PID_LAST_CAN_FAILURE_DETAIL 0xD89E //Not supported on all batteris
|
||||
#define PID_HW_VERSION_NUM 0xF193 //Not supported on all batteris
|
||||
#define PID_SW_VERSION_NUM 0xF195 //Not supported on all batteris
|
||||
#define PID_FACTORY_MODE_CONTROL 0xD900
|
||||
#define PID_BATTERY_SERIAL 0xD901
|
||||
#define PID_ALL_CELL_SOH 0xD4B5 //Very long message reply, too much data for this integration
|
||||
#define PID_AUX_FUSE_STATE 0xD86C
|
||||
#define PID_BATTERY_STATE 0xD811
|
||||
#define PID_PRECHARGE_SHORT_CIRCUIT 0xD4D8
|
||||
#define PID_ESERVICE_PLUG_STATE 0xD86A
|
||||
#define PID_MAINFUSE_STATE 0xD86B
|
||||
#define PID_MOST_CRITICAL_FAULT 0xD481
|
||||
#define PID_CURRENT_TIME 0xD47F
|
||||
#define PID_TIME_SENT_BY_CAR 0xD4CA
|
||||
#define PID_12V 0xD822
|
||||
#define PID_12V_ABNORMAL 0xD42B
|
||||
#define PID_HVIL_IN_VOLTAGE 0xD46B
|
||||
#define PID_HVIL_OUT_VOLTAGE 0xD46A
|
||||
#define PID_HVIL_STATE 0xD869
|
||||
#define PID_BMS_STATE 0xD45A
|
||||
#define PID_VEHICLE_SPEED 0xD802
|
||||
#define PID_TIME_SPENT_OVER_55C 0xE082
|
||||
#define PID_CONTACTOR_CLOSING_COUNTER 0xD416
|
||||
#define PID_DATE_OF_MANUFACTURE 0xF18B
|
||||
|
||||
static const uint16_t PID_WELD_CHECK = 0xD814;
|
||||
static const uint16_t PID_CONT_REASON_OPEN = 0xD812;
|
||||
static const uint16_t PID_CONTACTOR_STATUS = 0xD813;
|
||||
static const uint16_t PID_NEG_CONT_CONTROL = 0xD44F;
|
||||
static const uint16_t PID_NEG_CONT_STATUS = 0xD453;
|
||||
static const uint16_t PID_POS_CONT_CONTROL = 0xD44E;
|
||||
static const uint16_t PID_POS_CONT_STATUS = 0xD452;
|
||||
static const uint16_t PID_CONTACTOR_NEGATIVE = 0xD44C;
|
||||
static const uint16_t PID_CONTACTOR_POSITIVE = 0xD44D;
|
||||
static const uint16_t PID_PRECHARGE_RELAY_CONTROL = 0xD44B;
|
||||
static const uint16_t PID_PRECHARGE_RELAY_STATUS = 0xD451;
|
||||
static const uint16_t PID_RECHARGE_STATUS = 0xD864;
|
||||
static const uint16_t PID_DELTA_TEMPERATURE = 0xD878;
|
||||
static const uint16_t PID_COLDEST_MODULE = 0xD446;
|
||||
static const uint16_t PID_LOWEST_TEMPERATURE = 0xD87D;
|
||||
static const uint16_t PID_AVERAGE_TEMPERATURE = 0xD877;
|
||||
static const uint16_t PID_HIGHEST_TEMPERATURE = 0xD817;
|
||||
static const uint16_t PID_HOTTEST_MODULE = 0xD445;
|
||||
static const uint16_t PID_AVG_CELL_VOLTAGE = 0xD43D;
|
||||
static const uint16_t PID_CURRENT = 0xD816;
|
||||
static const uint16_t PID_INSULATION_NEG = 0xD87C;
|
||||
static const uint16_t PID_INSULATION_POS = 0xD87B;
|
||||
static const uint16_t PID_MAX_CURRENT_10S = 0xD876;
|
||||
static const uint16_t PID_MAX_DISCHARGE_10S = 0xD873;
|
||||
static const uint16_t PID_MAX_DISCHARGE_30S = 0xD874;
|
||||
static const uint16_t PID_MAX_CHARGE_10S = 0xD871;
|
||||
static const uint16_t PID_MAX_CHARGE_30S = 0xD872;
|
||||
static const uint16_t PID_ENERGY_CAPACITY = 0xD860;
|
||||
static const uint16_t PID_HIGH_CELL_NUM = 0xD43B;
|
||||
static const uint16_t PID_LOW_CELL_NUM = 0xD43C;
|
||||
static const uint16_t PID_SUM_OF_CELLS = 0xD438;
|
||||
static const uint16_t PID_CELL_MIN_CAPACITY = 0xD413;
|
||||
static const uint16_t PID_CELL_VOLTAGE_MEAS_STATUS = 0xD48A;
|
||||
static const uint16_t PID_INSULATION_RES = 0xD47A;
|
||||
static const uint16_t PID_PACK_VOLTAGE = 0xD815;
|
||||
static const uint16_t PID_HIGH_CELL_VOLTAGE = 0xD870;
|
||||
static const uint16_t PID_ALL_CELL_VOLTAGES = 0xD440; //Multi-frame
|
||||
static const uint16_t PID_LOW_CELL_VOLTAGE = 0xD86F;
|
||||
static const uint16_t PID_BATTERY_ENERGY = 0xD865;
|
||||
static const uint16_t PID_CELLBALANCE_STATUS = 0xD46F; //Multi-frame?
|
||||
static const uint16_t PID_CELLBALANCE_HWERR_MASK = 0xD470; //Multi-frame
|
||||
static const uint16_t PID_CRASH_COUNTER = 0xD42F;
|
||||
static const uint16_t PID_WIRE_CRASH = 0xD87F;
|
||||
static const uint16_t PID_CAN_CRASH = 0xD48D;
|
||||
static const uint16_t PID_HISTORY_DATA = 0xD465;
|
||||
static const uint16_t PID_LOWSOC_COUNTER = 0xD492; //Not supported on all batteris
|
||||
static const uint16_t PID_LAST_CAN_FAILURE_DETAIL = 0xD89E; //Not supported on all batteris
|
||||
static const uint16_t PID_HW_VERSION_NUM = 0xF193; //Not supported on all batteris
|
||||
static const uint16_t PID_SW_VERSION_NUM = 0xF195; //Not supported on all batteris
|
||||
static const uint16_t PID_FACTORY_MODE_CONTROL = 0xD900;
|
||||
static const uint16_t PID_BATTERY_SERIAL = 0xD901;
|
||||
static const uint16_t PID_ALL_CELL_SOH = 0xD4B5; //Very long message reply, too much data for this integration
|
||||
static const uint16_t PID_AUX_FUSE_STATE = 0xD86C;
|
||||
static const uint16_t PID_BATTERY_STATE = 0xD811;
|
||||
static const uint16_t PID_PRECHARGE_SHORT_CIRCUIT = 0xD4D8;
|
||||
static const uint16_t PID_ESERVICE_PLUG_STATE = 0xD86A;
|
||||
static const uint16_t PID_MAINFUSE_STATE = 0xD86B;
|
||||
static const uint16_t PID_MOST_CRITICAL_FAULT = 0xD481;
|
||||
static const uint16_t PID_CURRENT_TIME = 0xD47F;
|
||||
static const uint16_t PID_TIME_SENT_BY_CAR = 0xD4CA;
|
||||
static const uint16_t PID_12V = 0xD822;
|
||||
static const uint16_t PID_12V_ABNORMAL = 0xD42B;
|
||||
static const uint16_t PID_HVIL_IN_VOLTAGE = 0xD46B;
|
||||
static const uint16_t PID_HVIL_OUT_VOLTAGE = 0xD46A;
|
||||
static const uint16_t PID_HVIL_STATE = 0xD869;
|
||||
static const uint16_t PID_BMS_STATE = 0xD45A;
|
||||
static const uint16_t PID_VEHICLE_SPEED = 0xD802;
|
||||
static const uint16_t PID_TIME_SPENT_OVER_55C = 0xE082;
|
||||
static const uint16_t PID_CONTACTOR_CLOSING_COUNTER = 0xD416;
|
||||
static const uint16_t PID_DATE_OF_MANUFACTURE = 0xF18B;
|
||||
|
||||
uint16_t poll_state = PID_WELD_CHECK;
|
||||
uint16_t incoming_poll = 0;
|
||||
|
|
|
@ -572,7 +572,7 @@ void FoxessBattery::transmit_can(unsigned long currentMillis) {
|
|||
}
|
||||
|
||||
void FoxessBattery::setup(void) { // Performs one time setup at startup
|
||||
strncpy(datalayer.system.info.battery_protocol, "FoxESS HV2600/ECS4100 OEM battery", 63);
|
||||
strncpy(datalayer.system.info.battery_protocol, Name, 63);
|
||||
datalayer.system.info.battery_protocol[63] = '\0';
|
||||
datalayer.battery.info.number_of_cells = 0; //Startup with no cells, populates later when we know packsize
|
||||
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
|
||||
|
|
|
@ -15,6 +15,7 @@ class FoxessBattery : public CanBattery {
|
|||
virtual void handle_incoming_can_frame(CAN_frame rx_frame);
|
||||
virtual void update_values();
|
||||
virtual void transmit_can(unsigned long currentMillis);
|
||||
static constexpr char* Name = "FoxESS HV2600/ECS4100 OEM battery";
|
||||
|
||||
private:
|
||||
static const int MAX_PACK_VOLTAGE_DV = 4672; //467.2V for HS20.8 (used during startup, refined later)
|
||||
|
|
|
@ -660,7 +660,7 @@ void GeelyGeometryCBattery::transmit_can(unsigned long currentMillis) {
|
|||
}
|
||||
|
||||
void GeelyGeometryCBattery::setup(void) { // Performs one time setup at startup
|
||||
strncpy(datalayer.system.info.battery_protocol, "Geely Geometry C", 63);
|
||||
strncpy(datalayer.system.info.battery_protocol, Name, 63);
|
||||
datalayer.system.info.battery_protocol[63] = '\0';
|
||||
datalayer.system.status.battery_allows_contactor_closing = true;
|
||||
datalayer_battery->info.number_of_cells = 102; //70kWh pack has 102S, startup in this mode
|
||||
|
|
|
@ -16,6 +16,7 @@ class GeelyGeometryCBattery : public CanBattery {
|
|||
virtual void handle_incoming_can_frame(CAN_frame rx_frame);
|
||||
virtual void update_values();
|
||||
virtual void transmit_can(unsigned long currentMillis);
|
||||
static constexpr char* Name = "Geely Geometry C";
|
||||
|
||||
BatteryHtmlRenderer& get_status_renderer() { return renderer; }
|
||||
|
||||
|
|
|
@ -190,7 +190,7 @@ void ImievCZeroIonBattery::transmit_can(unsigned long currentMillis) {
|
|||
}
|
||||
|
||||
void ImievCZeroIonBattery::setup(void) { // Performs one time setup at startup
|
||||
strncpy(datalayer.system.info.battery_protocol, "I-Miev / C-Zero / Ion Triplet", 63);
|
||||
strncpy(datalayer.system.info.battery_protocol, Name, 63);
|
||||
datalayer.system.info.battery_protocol[63] = '\0';
|
||||
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
|
||||
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;
|
||||
|
|
|
@ -15,6 +15,7 @@ class ImievCZeroIonBattery : public CanBattery {
|
|||
virtual void handle_incoming_can_frame(CAN_frame rx_frame);
|
||||
virtual void update_values();
|
||||
virtual void transmit_can(unsigned long currentMillis);
|
||||
static constexpr char* Name = "I-Miev / C-Zero / Ion Triplet";
|
||||
|
||||
private:
|
||||
static const int MAX_PACK_VOLTAGE_DV = 3696; //5000 = 500.0V
|
||||
|
|
|
@ -13,6 +13,7 @@ class JaguarIpaceBattery : public CanBattery {
|
|||
virtual void handle_incoming_can_frame(CAN_frame rx_frame);
|
||||
virtual void update_values();
|
||||
virtual void transmit_can(unsigned long currentMillis);
|
||||
static constexpr char* Name = "Jaguar I-PACE";
|
||||
|
||||
private:
|
||||
static const int MAX_PACK_VOLTAGE_DV = 4546; //5000 = 500.0V
|
||||
|
|
|
@ -1090,7 +1090,7 @@ void KiaEGmpBattery::transmit_can(unsigned long currentMillis) {
|
|||
}
|
||||
|
||||
void KiaEGmpBattery::setup(void) { // Performs one time setup at startup
|
||||
strncpy(datalayer.system.info.battery_protocol, "Kia/Hyundai EGMP platform", 63);
|
||||
strncpy(datalayer.system.info.battery_protocol, Name, 63);
|
||||
datalayer.system.info.battery_protocol[63] = '\0';
|
||||
datalayer.system.status.battery_allows_contactor_closing = true;
|
||||
datalayer.battery.info.number_of_cells = 192; // TODO: will vary depending on battery
|
||||
|
|
|
@ -19,6 +19,7 @@ class KiaEGmpBattery : public CanBattery {
|
|||
virtual void handle_incoming_can_frame(CAN_frame rx_frame);
|
||||
virtual void update_values();
|
||||
virtual void transmit_can(unsigned long currentMillis);
|
||||
static constexpr char* Name = "Kia/Hyundai EGMP platform";
|
||||
|
||||
private:
|
||||
uint16_t estimateSOC(uint16_t packVoltage, uint16_t cellCount, int16_t currentAmps);
|
||||
|
|
|
@ -464,7 +464,7 @@ void KiaHyundai64Battery::transmit_can(unsigned long currentMillis) {
|
|||
}
|
||||
|
||||
void KiaHyundai64Battery::setup(void) { // Performs one time setup at startup
|
||||
strncpy(datalayer.system.info.battery_protocol, "Kia/Hyundai 64/40kWh battery", 63);
|
||||
strncpy(datalayer.system.info.battery_protocol, Name, 63);
|
||||
datalayer.system.info.battery_protocol[63] = '\0';
|
||||
datalayer_battery->info.max_design_voltage_dV = MAX_PACK_VOLTAGE_98S_DV; //Start with 98S value. Precised later
|
||||
datalayer_battery->info.min_design_voltage_dV = MIN_PACK_VOLTAGE_90S_DV; //Start with 90S value. Precised later
|
||||
|
|
|
@ -35,6 +35,7 @@ class KiaHyundai64Battery : public CanBattery {
|
|||
virtual void handle_incoming_can_frame(CAN_frame rx_frame);
|
||||
virtual void update_values();
|
||||
virtual void transmit_can(unsigned long currentMillis);
|
||||
static constexpr char* Name = "Kia/Hyundai 64/40kWh battery";
|
||||
|
||||
BatteryHtmlRenderer& get_status_renderer() { return renderer; }
|
||||
|
||||
|
|
|
@ -214,7 +214,7 @@ void KiaHyundaiHybridBattery::transmit_can(unsigned long currentMillis) {
|
|||
}
|
||||
|
||||
void KiaHyundaiHybridBattery::setup(void) { // Performs one time setup at startup
|
||||
strncpy(datalayer.system.info.battery_protocol, "Kia/Hyundai Hybrid", 63);
|
||||
strncpy(datalayer.system.info.battery_protocol, Name, 63);
|
||||
datalayer.system.info.battery_protocol[63] = '\0';
|
||||
datalayer.system.status.battery_allows_contactor_closing = true;
|
||||
datalayer.battery.info.number_of_cells = 56; // HEV , TODO: Make dynamic according to HEV/PHEV
|
||||
|
|
|
@ -15,6 +15,7 @@ class KiaHyundaiHybridBattery : public CanBattery {
|
|||
virtual void handle_incoming_can_frame(CAN_frame rx_frame);
|
||||
virtual void update_values();
|
||||
virtual void transmit_can(unsigned long currentMillis);
|
||||
static constexpr char* Name = "Kia/Hyundai Hybrid";
|
||||
|
||||
private:
|
||||
static const int MAX_PACK_VOLTAGE_DV = 2550; //5000 = 500.0V
|
||||
|
|
|
@ -2035,7 +2035,7 @@ void MebBattery::transmit_can(unsigned long currentMillis) {
|
|||
}
|
||||
|
||||
void MebBattery::setup(void) { // Performs one time setup at startup
|
||||
strncpy(datalayer.system.info.battery_protocol, "Volkswagen Group MEB platform via CAN-FD", 63);
|
||||
strncpy(datalayer.system.info.battery_protocol, Name, 63);
|
||||
datalayer.system.info.battery_protocol[63] = '\0';
|
||||
datalayer.battery.info.number_of_cells = 108; //Startup in 108S mode. We figure out the actual count later.
|
||||
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_108S_DV; //Defined later to correct pack size
|
||||
|
|
|
@ -17,6 +17,7 @@ class MebBattery : public CanBattery {
|
|||
virtual void transmit_can(unsigned long currentMillis);
|
||||
bool supports_real_BMS_status() { return true; }
|
||||
bool supports_charged_energy() { return true; }
|
||||
static constexpr char* Name = "Volkswagen Group MEB platform via CAN-FD";
|
||||
|
||||
BatteryHtmlRenderer& get_status_renderer() { return renderer; }
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ class Mg5Battery : public CanBattery {
|
|||
virtual void handle_incoming_can_frame(CAN_frame rx_frame);
|
||||
virtual void update_values();
|
||||
virtual void transmit_can(unsigned long currentMillis);
|
||||
static constexpr char* Name = "MG 5 battery";
|
||||
|
||||
private:
|
||||
static const int MAX_PACK_VOLTAGE_DV = 4040; //5000 = 500.0V
|
||||
|
|
|
@ -114,7 +114,7 @@ void OrionBms::transmit_can(unsigned long currentMillis) {
|
|||
}
|
||||
|
||||
void OrionBms::setup(void) { // Performs one time setup at startup
|
||||
strncpy(datalayer.system.info.battery_protocol, "DIY battery with Orion BMS (Victron setting)", 63);
|
||||
strncpy(datalayer.system.info.battery_protocol, Name, 63);
|
||||
datalayer.system.info.battery_protocol[63] = '\0';
|
||||
datalayer.battery.info.number_of_cells = NUMBER_OF_CELLS;
|
||||
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
|
||||
|
|
|
@ -15,6 +15,7 @@ class OrionBms : public CanBattery {
|
|||
virtual void handle_incoming_can_frame(CAN_frame rx_frame);
|
||||
virtual void update_values();
|
||||
virtual void transmit_can(unsigned long currentMillis);
|
||||
static constexpr char* Name = "DIY battery with Orion BMS (Victron setting)";
|
||||
|
||||
private:
|
||||
/* Change the following to suit your battery */
|
||||
|
|
|
@ -31,6 +31,7 @@ class PylonBattery : public CanBattery {
|
|||
virtual void handle_incoming_can_frame(CAN_frame rx_frame);
|
||||
virtual void update_values();
|
||||
virtual void transmit_can(unsigned long currentMillis);
|
||||
static constexpr char* Name = "Pylon compatible battery";
|
||||
|
||||
private:
|
||||
/* Change the following to suit your battery */
|
||||
|
|
|
@ -207,7 +207,7 @@ void RangeRoverPhevBattery::transmit_can(unsigned long currentMillis) {
|
|||
}
|
||||
|
||||
void RangeRoverPhevBattery::setup(void) { // Performs one time setup at startup
|
||||
strncpy(datalayer.system.info.battery_protocol, "Range Rover 13kWh PHEV battery (L494/L405)", 63);
|
||||
strncpy(datalayer.system.info.battery_protocol, Name, 63);
|
||||
datalayer.system.info.battery_protocol[63] = '\0';
|
||||
datalayer.system.status.battery_allows_contactor_closing = true;
|
||||
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
|
||||
|
|
|
@ -15,6 +15,7 @@ class RangeRoverPhevBattery : public CanBattery {
|
|||
virtual void handle_incoming_can_frame(CAN_frame rx_frame);
|
||||
virtual void update_values();
|
||||
virtual void transmit_can(unsigned long currentMillis);
|
||||
static constexpr char* Name = "Range Rover 13kWh PHEV battery (L494/L405)";
|
||||
|
||||
private:
|
||||
/* Change the following to suit your battery */
|
||||
|
|
|
@ -181,7 +181,7 @@ void RenaultKangooBattery::transmit_can(unsigned long currentMillis) {
|
|||
}
|
||||
|
||||
void RenaultKangooBattery::setup(void) { // Performs one time setup at startup
|
||||
strncpy(datalayer.system.info.battery_protocol, "Renault Kangoo", 63);
|
||||
strncpy(datalayer.system.info.battery_protocol, Name, 63);
|
||||
datalayer.system.info.battery_protocol[63] = '\0';
|
||||
datalayer.system.status.battery_allows_contactor_closing = true;
|
||||
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
|
||||
|
|
|
@ -15,6 +15,7 @@ class RenaultKangooBattery : public CanBattery {
|
|||
virtual void handle_incoming_can_frame(CAN_frame rx_frame);
|
||||
virtual void update_values();
|
||||
virtual void transmit_can(unsigned long currentMillis);
|
||||
static constexpr char* Name = "Renault Kangoo";
|
||||
|
||||
private:
|
||||
static const int MAX_PACK_VOLTAGE_DV = 4150; //5000 = 500.0V
|
||||
|
|
|
@ -118,7 +118,7 @@ void RenaultTwizyBattery::transmit_can(unsigned long currentMillis) {
|
|||
}
|
||||
|
||||
void RenaultTwizyBattery::setup(void) { // Performs one time setup at startup
|
||||
strncpy(datalayer.system.info.battery_protocol, "Renault Twizy", 63);
|
||||
strncpy(datalayer.system.info.battery_protocol, Name, 63);
|
||||
datalayer.system.info.battery_protocol[63] = '\0';
|
||||
datalayer.battery.info.number_of_cells = 14;
|
||||
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
|
||||
|
|
|
@ -13,6 +13,7 @@ class RenaultTwizyBattery : public CanBattery {
|
|||
virtual void handle_incoming_can_frame(CAN_frame rx_frame);
|
||||
virtual void update_values();
|
||||
virtual void transmit_can(unsigned long currentMillis);
|
||||
static constexpr char* Name = "Renault Twizy";
|
||||
|
||||
private:
|
||||
static const int MAX_PACK_VOLTAGE_DV = 579; // 57.9V at 100% SOC (with 70% SOH, new one might be higher)
|
||||
|
|
|
@ -513,7 +513,7 @@ void RenaultZoeGen1Battery::transmit_can(unsigned long currentMillis) {
|
|||
}
|
||||
|
||||
void RenaultZoeGen1Battery::setup(void) { // Performs one time setup at startup
|
||||
strncpy(datalayer.system.info.battery_protocol, "Renault Zoe Gen1 22/40kWh", 63);
|
||||
strncpy(datalayer.system.info.battery_protocol, Name, 63);
|
||||
datalayer.system.info.battery_protocol[63] = '\0';
|
||||
datalayer.system.status.battery_allows_contactor_closing = true;
|
||||
datalayer_battery->info.number_of_cells = 96;
|
||||
|
|
|
@ -31,6 +31,7 @@ class RenaultZoeGen1Battery : public CanBattery {
|
|||
virtual void handle_incoming_can_frame(CAN_frame rx_frame);
|
||||
virtual void update_values();
|
||||
virtual void transmit_can(unsigned long currentMillis);
|
||||
static constexpr char* Name = "Renault Zoe Gen1 22/40kWh";
|
||||
|
||||
BatteryHtmlRenderer& get_status_renderer() { return renderer; }
|
||||
|
||||
|
|
|
@ -704,7 +704,7 @@ void RenaultZoeGen2Battery::transmit_can(unsigned long currentMillis) {
|
|||
}
|
||||
|
||||
void RenaultZoeGen2Battery::setup(void) { // Performs one time setup at startup
|
||||
strncpy(datalayer.system.info.battery_protocol, "Renault Zoe Gen2 50kWh", 63);
|
||||
strncpy(datalayer.system.info.battery_protocol, Name, 63);
|
||||
datalayer.system.info.battery_protocol[63] = '\0';
|
||||
datalayer.system.status.battery_allows_contactor_closing = true;
|
||||
datalayer.battery.info.number_of_cells = 96;
|
||||
|
|
|
@ -15,6 +15,7 @@ class RenaultZoeGen2Battery : public CanBattery {
|
|||
virtual void handle_incoming_can_frame(CAN_frame rx_frame);
|
||||
virtual void update_values();
|
||||
virtual void transmit_can(unsigned long currentMillis);
|
||||
static constexpr char* Name = "Renault Zoe Gen2 50kWh";
|
||||
|
||||
bool supports_reset_NVROL() { return true; }
|
||||
|
||||
|
|
|
@ -516,7 +516,7 @@ void RjxzsBms::transmit_can(unsigned long currentMillis) {
|
|||
}
|
||||
|
||||
void RjxzsBms::setup(void) { // Performs one time setup at startup
|
||||
strncpy(datalayer.system.info.battery_protocol, "RJXZS BMS, DIY battery", 63);
|
||||
strncpy(datalayer.system.info.battery_protocol, Name, 63);
|
||||
datalayer.system.info.battery_protocol[63] = '\0';
|
||||
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
|
||||
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;
|
||||
|
|
|
@ -15,6 +15,7 @@ class RjxzsBms : public CanBattery {
|
|||
virtual void handle_incoming_can_frame(CAN_frame rx_frame);
|
||||
virtual void update_values();
|
||||
virtual void transmit_can(unsigned long currentMillis);
|
||||
static constexpr char* Name = "RJXZS BMS, DIY battery";
|
||||
|
||||
private:
|
||||
/* Tweak these according to your battery build */
|
||||
|
|
|
@ -319,7 +319,7 @@ void SantaFePhevBattery::transmit_can(unsigned long currentMillis) {
|
|||
}
|
||||
|
||||
void SantaFePhevBattery::setup(void) { // Performs one time setup at startup
|
||||
strncpy(datalayer.system.info.battery_protocol, "Santa Fe PHEV", 63);
|
||||
strncpy(datalayer.system.info.battery_protocol, Name, 63);
|
||||
datalayer.system.info.battery_protocol[63] = '\0';
|
||||
datalayer_battery->info.number_of_cells = 96;
|
||||
datalayer_battery->info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
|
||||
|
|
|
@ -27,6 +27,7 @@ class SantaFePhevBattery : public CanBattery {
|
|||
virtual void handle_incoming_can_frame(CAN_frame rx_frame);
|
||||
virtual void update_values();
|
||||
virtual void transmit_can(unsigned long currentMillis);
|
||||
static constexpr char* Name = "Santa Fe PHEV";
|
||||
|
||||
private:
|
||||
DATALAYER_BATTERY_TYPE* datalayer_battery;
|
||||
|
|
|
@ -92,7 +92,7 @@ void SimpBmsBattery::transmit_can(unsigned long currentMillis) {
|
|||
}
|
||||
|
||||
void SimpBmsBattery::setup(void) { // Performs one time setup at startup
|
||||
strncpy(datalayer.system.info.battery_protocol, "SIMPBMS battery", 63);
|
||||
strncpy(datalayer.system.info.battery_protocol, Name, 63);
|
||||
datalayer.system.info.battery_protocol[63] = '\0';
|
||||
datalayer.battery.info.number_of_cells = CELL_COUNT;
|
||||
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
|
||||
|
|
|
@ -15,6 +15,7 @@ class SimpBmsBattery : public CanBattery {
|
|||
virtual void handle_incoming_can_frame(CAN_frame rx_frame);
|
||||
virtual void update_values();
|
||||
virtual void transmit_can(unsigned long currentMillis);
|
||||
static constexpr char* Name = "SIMPBMS battery";
|
||||
|
||||
private:
|
||||
/* DEFAULT VALUES BMS will send configured */
|
||||
|
|
|
@ -140,7 +140,7 @@ void SonoBattery::transmit_can(unsigned long currentMillis) {
|
|||
}
|
||||
|
||||
void SonoBattery::setup(void) { // Performs one time setup at startup
|
||||
strncpy(datalayer.system.info.battery_protocol, "Sono Motors Sion 64kWh LFP ", 63);
|
||||
strncpy(datalayer.system.info.battery_protocol, Name, 63);
|
||||
datalayer.system.info.battery_protocol[63] = '\0';
|
||||
datalayer.battery.info.number_of_cells = 96;
|
||||
datalayer.system.status.battery_allows_contactor_closing = true;
|
||||
|
|
|
@ -15,6 +15,7 @@ class SonoBattery : public CanBattery {
|
|||
virtual void handle_incoming_can_frame(CAN_frame rx_frame);
|
||||
virtual void update_values();
|
||||
virtual void transmit_can(unsigned long currentMillis);
|
||||
static constexpr char* Name = "Sono Motors Sion 64kWh LFP ";
|
||||
|
||||
private:
|
||||
static const int MAX_PACK_VOLTAGE_DV = 5000; //5000 = 500.0V
|
||||
|
|
|
@ -377,7 +377,7 @@ void VolvoSpaBattery::transmit_can(unsigned long currentMillis) {
|
|||
}
|
||||
|
||||
void VolvoSpaBattery::setup(void) { // Performs one time setup at startup
|
||||
strncpy(datalayer.system.info.battery_protocol, "Volvo / Polestar 69/78kWh SPA battery", 63);
|
||||
strncpy(datalayer.system.info.battery_protocol, Name, 63);
|
||||
datalayer.system.info.battery_protocol[63] = '\0';
|
||||
datalayer.battery.info.number_of_cells = 0; // Initializes when all cells have been read
|
||||
datalayer.battery.info.total_capacity_Wh = 78200; //Startout in 78kWh mode (This value used for SOC calc)
|
||||
|
|
|
@ -16,6 +16,7 @@ class VolvoSpaBattery : public CanBattery {
|
|||
virtual void handle_incoming_can_frame(CAN_frame rx_frame);
|
||||
virtual void update_values();
|
||||
virtual void transmit_can(unsigned long currentMillis);
|
||||
static constexpr char* Name = "Volvo / Polestar 69/78kWh SPA battery";
|
||||
|
||||
bool supports_reset_DTC() { return true; }
|
||||
void reset_DTC() { datalayer_extended.VolvoPolestar.UserRequestDTCreset = true; }
|
||||
|
|
|
@ -552,8 +552,8 @@ void VolvoSpaHybridBattery::transmit_can(unsigned long currentMillis) {
|
|||
}
|
||||
}
|
||||
|
||||
void VolvoSpaHybridBattery::setup(void) { // Performs one time setup at startup
|
||||
strncpy(datalayer.system.info.battery_protocol, "Volvo PHEV battery", 63); //changed
|
||||
void VolvoSpaHybridBattery::setup(void) { // Performs one time setup at startup
|
||||
strncpy(datalayer.system.info.battery_protocol, Name, 63); //changed
|
||||
datalayer.system.info.battery_protocol[63] = '\0';
|
||||
datalayer.battery.info.number_of_cells = 102; //was 108, changed
|
||||
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
|
||||
|
|
|
@ -16,6 +16,7 @@ class VolvoSpaHybridBattery : public CanBattery {
|
|||
virtual void handle_incoming_can_frame(CAN_frame rx_frame);
|
||||
virtual void update_values();
|
||||
virtual void transmit_can(unsigned long currentMillis);
|
||||
static constexpr char* Name = "Volvo PHEV battery";
|
||||
|
||||
bool supports_reset_DTC() { return true; }
|
||||
void reset_DTC() { datalayer_extended.VolvoHybrid.UserRequestDTCreset = true; }
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
#define _CANRECEIVER_H
|
||||
|
||||
#include "src/devboard/utils/types.h"
|
||||
#include "src/include.h"
|
||||
|
||||
class CanReceiver {
|
||||
public:
|
||||
|
|
|
@ -29,6 +29,13 @@ const bool remote_bms_reset_default = true;
|
|||
#endif
|
||||
bool remote_bms_reset = remote_bms_reset_default;
|
||||
|
||||
#ifdef CONTACTOR_CONTROL_DOUBLE_BATTERY
|
||||
const bool contactor_control_enabled_double_battery_default = true;
|
||||
#else
|
||||
const bool contactor_control_enabled_double_battery_default = false;
|
||||
#endif
|
||||
bool contactor_control_enabled_double_battery = contactor_control_enabled_double_battery_default;
|
||||
|
||||
// TODO: Ensure valid values at run-time
|
||||
|
||||
// Parameters
|
||||
|
@ -105,12 +112,15 @@ void init_contactors() {
|
|||
set(PRECHARGE_PIN, OFF);
|
||||
}
|
||||
|
||||
#ifdef CONTACTOR_CONTROL_DOUBLE_BATTERY
|
||||
pinMode(SECOND_POSITIVE_CONTACTOR_PIN, OUTPUT);
|
||||
set(SECOND_POSITIVE_CONTACTOR_PIN, OFF);
|
||||
pinMode(SECOND_NEGATIVE_CONTACTOR_PIN, OUTPUT);
|
||||
set(SECOND_NEGATIVE_CONTACTOR_PIN, OFF);
|
||||
#endif // CONTACTOR_CONTROL_DOUBLE_BATTERY
|
||||
#if defined(SECOND_POSITIVE_CONTACTOR_PIN) && defined(SECOND_NEGATIVE_CONTACTOR_PIN)
|
||||
if (contactor_control_enabled_double_battery) {
|
||||
pinMode(SECOND_POSITIVE_CONTACTOR_PIN, OUTPUT);
|
||||
set(SECOND_POSITIVE_CONTACTOR_PIN, OFF);
|
||||
pinMode(SECOND_NEGATIVE_CONTACTOR_PIN, OUTPUT);
|
||||
set(SECOND_NEGATIVE_CONTACTOR_PIN, OFF);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Init BMS contactor
|
||||
#if defined HW_STARK || defined HW_3LB // This hardware has dedicated pin, always enable on start
|
||||
pinMode(BMS_POWER, OUTPUT); //LilyGo is omitted from this, only enabled if user selects PERIODIC_BMS_RESET
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
// Settings that can be changed at run-time
|
||||
extern bool contactor_control_enabled;
|
||||
extern bool contactor_control_enabled_double_battery;
|
||||
extern bool pwm_contactor_control;
|
||||
extern bool periodic_bms_reset;
|
||||
extern bool remote_bms_reset;
|
||||
|
|
|
@ -81,6 +81,7 @@ void init_stored_settings() {
|
|||
user_selected_charger_type = (ChargerType)settings.getUInt("CHGTYPE", (int)ChargerType::None);
|
||||
user_selected_second_battery = settings.getBool("DBLBTR", false);
|
||||
contactor_control_enabled = settings.getBool("CNTCTRL", false);
|
||||
contactor_control_enabled_double_battery = settings.getBool("CNTCTRLDBL", false);
|
||||
pwm_contactor_control = settings.getBool("PWMCNTCTRL", false);
|
||||
periodic_bms_reset = settings.getBool("PERBMSRESET", false);
|
||||
remote_bms_reset = settings.getBool("REMBMSRESET", false);
|
||||
|
|
|
@ -34,21 +34,25 @@ void store_settings_equipment_stop();
|
|||
*/
|
||||
void store_settings();
|
||||
|
||||
// Wraps the Preferences object begin/end calls, so that the scope of this object
|
||||
// runs them automatically (via constructor/destructor).
|
||||
class BatteryEmulatorSettingsStore {
|
||||
public:
|
||||
BatteryEmulatorSettingsStore() { settings.begin("batterySettings", false); }
|
||||
BatteryEmulatorSettingsStore(bool readOnly = false) {
|
||||
if (!settings.begin("batterySettings", readOnly)) {
|
||||
set_event(EVENT_PERSISTENT_SAVE_INFO, 0);
|
||||
}
|
||||
}
|
||||
|
||||
~BatteryEmulatorSettingsStore() { settings.end(); }
|
||||
|
||||
BatteryType get_batterytype() { return (BatteryType)settings.getUInt("BATTTYPE", (int)BatteryType::None); }
|
||||
uint32_t getUInt(const char* name, uint32_t defaultValue) { return settings.getUInt(name, defaultValue); }
|
||||
|
||||
void set_batterytype(BatteryType type) { settings.putUInt("BATTTYPE", (int)type); }
|
||||
void saveUInt(const char* name, uint32_t value) { settings.putUInt(name, value); }
|
||||
|
||||
InverterProtocolType get_invertertype() {
|
||||
return (InverterProtocolType)settings.getUInt("INVTYPE", (int)InverterProtocolType::None);
|
||||
}
|
||||
bool getBool(const char* name) { return settings.getBool(name, false); }
|
||||
|
||||
void set_invertertype(InverterProtocolType type) { settings.putUInt("INVTYPE", (int)type); }
|
||||
void saveBool(const char* name, bool value) { settings.putBool(name, value); }
|
||||
|
||||
private:
|
||||
Preferences settings;
|
||||
|
|
|
@ -55,161 +55,166 @@ void update_machineryprotection() {
|
|||
}
|
||||
|
||||
// Start checking that the battery is within reason. Incase we see any funny business, raise an event!
|
||||
// Don't check any battery issues if battery is not configured
|
||||
if (battery) {
|
||||
|
||||
// Pause function is on OR we have a critical fault event active
|
||||
if (emulator_pause_request_ON || (datalayer.battery.status.bms_status == FAULT)) {
|
||||
datalayer.battery.status.max_discharge_power_W = 0;
|
||||
datalayer.battery.status.max_charge_power_W = 0;
|
||||
}
|
||||
|
||||
// Battery is overheated!
|
||||
if (datalayer.battery.status.temperature_max_dC > BATTERY_MAXTEMPERATURE) {
|
||||
set_event(EVENT_BATTERY_OVERHEAT, datalayer.battery.status.temperature_max_dC);
|
||||
} else {
|
||||
clear_event(EVENT_BATTERY_OVERHEAT);
|
||||
}
|
||||
|
||||
// Battery is frozen!
|
||||
if (datalayer.battery.status.temperature_min_dC < BATTERY_MINTEMPERATURE) {
|
||||
set_event(EVENT_BATTERY_FROZEN, datalayer.battery.status.temperature_min_dC);
|
||||
} else {
|
||||
clear_event(EVENT_BATTERY_FROZEN);
|
||||
}
|
||||
|
||||
if (labs(datalayer.battery.status.temperature_max_dC - datalayer.battery.status.temperature_min_dC) >
|
||||
BATTERY_MAX_TEMPERATURE_DEVIATION) {
|
||||
set_event_latched(EVENT_BATTERY_TEMP_DEVIATION_HIGH,
|
||||
datalayer.battery.status.temperature_max_dC - datalayer.battery.status.temperature_min_dC);
|
||||
} else {
|
||||
clear_event(EVENT_BATTERY_TEMP_DEVIATION_HIGH);
|
||||
}
|
||||
|
||||
// Battery voltage is over designed max voltage!
|
||||
if (datalayer.battery.status.voltage_dV > datalayer.battery.info.max_design_voltage_dV) {
|
||||
set_event(EVENT_BATTERY_OVERVOLTAGE, datalayer.battery.status.voltage_dV);
|
||||
datalayer.battery.status.max_charge_power_W = 0;
|
||||
} else {
|
||||
clear_event(EVENT_BATTERY_OVERVOLTAGE);
|
||||
}
|
||||
|
||||
// Battery voltage is under designed min voltage!
|
||||
if (datalayer.battery.status.voltage_dV < datalayer.battery.info.min_design_voltage_dV) {
|
||||
set_event(EVENT_BATTERY_UNDERVOLTAGE, datalayer.battery.status.voltage_dV);
|
||||
datalayer.battery.status.max_discharge_power_W = 0;
|
||||
} else {
|
||||
clear_event(EVENT_BATTERY_UNDERVOLTAGE);
|
||||
}
|
||||
|
||||
// Cell overvoltage, further charging not possible. Battery might be imbalanced.
|
||||
if (datalayer.battery.status.cell_max_voltage_mV >= datalayer.battery.info.max_cell_voltage_mV) {
|
||||
set_event(EVENT_CELL_OVER_VOLTAGE, 0);
|
||||
datalayer.battery.status.max_charge_power_W = 0;
|
||||
}
|
||||
// Cell CRITICAL overvoltage, critical latching error without automatic reset. Requires user action to inspect battery.
|
||||
if (datalayer.battery.status.cell_max_voltage_mV >= (datalayer.battery.info.max_cell_voltage_mV + CELL_CRITICAL_MV)) {
|
||||
set_event(EVENT_CELL_CRITICAL_OVER_VOLTAGE, 0);
|
||||
}
|
||||
|
||||
// Cell undervoltage. Further discharge not possible. Battery might be imbalanced.
|
||||
if (datalayer.battery.status.cell_min_voltage_mV <= datalayer.battery.info.min_cell_voltage_mV) {
|
||||
set_event(EVENT_CELL_UNDER_VOLTAGE, 0);
|
||||
datalayer.battery.status.max_discharge_power_W = 0;
|
||||
}
|
||||
//Cell CRITICAL undervoltage. critical latching error without automatic reset. Requires user action to inspect battery.
|
||||
if (datalayer.battery.status.cell_min_voltage_mV <= (datalayer.battery.info.min_cell_voltage_mV - CELL_CRITICAL_MV)) {
|
||||
set_event(EVENT_CELL_CRITICAL_UNDER_VOLTAGE, 0);
|
||||
}
|
||||
|
||||
// Battery is fully charged. Dont allow any more power into it
|
||||
// Normally the BMS will send 0W allowed, but this acts as an additional layer of safety
|
||||
if (datalayer.battery.status.reported_soc == 10000) //Scaled SOC% value is 100.00%
|
||||
{
|
||||
if (!battery_full_event_fired) {
|
||||
set_event(EVENT_BATTERY_FULL, 0);
|
||||
battery_full_event_fired = true;
|
||||
// Pause function is on OR we have a critical fault event active
|
||||
if (emulator_pause_request_ON || (datalayer.battery.status.bms_status == FAULT)) {
|
||||
datalayer.battery.status.max_discharge_power_W = 0;
|
||||
datalayer.battery.status.max_charge_power_W = 0;
|
||||
}
|
||||
datalayer.battery.status.max_charge_power_W = 0;
|
||||
} else {
|
||||
clear_event(EVENT_BATTERY_FULL);
|
||||
battery_full_event_fired = false;
|
||||
}
|
||||
|
||||
// Battery is empty. Do not allow further discharge.
|
||||
// Normally the BMS will send 0W allowed, but this acts as an additional layer of safety
|
||||
if (datalayer.battery.status.bms_status == ACTIVE) {
|
||||
if (datalayer.battery.status.reported_soc == 0) { //Scaled SOC% value is 0.00%
|
||||
if (!battery_empty_event_fired) {
|
||||
set_event(EVENT_BATTERY_EMPTY, 0);
|
||||
battery_empty_event_fired = true;
|
||||
}
|
||||
// Battery is overheated!
|
||||
if (datalayer.battery.status.temperature_max_dC > BATTERY_MAXTEMPERATURE) {
|
||||
set_event(EVENT_BATTERY_OVERHEAT, datalayer.battery.status.temperature_max_dC);
|
||||
} else {
|
||||
clear_event(EVENT_BATTERY_OVERHEAT);
|
||||
}
|
||||
|
||||
// Battery is frozen!
|
||||
if (datalayer.battery.status.temperature_min_dC < BATTERY_MINTEMPERATURE) {
|
||||
set_event(EVENT_BATTERY_FROZEN, datalayer.battery.status.temperature_min_dC);
|
||||
} else {
|
||||
clear_event(EVENT_BATTERY_FROZEN);
|
||||
}
|
||||
|
||||
if (labs(datalayer.battery.status.temperature_max_dC - datalayer.battery.status.temperature_min_dC) >
|
||||
BATTERY_MAX_TEMPERATURE_DEVIATION) {
|
||||
set_event_latched(EVENT_BATTERY_TEMP_DEVIATION_HIGH,
|
||||
datalayer.battery.status.temperature_max_dC - datalayer.battery.status.temperature_min_dC);
|
||||
} else {
|
||||
clear_event(EVENT_BATTERY_TEMP_DEVIATION_HIGH);
|
||||
}
|
||||
|
||||
// Battery voltage is over designed max voltage!
|
||||
if (datalayer.battery.status.voltage_dV > datalayer.battery.info.max_design_voltage_dV) {
|
||||
set_event(EVENT_BATTERY_OVERVOLTAGE, datalayer.battery.status.voltage_dV);
|
||||
datalayer.battery.status.max_charge_power_W = 0;
|
||||
} else {
|
||||
clear_event(EVENT_BATTERY_OVERVOLTAGE);
|
||||
}
|
||||
|
||||
// Battery voltage is under designed min voltage!
|
||||
if (datalayer.battery.status.voltage_dV < datalayer.battery.info.min_design_voltage_dV) {
|
||||
set_event(EVENT_BATTERY_UNDERVOLTAGE, datalayer.battery.status.voltage_dV);
|
||||
datalayer.battery.status.max_discharge_power_W = 0;
|
||||
} else {
|
||||
clear_event(EVENT_BATTERY_EMPTY);
|
||||
battery_empty_event_fired = false;
|
||||
clear_event(EVENT_BATTERY_UNDERVOLTAGE);
|
||||
}
|
||||
}
|
||||
|
||||
// Battery is extremely degraded, not fit for secondlifestorage!
|
||||
if (datalayer.battery.status.soh_pptt < 2500) {
|
||||
set_event(EVENT_SOH_LOW, datalayer.battery.status.soh_pptt);
|
||||
} else {
|
||||
clear_event(EVENT_SOH_LOW);
|
||||
}
|
||||
// Cell overvoltage, further charging not possible. Battery might be imbalanced.
|
||||
if (datalayer.battery.status.cell_max_voltage_mV >= datalayer.battery.info.max_cell_voltage_mV) {
|
||||
set_event(EVENT_CELL_OVER_VOLTAGE, 0);
|
||||
datalayer.battery.status.max_charge_power_W = 0;
|
||||
}
|
||||
// Cell CRITICAL overvoltage, critical latching error without automatic reset. Requires user action to inspect battery.
|
||||
if (datalayer.battery.status.cell_max_voltage_mV >=
|
||||
(datalayer.battery.info.max_cell_voltage_mV + CELL_CRITICAL_MV)) {
|
||||
set_event(EVENT_CELL_CRITICAL_OVER_VOLTAGE, 0);
|
||||
}
|
||||
|
||||
if (battery && !battery->soc_plausible()) {
|
||||
set_event(EVENT_SOC_PLAUSIBILITY_ERROR, datalayer.battery.status.real_soc);
|
||||
}
|
||||
// Cell undervoltage. Further discharge not possible. Battery might be imbalanced.
|
||||
if (datalayer.battery.status.cell_min_voltage_mV <= datalayer.battery.info.min_cell_voltage_mV) {
|
||||
set_event(EVENT_CELL_UNDER_VOLTAGE, 0);
|
||||
datalayer.battery.status.max_discharge_power_W = 0;
|
||||
}
|
||||
//Cell CRITICAL undervoltage. critical latching error without automatic reset. Requires user action to inspect battery.
|
||||
if (datalayer.battery.status.cell_min_voltage_mV <=
|
||||
(datalayer.battery.info.min_cell_voltage_mV - CELL_CRITICAL_MV)) {
|
||||
set_event(EVENT_CELL_CRITICAL_UNDER_VOLTAGE, 0);
|
||||
}
|
||||
|
||||
// Check diff between highest and lowest cell
|
||||
cell_deviation_mV =
|
||||
std::abs(datalayer.battery.status.cell_max_voltage_mV - datalayer.battery.status.cell_min_voltage_mV);
|
||||
if (cell_deviation_mV > datalayer.battery.info.max_cell_voltage_deviation_mV) {
|
||||
set_event(EVENT_CELL_DEVIATION_HIGH, (cell_deviation_mV / 20));
|
||||
} else {
|
||||
clear_event(EVENT_CELL_DEVIATION_HIGH);
|
||||
}
|
||||
|
||||
// Inverter is charging with more power than battery wants!
|
||||
if (datalayer.battery.status.active_power_W > 0) { // Charging
|
||||
if (datalayer.battery.status.active_power_W > (datalayer.battery.status.max_charge_power_W + 2000)) {
|
||||
if (charge_limit_failures > MAX_CHARGE_DISCHARGE_LIMIT_FAILURES) {
|
||||
set_event(EVENT_CHARGE_LIMIT_EXCEEDED, 0); // Alert when 2kW over requested max
|
||||
} else {
|
||||
charge_limit_failures++;
|
||||
// Battery is fully charged. Dont allow any more power into it
|
||||
// Normally the BMS will send 0W allowed, but this acts as an additional layer of safety
|
||||
if (datalayer.battery.status.reported_soc == 10000) //Scaled SOC% value is 100.00%
|
||||
{
|
||||
if (!battery_full_event_fired) {
|
||||
set_event(EVENT_BATTERY_FULL, 0);
|
||||
battery_full_event_fired = true;
|
||||
}
|
||||
datalayer.battery.status.max_charge_power_W = 0;
|
||||
} else {
|
||||
clear_event(EVENT_CHARGE_LIMIT_EXCEEDED);
|
||||
charge_limit_failures = 0;
|
||||
clear_event(EVENT_BATTERY_FULL);
|
||||
battery_full_event_fired = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Inverter is pulling too much power from battery!
|
||||
if (datalayer.battery.status.active_power_W < 0) { // Discharging
|
||||
if (-datalayer.battery.status.active_power_W > (datalayer.battery.status.max_discharge_power_W + 2000)) {
|
||||
if (discharge_limit_failures > MAX_CHARGE_DISCHARGE_LIMIT_FAILURES) {
|
||||
set_event(EVENT_DISCHARGE_LIMIT_EXCEEDED, 0); // Alert when 2kW over requested max
|
||||
// Battery is empty. Do not allow further discharge.
|
||||
// Normally the BMS will send 0W allowed, but this acts as an additional layer of safety
|
||||
if (datalayer.battery.status.bms_status == ACTIVE) {
|
||||
if (datalayer.battery.status.reported_soc == 0) { //Scaled SOC% value is 0.00%
|
||||
if (!battery_empty_event_fired) {
|
||||
set_event(EVENT_BATTERY_EMPTY, 0);
|
||||
battery_empty_event_fired = true;
|
||||
}
|
||||
datalayer.battery.status.max_discharge_power_W = 0;
|
||||
} else {
|
||||
discharge_limit_failures++;
|
||||
clear_event(EVENT_BATTERY_EMPTY);
|
||||
battery_empty_event_fired = false;
|
||||
}
|
||||
} else {
|
||||
clear_event(EVENT_DISCHARGE_LIMIT_EXCEEDED);
|
||||
discharge_limit_failures = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error
|
||||
if (!datalayer.battery.status.CAN_battery_still_alive) {
|
||||
set_event(EVENT_CAN_BATTERY_MISSING, can_config.battery);
|
||||
} else {
|
||||
datalayer.battery.status.CAN_battery_still_alive--;
|
||||
clear_event(EVENT_CAN_BATTERY_MISSING);
|
||||
}
|
||||
// Battery is extremely degraded, not fit for secondlifestorage!
|
||||
if (datalayer.battery.status.soh_pptt < 2500) {
|
||||
set_event(EVENT_SOH_LOW, datalayer.battery.status.soh_pptt);
|
||||
} else {
|
||||
clear_event(EVENT_SOH_LOW);
|
||||
}
|
||||
|
||||
// Too many malformed CAN messages recieved!
|
||||
if (datalayer.battery.status.CAN_error_counter > MAX_CAN_FAILURES) {
|
||||
set_event(EVENT_CAN_CORRUPTED_WARNING, can_config.battery);
|
||||
} else {
|
||||
clear_event(EVENT_CAN_CORRUPTED_WARNING);
|
||||
if (battery && !battery->soc_plausible()) {
|
||||
set_event(EVENT_SOC_PLAUSIBILITY_ERROR, datalayer.battery.status.real_soc);
|
||||
}
|
||||
|
||||
// Check diff between highest and lowest cell
|
||||
cell_deviation_mV =
|
||||
std::abs(datalayer.battery.status.cell_max_voltage_mV - datalayer.battery.status.cell_min_voltage_mV);
|
||||
if (cell_deviation_mV > datalayer.battery.info.max_cell_voltage_deviation_mV) {
|
||||
set_event(EVENT_CELL_DEVIATION_HIGH, (cell_deviation_mV / 20));
|
||||
} else {
|
||||
clear_event(EVENT_CELL_DEVIATION_HIGH);
|
||||
}
|
||||
|
||||
// Inverter is charging with more power than battery wants!
|
||||
if (datalayer.battery.status.active_power_W > 0) { // Charging
|
||||
if (datalayer.battery.status.active_power_W > (datalayer.battery.status.max_charge_power_W + 2000)) {
|
||||
if (charge_limit_failures > MAX_CHARGE_DISCHARGE_LIMIT_FAILURES) {
|
||||
set_event(EVENT_CHARGE_LIMIT_EXCEEDED, 0); // Alert when 2kW over requested max
|
||||
} else {
|
||||
charge_limit_failures++;
|
||||
}
|
||||
} else {
|
||||
clear_event(EVENT_CHARGE_LIMIT_EXCEEDED);
|
||||
charge_limit_failures = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Inverter is pulling too much power from battery!
|
||||
if (datalayer.battery.status.active_power_W < 0) { // Discharging
|
||||
if (-datalayer.battery.status.active_power_W > (datalayer.battery.status.max_discharge_power_W + 2000)) {
|
||||
if (discharge_limit_failures > MAX_CHARGE_DISCHARGE_LIMIT_FAILURES) {
|
||||
set_event(EVENT_DISCHARGE_LIMIT_EXCEEDED, 0); // Alert when 2kW over requested max
|
||||
} else {
|
||||
discharge_limit_failures++;
|
||||
}
|
||||
} else {
|
||||
clear_event(EVENT_DISCHARGE_LIMIT_EXCEEDED);
|
||||
discharge_limit_failures = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error
|
||||
if (!datalayer.battery.status.CAN_battery_still_alive) {
|
||||
set_event(EVENT_CAN_BATTERY_MISSING, can_config.battery);
|
||||
} else {
|
||||
datalayer.battery.status.CAN_battery_still_alive--;
|
||||
clear_event(EVENT_CAN_BATTERY_MISSING);
|
||||
}
|
||||
|
||||
// Too many malformed CAN messages recieved!
|
||||
if (datalayer.battery.status.CAN_error_counter > MAX_CAN_FAILURES) {
|
||||
set_event(EVENT_CAN_CORRUPTED_WARNING, can_config.battery);
|
||||
} else {
|
||||
clear_event(EVENT_CAN_CORRUPTED_WARNING);
|
||||
}
|
||||
}
|
||||
|
||||
if (inverter && inverter->interface_type() == InverterInterfaceType::Can) {
|
||||
|
|
|
@ -42,6 +42,9 @@ enum PrechargeState {
|
|||
#define CAN_STILL_ALIVE 60
|
||||
// Set by battery each time we get a CAN message. Decrements every second. When reaching 0, sets event
|
||||
|
||||
typedef enum { CAN_NATIVE = 0, CANFD_NATIVE = 1, CAN_ADDON_MCP2515 = 2, CANFD_ADDON_MCP2518 = 3 } CAN_Interface;
|
||||
extern const char* getCANInterfaceName(CAN_Interface interface);
|
||||
|
||||
/* CAN Frame structure */
|
||||
typedef struct {
|
||||
bool FD;
|
||||
|
|
|
@ -107,29 +107,24 @@ String settings_processor(const String& var) {
|
|||
"<form action='saveSettings' method='post' style='display: grid; grid-template-columns: 1fr 2fr; gap: 10px; "
|
||||
"align-items: center;'>";
|
||||
content += "<label>Battery: </label><select style='max-width: 250px;' name='battery'>";
|
||||
content += battery_options(settings.get_batterytype());
|
||||
content += battery_options((BatteryType)settings.getUInt("BATTTYPE", (int)BatteryType::None));
|
||||
content += "</select>";
|
||||
content += "<label>Inverter protocol: </label><select style='max-width: 250px;' name='inverter'>";
|
||||
content += inverter_options(settings.get_invertertype());
|
||||
content += inverter_options((InverterProtocolType)settings.getUInt("INVTYPE", (int)InverterProtocolType::None));
|
||||
content += "</select>";
|
||||
content += "<label>Charger: </label><select style='max-width: 250px;' name='charger'>";
|
||||
content += charger_options((ChargerType)("CHGTYPE", 0));
|
||||
content += charger_options((ChargerType)settings.getUInt("CHGTYPE", 0));
|
||||
content += "</select>";
|
||||
//content += "<label>Double battery:</label>";
|
||||
|
||||
// TODO: Generalize settings: define settings in one place and use the definitions to render
|
||||
// UI and handle load/save
|
||||
render_checkbox(content, "Double battery", settings.get_doublebattery(), "dblbtr");
|
||||
render_checkbox(content, "Contactor control", get_bool("CNTCTRL"), "contctrl");
|
||||
render_checkbox(content, "PWM contactor control", get_bool("PWMCNTCTRL"), "pwmcontctrl");
|
||||
render_checkbox(content, "Periodic BMS reset", get_bool("PERBMSRESET"), "PERBMSRESET");
|
||||
render_checkbox(content, "Remote BMS reset", get_bool("REMBMSRESET"), "REMBMSRESET");
|
||||
render_checkbox(content, "Double battery", settings.getBool("DBLBTR"), "dblbtr");
|
||||
render_checkbox(content, "Contactor control", settings.getBool("CNTCTRL"), "contctrl");
|
||||
render_checkbox(content, "Contactor control double battery", settings.getBool("CNTCTRLDBL"), "contctrldbl");
|
||||
render_checkbox(content, "PWM contactor control", settings.getBool("PWMCNTCTRL"), "pwmcontctrl");
|
||||
render_checkbox(content, "Periodic BMS reset", settings.getBool("PERBMSRESET"), "PERBMSRESET");
|
||||
render_checkbox(content, "Remote BMS reset", settings.getBool("REMBMSRESET"), "REMBMSRESET");
|
||||
|
||||
/* content +=
|
||||
"<div style=\"display: flex; justify-content: flex-start;\"><input id='dblbtr' name='dblbtr' type='checkbox' "
|
||||
"style=\"margin-left: 0;\"";
|
||||
content += (user_selected_second_battery ? " checked" : "");
|
||||
content += " value='on'/></div>";*/
|
||||
content +=
|
||||
"<div style='grid-column: span 2; text-align: center; padding-top: 10px;'><button "
|
||||
"type='submit'>Save</button></div>";
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include "../../../USER_SECRETS.h"
|
||||
#include "../../battery/BATTERIES.h"
|
||||
#include "../../battery/Battery.h"
|
||||
#include "../../communication/contactorcontrol/comm_contactorcontrol.h"
|
||||
#include "../../communication/nvm/comm_nvm.h"
|
||||
#include "../../datalayer/datalayer.h"
|
||||
#include "../../datalayer/datalayer_extended.h"
|
||||
|
@ -39,7 +40,7 @@ const char get_firmware_info_html[] = R"rawliteral(%X%)rawliteral";
|
|||
String importedLogs = ""; // Store the uploaded logfile contents in RAM
|
||||
bool isReplayRunning = false; // Global flag to track replay state
|
||||
|
||||
// True when user has updated settings and a reboot is needed.
|
||||
// True when user has updated settings that need a reboot to be effective.
|
||||
bool settingsUpdated = false;
|
||||
|
||||
CAN_frame currentFrame = {.FD = true, .ext_ID = false, .DLC = 64, .ID = 0x12F, .data = {0}};
|
||||
|
@ -389,30 +390,32 @@ void init_webserver() {
|
|||
#ifdef COMMON_IMAGE
|
||||
// Handles the form POST from UI to save certain settings: battery/inverter type and double battery on/off
|
||||
server.on("/saveSettings", HTTP_POST, [](AsyncWebServerRequest* request) {
|
||||
BatteryEmulatorSettingsStore settings;
|
||||
|
||||
int params = request->params();
|
||||
// dblbtr not present in form content if not checked.
|
||||
bool secondBattery = false;
|
||||
for (int i = 0; i < params; i++) {
|
||||
auto p = request->getParam(i);
|
||||
if (p->name() == "inverter") {
|
||||
auto type = static_cast<InverterProtocolType>(atoi(p->value().c_str()));
|
||||
store_uint("INVTYPE", (int)type);
|
||||
settings.saveUInt("INVTYPE", (int)type);
|
||||
} else if (p->name() == "battery") {
|
||||
auto type = static_cast<BatteryType>(atoi(p->value().c_str()));
|
||||
store_uint("BATTTYPE", (int)type);
|
||||
settings.saveUInt("BATTTYPE", (int)type);
|
||||
} else if (p->name() == "charger") {
|
||||
auto type = static_cast<ChargerType>(atoi(p->value().c_str()));
|
||||
store_uint("CHGTYPE", (int)type);
|
||||
settings.saveUInt("CHGTYPE", (int)type);
|
||||
} else if (p->name() == "dblbtr") {
|
||||
store_bool("DBLBTR", p->value() == "on");
|
||||
settings.saveBool("DBLBTR", p->value() == "on");
|
||||
} else if (p->name() == "contctrl") {
|
||||
store_bool("CNTCTRL", p->value() == "on");
|
||||
settings.saveBool("CNTCTRL", p->value() == "on");
|
||||
} else if (p->name() == "contctrldbl") {
|
||||
settings.saveBool("CNTCTRLDBL", p->value() == "on");
|
||||
} else if (p->name() == "pwmcontctrl") {
|
||||
store_bool("PWMCNTCTRL", p->value() == "on");
|
||||
settings.saveBool("PWMCNTCTRL", p->value() == "on");
|
||||
} else if (p->name() == "PERBMSRESET") {
|
||||
store_bool("PERBMSRESET", p->value() == "on");
|
||||
settings.saveBool("PERBMSRESET", p->value() == "on");
|
||||
} else if (p->name() == "REMBMSRESET") {
|
||||
store_bool("REMBMSRESET", p->value() == "on");
|
||||
settings.saveBool("REMBMSRESET", p->value() == "on");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -937,6 +940,10 @@ String processor(const String& var) {
|
|||
|
||||
// Show version number
|
||||
content += "<h4>Software: " + String(version_number);
|
||||
|
||||
#ifdef COMMON_IMAGE
|
||||
content += " (Common image) ";
|
||||
#endif
|
||||
// Show hardware used:
|
||||
#ifdef HW_LILYGO
|
||||
content += " Hardware: LilyGo T-CAN485";
|
||||
|
@ -980,315 +987,98 @@ String processor(const String& var) {
|
|||
// Close the block
|
||||
content += "</div>";
|
||||
|
||||
// Start a new block with a specific background color
|
||||
content += "<div style='background-color: #333; padding: 10px; margin-bottom: 10px; border-radius: 50px'>";
|
||||
if (inverter || battery || shunt || charger) {
|
||||
// Start a new block with a specific background color
|
||||
content += "<div style='background-color: #333; padding: 10px; margin-bottom: 10px; border-radius: 50px'>";
|
||||
|
||||
// Display which components are used
|
||||
if (inverter) {
|
||||
content += "<h4 style='color: white;'>Inverter protocol: ";
|
||||
content += datalayer.system.info.inverter_protocol;
|
||||
content += " ";
|
||||
content += datalayer.system.info.inverter_brand;
|
||||
content += "</h4>";
|
||||
// Display which components are used
|
||||
if (inverter) {
|
||||
content += "<h4 style='color: white;'>Inverter protocol: ";
|
||||
content += datalayer.system.info.inverter_protocol;
|
||||
content += " ";
|
||||
content += datalayer.system.info.inverter_brand;
|
||||
content += "</h4>";
|
||||
}
|
||||
|
||||
if (battery) {
|
||||
content += "<h4 style='color: white;'>Battery protocol: ";
|
||||
content += datalayer.system.info.battery_protocol;
|
||||
if (battery2) {
|
||||
content += " (Double battery)";
|
||||
}
|
||||
if (datalayer.battery.info.chemistry == battery_chemistry_enum::LFP) {
|
||||
content += " (LFP)";
|
||||
}
|
||||
content += "</h4>";
|
||||
}
|
||||
|
||||
if (shunt) {
|
||||
content += "<h4 style='color: white;'>Shunt protocol: ";
|
||||
content += datalayer.system.info.shunt_protocol;
|
||||
content += "</h4>";
|
||||
}
|
||||
|
||||
if (charger) {
|
||||
content += "<h4 style='color: white;'>Charger protocol: ";
|
||||
content += charger->name();
|
||||
content += "</h4>";
|
||||
}
|
||||
|
||||
// Close the block
|
||||
content += "</div>";
|
||||
}
|
||||
|
||||
if (battery) {
|
||||
content += "<h4 style='color: white;'>Battery protocol: ";
|
||||
content += datalayer.system.info.battery_protocol;
|
||||
if (battery2) {
|
||||
content += " (Double battery)";
|
||||
}
|
||||
if (datalayer.battery.info.chemistry == battery_chemistry_enum::LFP) {
|
||||
content += " (LFP)";
|
||||
}
|
||||
content += "</h4>";
|
||||
}
|
||||
|
||||
if (shunt) {
|
||||
content += "<h4 style='color: white;'>Shunt protocol: ";
|
||||
content += datalayer.system.info.shunt_protocol;
|
||||
content += "</h4>";
|
||||
}
|
||||
|
||||
if (charger) {
|
||||
content += "<h4 style='color: white;'>Charger protocol: ";
|
||||
content += charger->name();
|
||||
content += "</h4>";
|
||||
}
|
||||
|
||||
// Close the block
|
||||
content += "</div>";
|
||||
|
||||
if (battery2) {
|
||||
// Start a new block with a specific background color. Color changes depending on BMS status
|
||||
content += "<div style='display: flex; width: 100%;'>";
|
||||
content += "<div style='flex: 1; background-color: ";
|
||||
} else {
|
||||
// Start a new block with a specific background color. Color changes depending on system status
|
||||
content += "<div style='background-color: ";
|
||||
}
|
||||
|
||||
switch (led_get_color()) {
|
||||
case led_color::GREEN:
|
||||
content += "#2D3F2F;";
|
||||
break;
|
||||
case led_color::YELLOW:
|
||||
content += "#F5CC00;";
|
||||
break;
|
||||
case led_color::BLUE:
|
||||
content += "#2B35AF;"; // Blue in test mode
|
||||
break;
|
||||
case led_color::RED:
|
||||
content += "#A70107;";
|
||||
break;
|
||||
default: // Some new color, make background green
|
||||
content += "#2D3F2F;";
|
||||
break;
|
||||
}
|
||||
|
||||
// Add the common style properties
|
||||
content += "padding: 10px; margin-bottom: 10px; border-radius: 50px;'>";
|
||||
|
||||
// Display battery statistics within this block
|
||||
float socRealFloat =
|
||||
static_cast<float>(datalayer.battery.status.real_soc) / 100.0; // Convert to float and divide by 100
|
||||
float socScaledFloat =
|
||||
static_cast<float>(datalayer.battery.status.reported_soc) / 100.0; // Convert to float and divide by 100
|
||||
float sohFloat =
|
||||
static_cast<float>(datalayer.battery.status.soh_pptt) / 100.0; // Convert to float and divide by 100
|
||||
float voltageFloat =
|
||||
static_cast<float>(datalayer.battery.status.voltage_dV) / 10.0; // Convert to float and divide by 10
|
||||
float currentFloat =
|
||||
static_cast<float>(datalayer.battery.status.current_dA) / 10.0; // Convert to float and divide by 10
|
||||
float powerFloat = static_cast<float>(datalayer.battery.status.active_power_W); // Convert to float
|
||||
float tempMaxFloat = static_cast<float>(datalayer.battery.status.temperature_max_dC) / 10.0; // Convert to float
|
||||
float tempMinFloat = static_cast<float>(datalayer.battery.status.temperature_min_dC) / 10.0; // Convert to float
|
||||
float maxCurrentChargeFloat =
|
||||
static_cast<float>(datalayer.battery.status.max_charge_current_dA) / 10.0; // Convert to float
|
||||
float maxCurrentDischargeFloat =
|
||||
static_cast<float>(datalayer.battery.status.max_discharge_current_dA) / 10.0; // Convert to float
|
||||
uint16_t cell_delta_mv =
|
||||
datalayer.battery.status.cell_max_voltage_mV - datalayer.battery.status.cell_min_voltage_mV;
|
||||
|
||||
if (datalayer.battery.settings.soc_scaling_active)
|
||||
content += "<h4 style='color: white;'>Scaled SOC: " + String(socScaledFloat, 2) +
|
||||
"% (real: " + String(socRealFloat, 2) + "%)</h4>";
|
||||
else
|
||||
content += "<h4 style='color: white;'>SOC: " + String(socRealFloat, 2) + "%</h4>";
|
||||
|
||||
content += "<h4 style='color: white;'>SOH: " + String(sohFloat, 2) + "%</h4>";
|
||||
content += "<h4 style='color: white;'>Voltage: " + String(voltageFloat, 1) +
|
||||
" V Current: " + String(currentFloat, 1) + " A</h4>";
|
||||
content += formatPowerValue("Power", powerFloat, "", 1);
|
||||
|
||||
if (datalayer.battery.settings.soc_scaling_active)
|
||||
content += "<h4 style='color: white;'>Scaled total capacity: " +
|
||||
formatPowerValue(datalayer.battery.info.reported_total_capacity_Wh, "h", 1) +
|
||||
" (real: " + formatPowerValue(datalayer.battery.info.total_capacity_Wh, "h", 1) + ")</h4>";
|
||||
else
|
||||
content += formatPowerValue("Total capacity", datalayer.battery.info.total_capacity_Wh, "h", 1);
|
||||
|
||||
if (datalayer.battery.settings.soc_scaling_active)
|
||||
content += "<h4 style='color: white;'>Scaled remaining capacity: " +
|
||||
formatPowerValue(datalayer.battery.status.reported_remaining_capacity_Wh, "h", 1) +
|
||||
" (real: " + formatPowerValue(datalayer.battery.status.remaining_capacity_Wh, "h", 1) + ")</h4>";
|
||||
else
|
||||
content += formatPowerValue("Remaining capacity", datalayer.battery.status.remaining_capacity_Wh, "h", 1);
|
||||
|
||||
if (datalayer.system.settings.equipment_stop_active) {
|
||||
content += formatPowerValue("Max discharge power", datalayer.battery.status.max_discharge_power_W, "", 1, "red");
|
||||
content += formatPowerValue("Max charge power", datalayer.battery.status.max_charge_power_W, "", 1, "red");
|
||||
content += "<h4 style='color: red;'>Max discharge current: " + String(maxCurrentDischargeFloat, 1) + " A</h4>";
|
||||
content += "<h4 style='color: red;'>Max charge current: " + String(maxCurrentChargeFloat, 1) + " A</h4>";
|
||||
} else {
|
||||
content += formatPowerValue("Max discharge power", datalayer.battery.status.max_discharge_power_W, "", 1);
|
||||
content += formatPowerValue("Max charge power", datalayer.battery.status.max_charge_power_W, "", 1);
|
||||
content += "<h4 style='color: white;'>Max discharge current: " + String(maxCurrentDischargeFloat, 1) + " A";
|
||||
if (datalayer.battery.settings.user_settings_limit_discharge) {
|
||||
content += " (Manual)</h4>";
|
||||
// Start a new block with a specific background color. Color changes depending on BMS status
|
||||
content += "<div style='display: flex; width: 100%;'>";
|
||||
content += "<div style='flex: 1; background-color: ";
|
||||
} else {
|
||||
content += " (BMS)</h4>";
|
||||
// Start a new block with a specific background color. Color changes depending on system status
|
||||
content += "<div style='background-color: ";
|
||||
}
|
||||
content += "<h4 style='color: white;'>Max charge current: " + String(maxCurrentChargeFloat, 1) + " A";
|
||||
if (datalayer.battery.settings.user_settings_limit_charge) {
|
||||
content += " (Manual)</h4>";
|
||||
} else {
|
||||
content += " (BMS)</h4>";
|
||||
}
|
||||
}
|
||||
|
||||
content += "<h4>Cell min/max: " + String(datalayer.battery.status.cell_min_voltage_mV) + " mV / " +
|
||||
String(datalayer.battery.status.cell_max_voltage_mV) + " mV</h4>";
|
||||
if (cell_delta_mv > datalayer.battery.info.max_cell_voltage_deviation_mV) {
|
||||
content += "<h4 style='color: red;'>Cell delta: " + String(cell_delta_mv) + " mV</h4>";
|
||||
} else {
|
||||
content += "<h4>Cell delta: " + String(cell_delta_mv) + " mV</h4>";
|
||||
}
|
||||
content +=
|
||||
"<h4>Temperature min/max: " + String(tempMinFloat, 1) + " °C / " + String(tempMaxFloat, 1) + " °C</h4>";
|
||||
|
||||
content += "<h4>System status: ";
|
||||
switch (datalayer.battery.status.bms_status) {
|
||||
case ACTIVE:
|
||||
content += String("OK");
|
||||
break;
|
||||
case UPDATING:
|
||||
content += String("UPDATING");
|
||||
break;
|
||||
case FAULT:
|
||||
content += String("FAULT");
|
||||
break;
|
||||
case INACTIVE:
|
||||
content += String("INACTIVE");
|
||||
break;
|
||||
case STANDBY:
|
||||
content += String("STANDBY");
|
||||
break;
|
||||
default:
|
||||
content += String("??");
|
||||
break;
|
||||
}
|
||||
content += "</h4>";
|
||||
|
||||
if (battery && battery->supports_real_BMS_status()) {
|
||||
content += "<h4>Battery BMS status: ";
|
||||
switch (datalayer.battery.status.real_bms_status) {
|
||||
case BMS_ACTIVE:
|
||||
content += String("OK");
|
||||
break;
|
||||
case BMS_FAULT:
|
||||
content += String("FAULT");
|
||||
break;
|
||||
case BMS_DISCONNECTED:
|
||||
content += String("DISCONNECTED");
|
||||
break;
|
||||
case BMS_STANDBY:
|
||||
content += String("STANDBY");
|
||||
break;
|
||||
default:
|
||||
content += String("??");
|
||||
break;
|
||||
}
|
||||
content += "</h4>";
|
||||
}
|
||||
|
||||
if (datalayer.battery.status.current_dA == 0) {
|
||||
content += "<h4>Battery idle</h4>";
|
||||
} else if (datalayer.battery.status.current_dA < 0) {
|
||||
content += "<h4>Battery discharging!";
|
||||
if (datalayer.battery.settings.inverter_limits_discharge) {
|
||||
content += " (Inverter limiting)</h4>";
|
||||
} else {
|
||||
if (datalayer.battery.settings.user_settings_limit_discharge) {
|
||||
content += " (Settings limiting)</h4>";
|
||||
} else {
|
||||
content += " (Battery limiting)</h4>";
|
||||
}
|
||||
}
|
||||
content += "</h4>";
|
||||
} else { // > 0 , positive current
|
||||
content += "<h4>Battery charging!";
|
||||
if (datalayer.battery.settings.inverter_limits_charge) {
|
||||
content += " (Inverter limiting)</h4>";
|
||||
} else {
|
||||
if (datalayer.battery.settings.user_settings_limit_charge) {
|
||||
content += " (Settings limiting)</h4>";
|
||||
} else {
|
||||
content += " (Battery limiting)</h4>";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
content += "<h4>Battery allows contactor closing: ";
|
||||
if (datalayer.system.status.battery_allows_contactor_closing == true) {
|
||||
content += "<span>✓</span>";
|
||||
} else {
|
||||
content += "<span style='color: red;'>✕</span>";
|
||||
}
|
||||
|
||||
content += " Inverter allows contactor closing: ";
|
||||
if (datalayer.system.status.inverter_allows_contactor_closing == true) {
|
||||
content += "<span>✓</span></h4>";
|
||||
} else {
|
||||
content += "<span style='color: red;'>✕</span></h4>";
|
||||
}
|
||||
if (emulator_pause_status == NORMAL)
|
||||
content += "<h4>Power status: " + String(get_emulator_pause_status().c_str()) + " </h4>";
|
||||
else
|
||||
content += "<h4 style='color: red;'>Power status: " + String(get_emulator_pause_status().c_str()) + " </h4>";
|
||||
|
||||
#ifdef CONTACTOR_CONTROL
|
||||
content += "<h4>Contactors controlled by emulator, state: ";
|
||||
if (datalayer.system.status.contactors_engaged) {
|
||||
content += "<span style='color: green;'>ON</span>";
|
||||
} else {
|
||||
content += "<span style='color: red;'>OFF</span>";
|
||||
}
|
||||
content += "</h4>";
|
||||
|
||||
content += "<h4>Precharge: (";
|
||||
content += PRECHARGE_TIME_MS;
|
||||
content += " ms) Cont. Neg.: ";
|
||||
#ifdef PWM_CONTACTOR_CONTROL
|
||||
if (datalayer.system.status.contactors_engaged) {
|
||||
content += "<span style='color: green;'>Economized</span>";
|
||||
content += " Cont. Pos.: ";
|
||||
content += "<span style='color: green;'>Economized</span>";
|
||||
} else {
|
||||
content += "<span style='color: red;'>✕</span>";
|
||||
content += " Cont. Pos.: ";
|
||||
content += "<span style='color: red;'>✕</span>";
|
||||
}
|
||||
|
||||
#else // No PWM_CONTACTOR_CONTROL , we can read the pin and see feedback. Helpful if channel overloaded
|
||||
if (digitalRead(NEGATIVE_CONTACTOR_PIN) == HIGH) {
|
||||
content += "<span style='color: green;'>✓</span>";
|
||||
} else {
|
||||
content += "<span style='color: red;'>✕</span>";
|
||||
}
|
||||
|
||||
content += " Cont. Pos.: ";
|
||||
if (digitalRead(POSITIVE_CONTACTOR_PIN) == HIGH) {
|
||||
content += "<span style='color: green;'>✓</span>";
|
||||
} else {
|
||||
content += "<span style='color: red;'>✕</span>";
|
||||
}
|
||||
#endif //no PWM_CONTACTOR_CONTROL
|
||||
content += "</h4>";
|
||||
#endif
|
||||
|
||||
// Close the block
|
||||
content += "</div>";
|
||||
|
||||
if (battery2) {
|
||||
content += "<div style='flex: 1; background-color: ";
|
||||
switch (datalayer.battery.status.bms_status) {
|
||||
case ACTIVE:
|
||||
switch (led_get_color()) {
|
||||
case led_color::GREEN:
|
||||
content += "#2D3F2F;";
|
||||
break;
|
||||
case FAULT:
|
||||
case led_color::YELLOW:
|
||||
content += "#F5CC00;";
|
||||
break;
|
||||
case led_color::BLUE:
|
||||
content += "#2B35AF;"; // Blue in test mode
|
||||
break;
|
||||
case led_color::RED:
|
||||
content += "#A70107;";
|
||||
break;
|
||||
default:
|
||||
default: // Some new color, make background green
|
||||
content += "#2D3F2F;";
|
||||
break;
|
||||
}
|
||||
|
||||
// Add the common style properties
|
||||
content += "padding: 10px; margin-bottom: 10px; border-radius: 50px;'>";
|
||||
|
||||
// Display battery statistics within this block
|
||||
socRealFloat =
|
||||
static_cast<float>(datalayer.battery2.status.real_soc) / 100.0; // Convert to float and divide by 100
|
||||
//socScaledFloat; // Same value used for bat2
|
||||
sohFloat = static_cast<float>(datalayer.battery2.status.soh_pptt) / 100.0; // Convert to float and divide by 100
|
||||
voltageFloat =
|
||||
static_cast<float>(datalayer.battery2.status.voltage_dV) / 10.0; // Convert to float and divide by 10
|
||||
currentFloat =
|
||||
static_cast<float>(datalayer.battery2.status.current_dA) / 10.0; // Convert to float and divide by 10
|
||||
powerFloat = static_cast<float>(datalayer.battery2.status.active_power_W); // Convert to float
|
||||
tempMaxFloat = static_cast<float>(datalayer.battery2.status.temperature_max_dC) / 10.0; // Convert to float
|
||||
tempMinFloat = static_cast<float>(datalayer.battery2.status.temperature_min_dC) / 10.0; // Convert to float
|
||||
cell_delta_mv = datalayer.battery2.status.cell_max_voltage_mV - datalayer.battery2.status.cell_min_voltage_mV;
|
||||
float socRealFloat =
|
||||
static_cast<float>(datalayer.battery.status.real_soc) / 100.0; // Convert to float and divide by 100
|
||||
float socScaledFloat =
|
||||
static_cast<float>(datalayer.battery.status.reported_soc) / 100.0; // Convert to float and divide by 100
|
||||
float sohFloat =
|
||||
static_cast<float>(datalayer.battery.status.soh_pptt) / 100.0; // Convert to float and divide by 100
|
||||
float voltageFloat =
|
||||
static_cast<float>(datalayer.battery.status.voltage_dV) / 10.0; // Convert to float and divide by 10
|
||||
float currentFloat =
|
||||
static_cast<float>(datalayer.battery.status.current_dA) / 10.0; // Convert to float and divide by 10
|
||||
float powerFloat = static_cast<float>(datalayer.battery.status.active_power_W); // Convert to float
|
||||
float tempMaxFloat = static_cast<float>(datalayer.battery.status.temperature_max_dC) / 10.0; // Convert to float
|
||||
float tempMinFloat = static_cast<float>(datalayer.battery.status.temperature_min_dC) / 10.0; // Convert to float
|
||||
float maxCurrentChargeFloat =
|
||||
static_cast<float>(datalayer.battery.status.max_charge_current_dA) / 10.0; // Convert to float
|
||||
float maxCurrentDischargeFloat =
|
||||
static_cast<float>(datalayer.battery.status.max_discharge_current_dA) / 10.0; // Convert to float
|
||||
uint16_t cell_delta_mv =
|
||||
datalayer.battery.status.cell_max_voltage_mV - datalayer.battery.status.cell_min_voltage_mV;
|
||||
|
||||
if (datalayer.battery.settings.soc_scaling_active)
|
||||
content += "<h4 style='color: white;'>Scaled SOC: " + String(socScaledFloat, 2) +
|
||||
|
@ -1303,117 +1093,342 @@ String processor(const String& var) {
|
|||
|
||||
if (datalayer.battery.settings.soc_scaling_active)
|
||||
content += "<h4 style='color: white;'>Scaled total capacity: " +
|
||||
formatPowerValue(datalayer.battery2.info.reported_total_capacity_Wh, "h", 1) +
|
||||
" (real: " + formatPowerValue(datalayer.battery2.info.total_capacity_Wh, "h", 1) + ")</h4>";
|
||||
formatPowerValue(datalayer.battery.info.reported_total_capacity_Wh, "h", 1) +
|
||||
" (real: " + formatPowerValue(datalayer.battery.info.total_capacity_Wh, "h", 1) + ")</h4>";
|
||||
else
|
||||
content += formatPowerValue("Total capacity", datalayer.battery2.info.total_capacity_Wh, "h", 1);
|
||||
content += formatPowerValue("Total capacity", datalayer.battery.info.total_capacity_Wh, "h", 1);
|
||||
|
||||
if (datalayer.battery.settings.soc_scaling_active)
|
||||
content += "<h4 style='color: white;'>Scaled remaining capacity: " +
|
||||
formatPowerValue(datalayer.battery2.status.reported_remaining_capacity_Wh, "h", 1) +
|
||||
" (real: " + formatPowerValue(datalayer.battery2.status.remaining_capacity_Wh, "h", 1) + ")</h4>";
|
||||
formatPowerValue(datalayer.battery.status.reported_remaining_capacity_Wh, "h", 1) +
|
||||
" (real: " + formatPowerValue(datalayer.battery.status.remaining_capacity_Wh, "h", 1) + ")</h4>";
|
||||
else
|
||||
content += formatPowerValue("Remaining capacity", datalayer.battery2.status.remaining_capacity_Wh, "h", 1);
|
||||
content += formatPowerValue("Remaining capacity", datalayer.battery.status.remaining_capacity_Wh, "h", 1);
|
||||
|
||||
if (datalayer.system.settings.equipment_stop_active) {
|
||||
content +=
|
||||
formatPowerValue("Max discharge power", datalayer.battery2.status.max_discharge_power_W, "", 1, "red");
|
||||
content += formatPowerValue("Max charge power", datalayer.battery2.status.max_charge_power_W, "", 1, "red");
|
||||
formatPowerValue("Max discharge power", datalayer.battery.status.max_discharge_power_W, "", 1, "red");
|
||||
content += formatPowerValue("Max charge power", datalayer.battery.status.max_charge_power_W, "", 1, "red");
|
||||
content += "<h4 style='color: red;'>Max discharge current: " + String(maxCurrentDischargeFloat, 1) + " A</h4>";
|
||||
content += "<h4 style='color: red;'>Max charge current: " + String(maxCurrentChargeFloat, 1) + " A</h4>";
|
||||
} else {
|
||||
content += formatPowerValue("Max discharge power", datalayer.battery2.status.max_discharge_power_W, "", 1);
|
||||
content += formatPowerValue("Max charge power", datalayer.battery2.status.max_charge_power_W, "", 1);
|
||||
content +=
|
||||
"<h4 style='color: white;'>Max discharge current: " + String(maxCurrentDischargeFloat, 1) + " A</h4>";
|
||||
content += "<h4 style='color: white;'>Max charge current: " + String(maxCurrentChargeFloat, 1) + " A</h4>";
|
||||
content += formatPowerValue("Max discharge power", datalayer.battery.status.max_discharge_power_W, "", 1);
|
||||
content += formatPowerValue("Max charge power", datalayer.battery.status.max_charge_power_W, "", 1);
|
||||
content += "<h4 style='color: white;'>Max discharge current: " + String(maxCurrentDischargeFloat, 1) + " A";
|
||||
if (datalayer.battery.settings.user_settings_limit_discharge) {
|
||||
content += " (Manual)</h4>";
|
||||
} else {
|
||||
content += " (BMS)</h4>";
|
||||
}
|
||||
content += "<h4 style='color: white;'>Max charge current: " + String(maxCurrentChargeFloat, 1) + " A";
|
||||
if (datalayer.battery.settings.user_settings_limit_charge) {
|
||||
content += " (Manual)</h4>";
|
||||
} else {
|
||||
content += " (BMS)</h4>";
|
||||
}
|
||||
}
|
||||
|
||||
content += "<h4>Cell min/max: " + String(datalayer.battery2.status.cell_min_voltage_mV) + " mV / " +
|
||||
String(datalayer.battery2.status.cell_max_voltage_mV) + " mV</h4>";
|
||||
if (cell_delta_mv > datalayer.battery2.info.max_cell_voltage_deviation_mV) {
|
||||
content += "<h4>Cell min/max: " + String(datalayer.battery.status.cell_min_voltage_mV) + " mV / " +
|
||||
String(datalayer.battery.status.cell_max_voltage_mV) + " mV</h4>";
|
||||
if (cell_delta_mv > datalayer.battery.info.max_cell_voltage_deviation_mV) {
|
||||
content += "<h4 style='color: red;'>Cell delta: " + String(cell_delta_mv) + " mV</h4>";
|
||||
} else {
|
||||
content += "<h4>Cell delta: " + String(cell_delta_mv) + " mV</h4>";
|
||||
}
|
||||
content += "<h4>Temperature min/max: " + String(tempMinFloat, 1) + " °C / " + String(tempMaxFloat, 1) +
|
||||
" °C</h4>";
|
||||
if (datalayer.battery.status.bms_status == ACTIVE) {
|
||||
content += "<h4>System status: OK </h4>";
|
||||
} else if (datalayer.battery.status.bms_status == UPDATING) {
|
||||
content += "<h4>System status: UPDATING </h4>";
|
||||
} else {
|
||||
content += "<h4>System status: FAULT </h4>";
|
||||
|
||||
content += "<h4>System status: ";
|
||||
switch (datalayer.battery.status.bms_status) {
|
||||
case ACTIVE:
|
||||
content += String("OK");
|
||||
break;
|
||||
case UPDATING:
|
||||
content += String("UPDATING");
|
||||
break;
|
||||
case FAULT:
|
||||
content += String("FAULT");
|
||||
break;
|
||||
case INACTIVE:
|
||||
content += String("INACTIVE");
|
||||
break;
|
||||
case STANDBY:
|
||||
content += String("STANDBY");
|
||||
break;
|
||||
default:
|
||||
content += String("??");
|
||||
break;
|
||||
}
|
||||
if (datalayer.battery2.status.current_dA == 0) {
|
||||
content += "<h4>Battery idle</h4>";
|
||||
} else if (datalayer.battery2.status.current_dA < 0) {
|
||||
content += "<h4>Battery discharging!</h4>";
|
||||
} else { // > 0
|
||||
content += "<h4>Battery charging!</h4>";
|
||||
content += "</h4>";
|
||||
|
||||
if (battery && battery->supports_real_BMS_status()) {
|
||||
content += "<h4>Battery BMS status: ";
|
||||
switch (datalayer.battery.status.real_bms_status) {
|
||||
case BMS_ACTIVE:
|
||||
content += String("OK");
|
||||
break;
|
||||
case BMS_FAULT:
|
||||
content += String("FAULT");
|
||||
break;
|
||||
case BMS_DISCONNECTED:
|
||||
content += String("DISCONNECTED");
|
||||
break;
|
||||
case BMS_STANDBY:
|
||||
content += String("STANDBY");
|
||||
break;
|
||||
default:
|
||||
content += String("??");
|
||||
break;
|
||||
}
|
||||
content += "</h4>";
|
||||
}
|
||||
|
||||
content += "<h4>Automatic contactor closing allowed:</h4>";
|
||||
content += "<h4>Battery: ";
|
||||
if (datalayer.system.status.battery2_allowed_contactor_closing == true) {
|
||||
if (datalayer.battery.status.current_dA == 0) {
|
||||
content += "<h4>Battery idle</h4>";
|
||||
} else if (datalayer.battery.status.current_dA < 0) {
|
||||
content += "<h4>Battery discharging!";
|
||||
if (datalayer.battery.settings.inverter_limits_discharge) {
|
||||
content += " (Inverter limiting)</h4>";
|
||||
} else {
|
||||
if (datalayer.battery.settings.user_settings_limit_discharge) {
|
||||
content += " (Settings limiting)</h4>";
|
||||
} else {
|
||||
content += " (Battery limiting)</h4>";
|
||||
}
|
||||
}
|
||||
content += "</h4>";
|
||||
} else { // > 0 , positive current
|
||||
content += "<h4>Battery charging!";
|
||||
if (datalayer.battery.settings.inverter_limits_charge) {
|
||||
content += " (Inverter limiting)</h4>";
|
||||
} else {
|
||||
if (datalayer.battery.settings.user_settings_limit_charge) {
|
||||
content += " (Settings limiting)</h4>";
|
||||
} else {
|
||||
content += " (Battery limiting)</h4>";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
content += "<h4>Battery allows contactor closing: ";
|
||||
if (datalayer.system.status.battery_allows_contactor_closing == true) {
|
||||
content += "<span>✓</span>";
|
||||
} else {
|
||||
content += "<span style='color: red;'>✕</span>";
|
||||
}
|
||||
|
||||
content += " Inverter: ";
|
||||
content += " Inverter allows contactor closing: ";
|
||||
if (datalayer.system.status.inverter_allows_contactor_closing == true) {
|
||||
content += "<span>✓</span></h4>";
|
||||
} else {
|
||||
content += "<span style='color: red;'>✕</span></h4>";
|
||||
}
|
||||
|
||||
if (emulator_pause_status == NORMAL)
|
||||
content += "<h4>Power status: " + String(get_emulator_pause_status().c_str()) + " </h4>";
|
||||
else
|
||||
content += "<h4 style='color: red;'>Power status: " + String(get_emulator_pause_status().c_str()) + " </h4>";
|
||||
|
||||
#ifdef CONTACTOR_CONTROL
|
||||
content += "<h4>Contactors controlled by emulator, state: ";
|
||||
if (datalayer.system.status.contactors_battery2_engaged) {
|
||||
content += "<span style='color: green;'>ON</span>";
|
||||
} else {
|
||||
content += "<span style='color: red;'>OFF</span>";
|
||||
}
|
||||
content += "</h4>";
|
||||
#ifdef CONTACTOR_CONTROL_DOUBLE_BATTERY
|
||||
content += "<h4>Cont. Neg.: ";
|
||||
#ifdef PWM_CONTACTOR_CONTROL
|
||||
if (datalayer.system.status.contactors_battery2_engaged) {
|
||||
content += "<span style='color: green;'>Economized</span>";
|
||||
content += " Cont. Pos.: ";
|
||||
content += "<span style='color: green;'>Economized</span>";
|
||||
} else {
|
||||
content += "<span style='color: red;'>✕</span>";
|
||||
content += " Cont. Pos.: ";
|
||||
content += "<span style='color: red;'>✕</span>";
|
||||
if (contactor_control_enabled) {
|
||||
content += "<h4>Contactors controlled by emulator, state: ";
|
||||
if (datalayer.system.status.contactors_engaged) {
|
||||
content += "<span style='color: green;'>ON</span>";
|
||||
} else {
|
||||
content += "<span style='color: red;'>OFF</span>";
|
||||
}
|
||||
content += "</h4>";
|
||||
|
||||
content += "<h4>Precharge: (";
|
||||
content += PRECHARGE_TIME_MS;
|
||||
content += " ms) Cont. Neg.: ";
|
||||
|
||||
if (pwm_contactor_control) {
|
||||
if (datalayer.system.status.contactors_engaged) {
|
||||
content += "<span style='color: green;'>Economized</span>";
|
||||
content += " Cont. Pos.: ";
|
||||
content += "<span style='color: green;'>Economized</span>";
|
||||
} else {
|
||||
content += "<span style='color: red;'>✕</span>";
|
||||
content += " Cont. Pos.: ";
|
||||
content += "<span style='color: red;'>✕</span>";
|
||||
}
|
||||
} else { // No PWM_CONTACTOR_CONTROL , we can read the pin and see feedback. Helpful if channel overloaded
|
||||
if (digitalRead(NEGATIVE_CONTACTOR_PIN) == HIGH) {
|
||||
content += "<span style='color: green;'>✓</span>";
|
||||
} else {
|
||||
content += "<span style='color: red;'>✕</span>";
|
||||
}
|
||||
|
||||
content += " Cont. Pos.: ";
|
||||
if (digitalRead(POSITIVE_CONTACTOR_PIN) == HIGH) {
|
||||
content += "<span style='color: green;'>✓</span>";
|
||||
} else {
|
||||
content += "<span style='color: red;'>✕</span>";
|
||||
}
|
||||
}
|
||||
content += "</h4>";
|
||||
}
|
||||
|
||||
#else // No PWM_CONTACTOR_CONTROL , we can read the pin and see feedback. Helpful if channel overloaded
|
||||
if (digitalRead(SECOND_NEGATIVE_CONTACTOR_PIN) == HIGH) {
|
||||
content += "<span style='color: green;'>✓</span>";
|
||||
} else {
|
||||
content += "<span style='color: red;'>✕</span>";
|
||||
}
|
||||
|
||||
content += " Cont. Pos.: ";
|
||||
if (digitalRead(SECOND_POSITIVE_CONTACTOR_PIN) == HIGH) {
|
||||
content += "<span style='color: green;'>✓</span>";
|
||||
} else {
|
||||
content += "<span style='color: red;'>✕</span>";
|
||||
}
|
||||
#endif //no PWM_CONTACTOR_CONTROL
|
||||
content += "</h4>";
|
||||
#endif // CONTACTOR_CONTROL_DOUBLE_BATTERY
|
||||
#endif // CONTACTOR_CONTROL
|
||||
|
||||
content += "</div>";
|
||||
// Close the block
|
||||
content += "</div>";
|
||||
|
||||
if (battery2) {
|
||||
content += "<div style='flex: 1; background-color: ";
|
||||
switch (datalayer.battery.status.bms_status) {
|
||||
case ACTIVE:
|
||||
content += "#2D3F2F;";
|
||||
break;
|
||||
case FAULT:
|
||||
content += "#A70107;";
|
||||
break;
|
||||
default:
|
||||
content += "#2D3F2F;";
|
||||
break;
|
||||
}
|
||||
// Add the common style properties
|
||||
content += "padding: 10px; margin-bottom: 10px; border-radius: 50px;'>";
|
||||
|
||||
// Display battery statistics within this block
|
||||
socRealFloat =
|
||||
static_cast<float>(datalayer.battery2.status.real_soc) / 100.0; // Convert to float and divide by 100
|
||||
//socScaledFloat; // Same value used for bat2
|
||||
sohFloat =
|
||||
static_cast<float>(datalayer.battery2.status.soh_pptt) / 100.0; // Convert to float and divide by 100
|
||||
voltageFloat =
|
||||
static_cast<float>(datalayer.battery2.status.voltage_dV) / 10.0; // Convert to float and divide by 10
|
||||
currentFloat =
|
||||
static_cast<float>(datalayer.battery2.status.current_dA) / 10.0; // Convert to float and divide by 10
|
||||
powerFloat = static_cast<float>(datalayer.battery2.status.active_power_W); // Convert to float
|
||||
tempMaxFloat = static_cast<float>(datalayer.battery2.status.temperature_max_dC) / 10.0; // Convert to float
|
||||
tempMinFloat = static_cast<float>(datalayer.battery2.status.temperature_min_dC) / 10.0; // Convert to float
|
||||
cell_delta_mv = datalayer.battery2.status.cell_max_voltage_mV - datalayer.battery2.status.cell_min_voltage_mV;
|
||||
|
||||
if (datalayer.battery.settings.soc_scaling_active)
|
||||
content += "<h4 style='color: white;'>Scaled SOC: " + String(socScaledFloat, 2) +
|
||||
"% (real: " + String(socRealFloat, 2) + "%)</h4>";
|
||||
else
|
||||
content += "<h4 style='color: white;'>SOC: " + String(socRealFloat, 2) + "%</h4>";
|
||||
|
||||
content += "<h4 style='color: white;'>SOH: " + String(sohFloat, 2) + "%</h4>";
|
||||
content += "<h4 style='color: white;'>Voltage: " + String(voltageFloat, 1) +
|
||||
" V Current: " + String(currentFloat, 1) + " A</h4>";
|
||||
content += formatPowerValue("Power", powerFloat, "", 1);
|
||||
|
||||
if (datalayer.battery.settings.soc_scaling_active)
|
||||
content += "<h4 style='color: white;'>Scaled total capacity: " +
|
||||
formatPowerValue(datalayer.battery2.info.reported_total_capacity_Wh, "h", 1) +
|
||||
" (real: " + formatPowerValue(datalayer.battery2.info.total_capacity_Wh, "h", 1) + ")</h4>";
|
||||
else
|
||||
content += formatPowerValue("Total capacity", datalayer.battery2.info.total_capacity_Wh, "h", 1);
|
||||
|
||||
if (datalayer.battery.settings.soc_scaling_active)
|
||||
content += "<h4 style='color: white;'>Scaled remaining capacity: " +
|
||||
formatPowerValue(datalayer.battery2.status.reported_remaining_capacity_Wh, "h", 1) +
|
||||
" (real: " + formatPowerValue(datalayer.battery2.status.remaining_capacity_Wh, "h", 1) + ")</h4>";
|
||||
else
|
||||
content += formatPowerValue("Remaining capacity", datalayer.battery2.status.remaining_capacity_Wh, "h", 1);
|
||||
|
||||
if (datalayer.system.settings.equipment_stop_active) {
|
||||
content +=
|
||||
formatPowerValue("Max discharge power", datalayer.battery2.status.max_discharge_power_W, "", 1, "red");
|
||||
content += formatPowerValue("Max charge power", datalayer.battery2.status.max_charge_power_W, "", 1, "red");
|
||||
content +=
|
||||
"<h4 style='color: red;'>Max discharge current: " + String(maxCurrentDischargeFloat, 1) + " A</h4>";
|
||||
content += "<h4 style='color: red;'>Max charge current: " + String(maxCurrentChargeFloat, 1) + " A</h4>";
|
||||
} else {
|
||||
content += formatPowerValue("Max discharge power", datalayer.battery2.status.max_discharge_power_W, "", 1);
|
||||
content += formatPowerValue("Max charge power", datalayer.battery2.status.max_charge_power_W, "", 1);
|
||||
content +=
|
||||
"<h4 style='color: white;'>Max discharge current: " + String(maxCurrentDischargeFloat, 1) + " A</h4>";
|
||||
content += "<h4 style='color: white;'>Max charge current: " + String(maxCurrentChargeFloat, 1) + " A</h4>";
|
||||
}
|
||||
|
||||
content += "<h4>Cell min/max: " + String(datalayer.battery2.status.cell_min_voltage_mV) + " mV / " +
|
||||
String(datalayer.battery2.status.cell_max_voltage_mV) + " mV</h4>";
|
||||
if (cell_delta_mv > datalayer.battery2.info.max_cell_voltage_deviation_mV) {
|
||||
content += "<h4 style='color: red;'>Cell delta: " + String(cell_delta_mv) + " mV</h4>";
|
||||
} else {
|
||||
content += "<h4>Cell delta: " + String(cell_delta_mv) + " mV</h4>";
|
||||
}
|
||||
content += "<h4>Temperature min/max: " + String(tempMinFloat, 1) + " °C / " + String(tempMaxFloat, 1) +
|
||||
" °C</h4>";
|
||||
if (datalayer.battery.status.bms_status == ACTIVE) {
|
||||
content += "<h4>System status: OK </h4>";
|
||||
} else if (datalayer.battery.status.bms_status == UPDATING) {
|
||||
content += "<h4>System status: UPDATING </h4>";
|
||||
} else {
|
||||
content += "<h4>System status: FAULT </h4>";
|
||||
}
|
||||
if (datalayer.battery2.status.current_dA == 0) {
|
||||
content += "<h4>Battery idle</h4>";
|
||||
} else if (datalayer.battery2.status.current_dA < 0) {
|
||||
content += "<h4>Battery discharging!</h4>";
|
||||
} else { // > 0
|
||||
content += "<h4>Battery charging!</h4>";
|
||||
}
|
||||
|
||||
content += "<h4>Automatic contactor closing allowed:</h4>";
|
||||
content += "<h4>Battery: ";
|
||||
if (datalayer.system.status.battery2_allowed_contactor_closing == true) {
|
||||
content += "<span>✓</span>";
|
||||
} else {
|
||||
content += "<span style='color: red;'>✕</span>";
|
||||
}
|
||||
|
||||
content += " Inverter: ";
|
||||
if (datalayer.system.status.inverter_allows_contactor_closing == true) {
|
||||
content += "<span>✓</span></h4>";
|
||||
} else {
|
||||
content += "<span style='color: red;'>✕</span></h4>";
|
||||
}
|
||||
|
||||
if (emulator_pause_status == NORMAL)
|
||||
content += "<h4>Power status: " + String(get_emulator_pause_status().c_str()) + " </h4>";
|
||||
else
|
||||
content += "<h4 style='color: red;'>Power status: " + String(get_emulator_pause_status().c_str()) + " </h4>";
|
||||
|
||||
if (contactor_control_enabled) {
|
||||
content += "<h4>Contactors controlled by emulator, state: ";
|
||||
if (datalayer.system.status.contactors_battery2_engaged) {
|
||||
content += "<span style='color: green;'>ON</span>";
|
||||
} else {
|
||||
content += "<span style='color: red;'>OFF</span>";
|
||||
}
|
||||
content += "</h4>";
|
||||
|
||||
if (contactor_control_enabled_double_battery) {
|
||||
content += "<h4>Cont. Neg.: ";
|
||||
if (pwm_contactor_control) {
|
||||
if (datalayer.system.status.contactors_battery2_engaged) {
|
||||
content += "<span style='color: green;'>Economized</span>";
|
||||
content += " Cont. Pos.: ";
|
||||
content += "<span style='color: green;'>Economized</span>";
|
||||
} else {
|
||||
content += "<span style='color: red;'>✕</span>";
|
||||
content += " Cont. Pos.: ";
|
||||
content += "<span style='color: red;'>✕</span>";
|
||||
}
|
||||
} else { // No PWM_CONTACTOR_CONTROL , we can read the pin and see feedback. Helpful if channel overloaded
|
||||
#if defined(SECOND_POSITIVE_CONTACTOR_PIN) && defined(SECOND_NEGATIVE_CONTACTOR_PIN)
|
||||
if (digitalRead(SECOND_NEGATIVE_CONTACTOR_PIN) == HIGH) {
|
||||
content += "<span style='color: green;'>✓</span>";
|
||||
} else {
|
||||
content += "<span style='color: red;'>✕</span>";
|
||||
}
|
||||
|
||||
content += " Cont. Pos.: ";
|
||||
if (digitalRead(SECOND_POSITIVE_CONTACTOR_PIN) == HIGH) {
|
||||
content += "<span style='color: green;'>✓</span>";
|
||||
} else {
|
||||
content += "<span style='color: red;'>✕</span>";
|
||||
}
|
||||
#endif
|
||||
}
|
||||
content += "</h4>";
|
||||
}
|
||||
}
|
||||
content += "</div>";
|
||||
content += "</div>";
|
||||
}
|
||||
}
|
||||
|
||||
if (charger) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue