From a74fe405ee8c45c0ee5ee597257c6e061f1ef381 Mon Sep 17 00:00:00 2001 From: Stuart Pittaway <1201909+stuartpittaway@users.noreply.github.com> Date: Thu, 7 Dec 2023 15:27:40 +0000 Subject: [PATCH] SOH calculations --- ESPController/include/defines.h | 12 +++++++++++- ESPController/src/Rules.cpp | 2 +- ESPController/src/main.cpp | 28 +++++++++++++++++++++++----- ESPController/src/pylon_canbus.cpp | 3 +-- ESPController/src/settings.cpp | 21 ++++++++++++++++++--- ESPController/src/victron_canbus.cpp | 4 ++-- 6 files changed, 56 insertions(+), 14 deletions(-) diff --git a/ESPController/include/defines.h b/ESPController/include/defines.h index 4ef491fa..29a409d6 100644 --- a/ESPController/include/defines.h +++ b/ESPController/include/defines.h @@ -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; @@ -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 diff --git a/ESPController/src/Rules.cpp b/ESPController/src/Rules.cpp index d4bfeb93..6c8cea9c 100644 --- a/ESPController/src/Rules.cpp +++ b/ESPController/src/Rules.cpp @@ -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) diff --git a/ESPController/src/main.cpp b/ESPController/src/main.cpp index 6426a436..dc438ce5 100644 --- a/ESPController/src/main.cpp +++ b/ESPController/src/main.cpp @@ -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); @@ -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 *) { @@ -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(); @@ -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 @@ -3883,6 +3899,8 @@ ESP32 Chip model = %u, Rev %u, Cores=%u, Features=%u)", currentmon_internal.GuessSOC(); currentmon_internal.TakeReadings(); + + CalculateStateOfHealth(&mysettings); } else { diff --git a/ESPController/src/pylon_canbus.cpp b/ESPController/src/pylon_canbus.cpp index 7d8ffbfe..d9e51ca6 100644 --- a/ESPController/src/pylon_canbus.cpp +++ b/ESPController/src/pylon_canbus.cpp @@ -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)); } diff --git a/ESPController/src/settings.cpp b/ESPController/src/settings.cpp index 72c9b3b4..1f36185d 100644 --- a/ESPController/src/settings.cpp +++ b/ESPController/src/settings.cpp @@ -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 */ @@ -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); @@ -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); @@ -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); } @@ -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 @@ -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) @@ -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().c_str(), sizeof(settings->homeassist_apikey)); diff --git a/ESPController/src/victron_canbus.cpp b/ESPController/src/victron_canbus.cpp index efa26fd6..47f67e1a 100644 --- a/ESPController/src/victron_canbus.cpp +++ b/ESPController/src/victron_canbus.cpp @@ -183,7 +183,7 @@ void victron_message_355() struct data355 { uint16_t stateofchargevalue; - // uint16_t stateofhealthvalue; + uint16_t stateofhealthvalue; // uint16_t highresolutionsoc; }; @@ -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)); }