From 8cb752463ffcfb3609dd41f19dccafbdb4b85921 Mon Sep 17 00:00:00 2001 From: Stuart Pittaway <1201909+stuartpittaway@users.noreply.github.com> Date: Thu, 7 Mar 2024 11:31:54 +0000 Subject: [PATCH] Store SoC into flash and restore on power up --- ESPController/include/CurrentMonitorINA229.h | 8 ++- ESPController/include/defines.h | 1 + ESPController/include/settings.h | 3 + ESPController/src/CurrentMonitorINA229.cpp | 60 ++++++---------- ESPController/src/main.cpp | 31 ++++++-- ESPController/src/settings.cpp | 75 ++++++++++++++++++-- 6 files changed, 126 insertions(+), 52 deletions(-) diff --git a/ESPController/include/CurrentMonitorINA229.h b/ESPController/include/CurrentMonitorINA229.h index 0e98b5a..47edf6f 100644 --- a/ESPController/include/CurrentMonitorINA229.h +++ b/ESPController/include/CurrentMonitorINA229.h @@ -217,9 +217,12 @@ class CurrentMonitorINA229 uint16_t shunttempcoefficient, bool TemperatureCompEnabled); - void GuessSOC(); + void DefaultSOC(); void TakeReadings(); + uint32_t raw_milliamphour_out() const{ return milliamphour_out; } + uint32_t raw_milliamphour_in() const{ return milliamphour_in; } + uint32_t calc_milliamphour_out() const{ return milliamphour_out - milliamphour_out_offset; } uint32_t calc_milliamphour_in() const{ return milliamphour_in - milliamphour_in_offset; } uint32_t calc_daily_milliamphour_out() const{ return daily_milliamphour_out; } @@ -264,12 +267,15 @@ class CurrentMonitorINA229 return registers.R_DIAG_ALRT & ALL_ALERT_BITS; } void SetSOC(uint16_t value); + void SetSOCByMilliAmpCounter(uint32_t in,uint32_t out); void ResetDailyAmpHourCounters() { daily_milliamphour_out=0; daily_milliamphour_in=0; } + uint16_t raw_stateofcharge() const { return SOC; } + private: uint16_t SOC = 0; float voltage = 0; diff --git a/ESPController/include/defines.h b/ESPController/include/defines.h index 76d63fc..235e54c 100644 --- a/ESPController/include/defines.h +++ b/ESPController/include/defines.h @@ -258,6 +258,7 @@ struct diybms_eeprom_settings char homeassist_apikey[24+1]; /// @brief State of health variables - total lifetime mAh output (discharge) + // Might need to watch overflow on the uint32 (max value 4,294,967,295mAh) = approx 15339 cycles of 280Ah battery uint32_t soh_total_milliamphour_out; /// @brief State of health variables - total lifetime mAh input (charge) uint32_t soh_total_milliamphour_in; diff --git a/ESPController/include/settings.h b/ESPController/include/settings.h index e0c6862..89179c0 100644 --- a/ESPController/include/settings.h +++ b/ESPController/include/settings.h @@ -43,4 +43,7 @@ void writeSetting(nvs_handle_t handle, const char *key, int8_t value); void writeSetting(nvs_handle_t handle, const char *key, const char *value); void writeSettingBlob(nvs_handle_t handle, const char *key, const void *value, size_t length); +bool GetStateOfCharge(uint32_t *in,uint32_t *out); +void SaveStateOfCharge(uint32_t,uint32_t); + #endif \ No newline at end of file diff --git a/ESPController/src/CurrentMonitorINA229.cpp b/ESPController/src/CurrentMonitorINA229.cpp index b4c271f..c7cf129 100644 --- a/ESPController/src/CurrentMonitorINA229.cpp +++ b/ESPController/src/CurrentMonitorINA229.cpp @@ -71,6 +71,20 @@ void CurrentMonitorINA229::CalculateLSB() // registers.R_SHUNT_CAL = ((uint32_t)registers.R_SHUNT_CAL * 985) / 1000; } + +void CurrentMonitorINA229::SetSOCByMilliAmpCounter(uint32_t in,uint32_t out) { + // Assume battery is fully charged + milliamphour_in = in; + // And we have consumed this much... + milliamphour_out = out; + + // Zero out readings using the offsets + milliamphour_out_offset = milliamphour_out; + milliamphour_in_offset = milliamphour_in; + + ESP_LOGI(TAG, "SetSOCByMilliAmpCounter mA in=%u, mA out=%u",milliamphour_in,milliamphour_out); +} + // Sets SOC by setting "fake" in/out amphour counts // value=8212 = 82.12% void CurrentMonitorINA229::SetSOC(uint16_t value) @@ -78,11 +92,13 @@ void CurrentMonitorINA229::SetSOC(uint16_t value) // Assume battery is fully charged milliamphour_in = 1000 * (uint32_t)registers.batterycapacity_amphour; // And we have consumed this much... - milliamphour_out = (uint32_t)((1.0F - ((float)value / 10000.0F)) * milliamphour_in); + milliamphour_out = (uint32_t)((1.0F - ((float)value / 10000.0F)) * (float)milliamphour_in); // Zero out readings using the offsets milliamphour_out_offset = milliamphour_out; milliamphour_in_offset = milliamphour_in; + + ESP_LOGI(TAG, "SetSOC mA in=%u, mA out=%u",milliamphour_in,milliamphour_out); } uint8_t CurrentMonitorINA229::readRegisterValue(INA_REGISTER r) const @@ -391,47 +407,11 @@ void CurrentMonitorINA229::CalculateAmpHourCounts() } } -// Guess the SoC % based on battery voltage - not accurate, just a guess! -void CurrentMonitorINA229::GuessSOC() +// Set SoC to default values +void CurrentMonitorINA229::DefaultSOC() { // Default SOC% at 60% - uint16_t soc = 6000; - - // We apply a "guestimate" to SoC based on voltage - not really accurate, but somewhere to start - // only applicable to 24V/48V (16S) LIFEPO4 setups. These voltages should be the unloaded (no current flowing) voltage. - // Assumption that its LIFEPO4 cells we are using... - float v = BusVoltage(); - - if (v > 20 && v < 30) - { - // Scale up 24V battery to use the 48V scale - v = v * 2; - } - - if (v > 40 && v < 60) - { - // 16S LIFEPO4... - if (v >= 40.0) - soc = 500; - if (v >= 48.0) - soc = 900; - if (v >= 50.0) - soc = 1400; - if (v >= 51.2) - soc = 1700; - if (v >= 51.6) - soc = 2000; - if (v >= 52.0) - soc = 3000; - if (v >= 52.4) - soc = 4000; - if (v >= 52.8) - soc = 7000; - if (v >= 53.2) - soc = 9000; - } - - SetSOC(soc); + SetSOC(6000); // Reset the daily counters daily_milliamphour_in = 0; diff --git a/ESPController/src/main.cpp b/ESPController/src/main.cpp index 22037a2..b9d0423 100644 --- a/ESPController/src/main.cpp +++ b/ESPController/src/main.cpp @@ -3704,7 +3704,7 @@ struct log_level_t }; // Default log levels to use for various components. -const std::array log_levels = +const std::array log_levels = { log_level_t{.tag = "*", .level = ESP_LOG_DEBUG}, {.tag = "wifi", .level = ESP_LOG_WARN}, @@ -3725,6 +3725,7 @@ const std::array log_levels = {.tag = "diybms-web", .level = ESP_LOG_INFO}, {.tag = "diybms-set", .level = ESP_LOG_INFO}, {.tag = "diybms-mqtt", .level = ESP_LOG_INFO}, + {.tag = "diybms-ctrl", .level = ESP_LOG_INFO}, {.tag = "diybms-pylon", .level = ESP_LOG_INFO}, {.tag = "diybms-pyforce", .level = ESP_LOG_INFO}, {.tag = "curmon", .level = ESP_LOG_INFO}}; @@ -3916,10 +3917,19 @@ ESP32 Chip model = %u, Rev %u, Cores=%u, Features=%u)", mysettings.currentMonitoring_shunttempcoefficient, mysettings.currentMonitoring_tempcompenabled); - currentmon_internal.GuessSOC(); + currentmon_internal.DefaultSOC(); + + uint32_t in; + uint32_t out; + if (GetStateOfCharge(&in,&out)) + { + currentmon_internal.SetSOCByMilliAmpCounter(in,out); + } currentmon_internal.TakeReadings(); + + CalculateStateOfHealth(&mysettings); } else @@ -4061,13 +4071,15 @@ void ESPCoreDumpToJSON(JsonObject &doc) ultoa(summary->ex_info.exc_vaddr, outputString, 16); core["exc_vaddr"] = outputString; - auto exc_a = core["exc_a"].to();; + auto exc_a = core["exc_a"].to(); + ; for (auto value : summary->ex_info.exc_a) { ultoa(value, outputString, 16); exc_a.add(outputString); } - auto epcx = core["epcx"].to();; + auto epcx = core["epcx"].to(); + ; for (auto value : summary->ex_info.epcx) { ultoa(value, outputString, 16); @@ -4227,7 +4239,14 @@ void loop() heap.free_blocks, heap.total_blocks); - // Report again in 30 seconds - heaptimer = currentMillis + 30000; + // Report again in 60 seconds + heaptimer = currentMillis + 60000; + + //Once per minute, store the state of charge into flash, just in case the controller is rebooted and we can restore this value + //on power up. + if (mysettings.currentMonitoringEnabled && mysettings.currentMonitoringDevice == CurrentMonitorDevice::DIYBMS_CURRENT_MON_INTERNAL) + { + SaveStateOfCharge(currentmon_internal.raw_milliamphour_in(),currentmon_internal.raw_milliamphour_out()); + } } } diff --git a/ESPController/src/settings.cpp b/ESPController/src/settings.cpp index b125868..da6e6b8 100644 --- a/ESPController/src/settings.cpp +++ b/ESPController/src/settings.cpp @@ -194,6 +194,9 @@ 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"; +static const char soc_milliamphour_out_NVSKEY[] = "soc_mah_out"; +static const char soc_milliamphour_in_NVSKEY[] = "soc_mah_in"; + #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); #define MACRO_NVSWRITESTRING(VARNAME) writeSetting(nvs_handle, VARNAME##_NVSKEY, &settings->VARNAME[0]); @@ -346,6 +349,64 @@ void writeSettingBlob(nvs_handle_t handle, const char *key, const void *value, s ESP_ERROR_CHECK(nvs_set_blob(handle, key, value, length)); } +/// @brief Reads state of charge (milliamp hour counts) from flash +/// @param in pointer to milliamp in count +/// @param out pointer to milliamp out count +/// @return true if values are valid +bool GetStateOfCharge(uint32_t *in, uint32_t *out) +{ + const char *partname = "diybms-ctrl"; + ESP_LOGI(TAG, "Read state of charge from flash"); + + nvs_handle_t nvs_handle; + auto err = nvs_open(partname, NVS_READONLY, &nvs_handle); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "Error (%s) opening NVS handle", esp_err_to_name(err)); + } + else + { + // Open + auto ret1 = getSetting(nvs_handle, soc_milliamphour_in_NVSKEY, in); + auto ret2 = getSetting(nvs_handle, soc_milliamphour_out_NVSKEY, out); + + nvs_close(nvs_handle); + + if (ret1 && ret2) + { + return true; + } + + ESP_LOGI(TAG, "SoC value doesn't exist in flash"); + } + return false; +} + +/// @brief Stores the milliamp current values to allow restore of state of charge on power up +/// @param in milliamp hours in +/// @param out milliamp hours out +void SaveStateOfCharge(uint32_t in, uint32_t out) +{ + const char *partname = "diybms-ctrl"; + ESP_LOGI(TAG, "Write SoC to flash in=%u out=%u", in, out); + + nvs_handle_t nvs_handle; + esp_err_t err = nvs_open(partname, NVS_READWRITE, &nvs_handle); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "Error %s opening NVS handle!", esp_err_to_name(err)); + } + else + { + // Save settings + writeSetting(nvs_handle, soc_milliamphour_in_NVSKEY, in); + writeSetting(nvs_handle, soc_milliamphour_out_NVSKEY, out); + + ESP_ERROR_CHECK(nvs_commit(nvs_handle)); + nvs_close(nvs_handle); + } +} + void SaveConfiguration(const diybms_eeprom_settings *settings) { const char *partname = "diybms-ctrl"; @@ -1004,8 +1065,9 @@ void ValidateConfiguration(diybms_eeprom_settings *settings) settings->stateofchargeresumevalue = settings->stateofchargeresumevalue; } - if (settings->soh_discharge_depth==0 || settings->soh_discharge_depth>100) { - settings->soh_discharge_depth=80; + if (settings->soh_discharge_depth == 0 || settings->soh_discharge_depth > 100) + { + settings->soh_discharge_depth = 80; } } @@ -1074,8 +1136,10 @@ void GenerateSettingsJSONDocument(JsonDocument &doc, diybms_eeprom_settings *set influxdb[influxdb_loggingFreqSeconds_JSONKEY] = settings->influxdb_loggingFreqSeconds; JsonObject outputs = root["outputs"].to(); - JsonArray d = outputs["default"].to();; - JsonArray t = outputs["type"].to();; + JsonArray d = outputs["default"].to(); + ; + JsonArray t = outputs["type"].to(); + ; for (uint8_t i = 0; i < RELAY_TOTAL; i++) { d.add(settings->rulerelaydefault[i]); @@ -1104,7 +1168,8 @@ void GenerateSettingsJSONDocument(JsonDocument &doc, diybms_eeprom_settings *set state["value"] = settings->rulevalue[rr]; state["hysteresis"] = settings->rulehysteresis[rr]; - JsonArray relaystate = state["state"].to();; + JsonArray relaystate = state["state"].to(); + ; for (uint8_t rt = 0; rt < RELAY_TOTAL; rt++) { relaystate.add(settings->rulerelaystate[rr][rt]);