Skip to content

Commit

Permalink
SOH calculations
Browse files Browse the repository at this point in the history
  • Loading branch information
stuartpittaway committed Dec 7, 2023
1 parent b58632f commit a74fe40
Show file tree
Hide file tree
Showing 6 changed files with 56 additions and 14 deletions.
12 changes: 11 additions & 1 deletion ESPController/include/defines.h
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ struct diybms_eeprom_settings

uint16_t currentMonitoring_shuntmv;
uint16_t currentMonitoring_shuntmaxcur;
/// @brief Amp-hours battery capacity
uint16_t currentMonitoring_batterycapacity;
uint16_t currentMonitoring_fullchargevolt;
uint16_t currentMonitoring_tailcurrent;
Expand Down Expand Up @@ -255,9 +256,18 @@ struct diybms_eeprom_settings
uint8_t canbus_equipment_addr; // battery index on the same canbus for PYLONFORCE, 0 - 15, default 0
char homeassist_apikey[24+1];

// State of health variables
/// @brief State of health variables - total lifetime mAh output (discharge)
uint32_t soh_total_milliamphour_out;
/// @brief State of health variables - total lifetime mAh input (charge)
uint32_t soh_total_milliamphour_in;
/// @brief State of health variables - total expected lifetime cycles of battery (6000)
uint16_t soh_lifetime_battery_cycles;
/// @brief State of health variables - estimated number of cycles
uint16_t soh_estimated_battery_cycles;
/// @brief State of health variables - discharge depth (80%)
uint8_t soh_discharge_depth;
/// @brief Calculated percentage calculation of health
float soh_percent;
};

typedef union
Expand Down
2 changes: 1 addition & 1 deletion ESPController/src/Rules.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -667,7 +667,7 @@ void Rules::CalculateDynamicChargeVoltage(const diybms_eeprom_settings *mysettin
/// @return SoC is rounded down to nearest integer and limits output range between 0 and 100.
uint16_t Rules::StateOfChargeWithRulesApplied(const diybms_eeprom_settings *mysettings, float realSOC) const
{
uint16_t value = floor(realSOC);
auto value = (uint16_t)floor(realSOC);

// Deliberately force SoC to be reported as 2%, to trick external CANBUS devices into trickle charging (if they support it)
if (mysettings->socforcelow)
Expand Down
28 changes: 23 additions & 5 deletions ESPController/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2821,11 +2821,11 @@ void send_ext_canbus_message(const uint32_t identifier, const uint8_t *buffer, c
if (res == ESP_OK)
{
canbus_messages_received++;
ESP_LOGD(TAG, "CANBUS received message ID: %0x, DLC: %d, flags: %0x",
message.identifier, message.data_length_code, message.flags);
// ESP_LOGD(TAG, "CANBUS received message ID: %0x, DLC: %d, flags: %0x",message.identifier, message.data_length_code, message.flags);

if (!(message.flags & TWAI_MSG_FLAG_RTR)) // we do not answer to Remote-Transmission-Requests
{
// ESP_LOG_BUFFER_HEXDUMP(TAG, message.data, message.data_length_code, ESP_LOG_DEBUG);
// ESP_LOG_BUFFER_HEXDUMP(TAG, message.data, message.data_length_code, ESP_LOG_DEBUG);
if (mysettings.canbusprotocol == CanBusProtocolEmulation::CANBUS_PYLONFORCEH2)
{
pylonforce_handle_rx(&message);
Expand Down Expand Up @@ -3195,10 +3195,25 @@ void send_ext_canbus_message(const uint32_t identifier, const uint8_t *buffer, c
// Screen off
tftsleep();
}

}
}

void CalculateStateOfHealth(diybms_eeprom_settings *settings)
{
// Value indicating what a typical discharge cycle looks like in amp-hours (normally 80% of cell for LFP)
float depth = 1000.0F * ((float)settings->currentMonitoring_batterycapacity/100.0F * (float)settings->soh_discharge_depth);
float in = (float)settings->soh_total_milliamphour_in / depth;
float out = (float)settings->soh_total_milliamphour_out / depth;
//Take worst case
float cycles = max(in, out);

settings->soh_percent =100-((cycles / (float)settings->soh_lifetime_battery_cycles) * 100.0F);

settings->soh_estimated_battery_cycles=(uint16_t)round(cycles);

ESP_LOGI(TAG, "State of health calc %f %, estimated cycles=%f", settings->soh_percent, cycles);
}

// Do activities which are not critical to the system like background loading of config, or updating timing results etc.
[[noreturn]] void lazy_tasks(void *)
{
Expand Down Expand Up @@ -3240,6 +3255,8 @@ void send_ext_canbus_message(const uint32_t identifier, const uint8_t *buffer, c

SaveConfiguration(&mysettings);

CalculateStateOfHealth(&mysettings);

// Reset the current monitor at midnight (ish)
CurrentMonitorResetDailyAmpHourCounters();

Expand Down Expand Up @@ -3840,7 +3857,6 @@ ESP32 Chip model = %u, Rev %u, Cores=%u, Features=%u)",
LoadConfiguration(&mysettings);
ValidateConfiguration(&mysettings);


if (strlen(mysettings.homeassist_apikey) == 0)
{
// Generate new key
Expand Down Expand Up @@ -3883,6 +3899,8 @@ ESP32 Chip model = %u, Rev %u, Cores=%u, Features=%u)",
currentmon_internal.GuessSOC();

currentmon_internal.TakeReadings();

CalculateStateOfHealth(&mysettings);
}
else
{
Expand Down
3 changes: 1 addition & 2 deletions ESPController/src/pylon_canbus.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,7 @@ void pylon_message_355()
data.stateofchargevalue = rules.StateOfChargeWithRulesApplied(&mysettings, currentMonitor.stateofcharge);

// 2 SOH value un16 1 %
// TODO: Need to determine this based on age of battery/cycles etc.
data.stateofhealthvalue = 100;
data.stateofhealthvalue = (uint16_t)(trunc(mysettings.soh_percent));

send_canbus_message(0x355, (uint8_t *)&data, sizeof(data355));
}
Expand Down
21 changes: 18 additions & 3 deletions ESPController/src/settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@ static const char homeassist_apikey_JSONKEY[] = "homeassistapikey";
static const char soh_total_milliamphour_out_JSONKEY[] = "soh_mah_out";
static const char soh_total_milliamphour_in_JSONKEY[] = "soh_mah_in";

static const char soh_lifetime_battery_cycles_JSONKEY[] = "soh_batcycle";
static const char soh_discharge_depth_JSONKEY[] = "soh_disdepth";

/* NVS KEYS
THESE STRINGS ARE USED TO HOLD THE PARAMETER IN NVS FLASH, MAXIMUM LENGTH OF 16 CHARACTERS
*/
Expand Down Expand Up @@ -188,7 +191,8 @@ static const char homeassist_apikey_NVSKEY[] = "haapikey";

static const char soh_total_milliamphour_out_NVSKEY[] = "soh_mah_out";
static const char soh_total_milliamphour_in_NVSKEY[] = "soh_mah_in";

static const char soh_lifetime_battery_cycles_NVSKEY[] = "soh_batcycle";
static const char soh_discharge_depth_NVSKEY[] = "soh_disdepth";

#define MACRO_NVSWRITE(VARNAME) writeSetting(nvs_handle, VARNAME##_NVSKEY, settings->VARNAME);
#define MACRO_NVSWRITE_UINT8(VARNAME) writeSetting(nvs_handle, VARNAME##_NVSKEY, (uint8_t)settings->VARNAME);
Expand Down Expand Up @@ -455,6 +459,8 @@ void SaveConfiguration(const diybms_eeprom_settings *settings)

MACRO_NVSWRITE(soh_total_milliamphour_out)
MACRO_NVSWRITE(soh_total_milliamphour_in)
MACRO_NVSWRITE(soh_lifetime_battery_cycles)
MACRO_NVSWRITE_UINT8(soh_discharge_depth)

ESP_ERROR_CHECK(nvs_commit(nvs_handle));
nvs_close(nvs_handle);
Expand Down Expand Up @@ -585,8 +591,10 @@ void LoadConfiguration(diybms_eeprom_settings *settings)

MACRO_NVSREADSTRING(homeassist_apikey);

MACRO_NVSREAD(soh_total_milliamphour_out);
MACRO_NVSREAD(soh_total_milliamphour_in);
MACRO_NVSREAD(soh_total_milliamphour_out)
MACRO_NVSREAD(soh_total_milliamphour_in)
MACRO_NVSREAD(soh_lifetime_battery_cycles)
MACRO_NVSREAD_UINT8(soh_discharge_depth)

nvs_close(nvs_handle);
}
Expand Down Expand Up @@ -774,6 +782,9 @@ void DefaultConfiguration(diybms_eeprom_settings *_myset)
// State of health
_myset->soh_total_milliamphour_out = 0;
_myset->soh_total_milliamphour_in = 0;
_myset->soh_lifetime_battery_cycles = 6000;
_myset->soh_discharge_depth = 80;
_myset->soh_percent = 100.0F;
}

/// @brief Save WIFI settings into FLASH NVS
Expand Down Expand Up @@ -1137,6 +1148,8 @@ void GenerateSettingsJSONDocument(DynamicJsonDocument *doc, diybms_eeprom_settin

root[soh_total_milliamphour_out_JSONKEY] = settings->soh_total_milliamphour_out;
root[soh_total_milliamphour_in_JSONKEY] = settings->soh_total_milliamphour_in;
root[soh_lifetime_battery_cycles_JSONKEY] = settings->soh_lifetime_battery_cycles;
root[soh_discharge_depth_JSONKEY] = settings->soh_discharge_depth;
}

void JSONToSettings(DynamicJsonDocument &doc, diybms_eeprom_settings *settings)
Expand Down Expand Up @@ -1232,6 +1245,8 @@ void JSONToSettings(DynamicJsonDocument &doc, diybms_eeprom_settings *settings)

settings->soh_total_milliamphour_out = root[soh_total_milliamphour_out_JSONKEY];
settings->soh_total_milliamphour_in = root[soh_total_milliamphour_in_JSONKEY];
settings->soh_lifetime_battery_cycles = root[soh_lifetime_battery_cycles_JSONKEY];
settings->soh_discharge_depth = root[soh_discharge_depth_JSONKEY];

strncpy(settings->homeassist_apikey, root[homeassist_apikey_JSONKEY].as<String>().c_str(), sizeof(settings->homeassist_apikey));

Expand Down
4 changes: 2 additions & 2 deletions ESPController/src/victron_canbus.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ void victron_message_355()
struct data355
{
uint16_t stateofchargevalue;
// uint16_t stateofhealthvalue;
uint16_t stateofhealthvalue;
// uint16_t highresolutionsoc;
};

Expand All @@ -193,7 +193,7 @@ void victron_message_355()
// 0 SOC value un16 1 %
data.stateofchargevalue = rules.StateOfChargeWithRulesApplied(&mysettings, currentMonitor.stateofcharge);
// 2 SOH value un16 1 %
// data.stateofhealthvalue = 100;
data.stateofhealthvalue = (uint16_t)(trunc(mysettings.soh_percent));

send_canbus_message(0x355, (uint8_t *)&data, sizeof(data355));
}
Expand Down

0 comments on commit a74fe40

Please sign in to comment.