diff --git a/Software/Software.ino b/Software/Software.ino index 6696ee02..8394317f 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -388,7 +388,7 @@ void init_stored_settings() { //always save the equipment stop status settings.putBool("EQUIPMENT_STOP", datalayer.system.settings.equipment_stop_active); -#endif +#endif //LOAD_SAVED_SETTINGS_ON_BOOT #ifdef WIFI @@ -404,7 +404,7 @@ void init_stored_settings() { password = tempPasswordString; } else { // Reading from settings failed. Do nothing with SSID. Raise event? } -#endif +#endif //WIFI temp = settings.getUInt("BATTERY_WH_MAX", false); if (temp != 0) { @@ -425,10 +425,20 @@ void init_stored_settings() { temp = settings.getUInt("MAXDISCHARGEAMP", false); if (temp != 0) { datalayer.battery.settings.max_user_set_discharge_dA = temp; - temp = settings.getBool("USE_SCALED_SOC", false); - datalayer.battery.settings.soc_scaling_active = temp; //This bool needs to be checked inside the temp!= block - } // No way to know if it wasnt reset otherwise + } + datalayer.battery.settings.soc_scaling_active = settings.getBool("USE_SCALED_SOC", false); + settings.end(); + settings.begin("batteryExtra", false); + temp = settings.getUInt("TARGETCHARGEVOLTAGE", false); + if (temp != 0) { + datalayer.battery.settings.max_user_set_charge_voltage_dV = temp; + } + temp = settings.getUInt("TARGETDISCHARGEVOLTAGE", false); + if (temp != 0) { + datalayer.battery.settings.max_user_set_discharge_voltage_dV = temp; + } + datalayer.battery.settings.user_set_voltage_limits_active = settings.getBool("USE_VOLTAGE_LIMITS", false); settings.end(); } @@ -1049,20 +1059,48 @@ void store_settings_equipment_stop() { } void storeSettings() { - settings.begin("batterySettings", false); + if (!settings.begin("batterySettings", false)) { + set_event(EVENT_PERSISTENT_SAVE_INFO, 0); + return; + } + #ifdef WIFI - settings.putString("SSID", String(ssid.c_str())); - settings.putString("PASSWORD", String(password.c_str())); + if (!settings.putString("SSID", String(ssid.c_str()))) { + set_event(EVENT_PERSISTENT_SAVE_INFO, 1); + } + if (!settings.putString("PASSWORD", String(password.c_str()))) { + set_event(EVENT_PERSISTENT_SAVE_INFO, 2); + } #endif - settings.putUInt("BATTERY_WH_MAX", datalayer.battery.info.total_capacity_Wh); - settings.putUInt("MAXPERCENTAGE", - datalayer.battery.settings.max_percentage / 10); // Divide by 10 for backwards compatibility - settings.putUInt("MINPERCENTAGE", - datalayer.battery.settings.min_percentage / 10); // Divide by 10 for backwards compatibility - settings.putUInt("MAXCHARGEAMP", datalayer.battery.settings.max_user_set_charge_dA); - settings.putUInt("MAXDISCHARGEAMP", datalayer.battery.settings.max_user_set_discharge_dA); - settings.putBool("USE_SCALED_SOC", datalayer.battery.settings.soc_scaling_active); - settings.end(); + + if (!settings.putUInt("BATTERY_WH_MAX", datalayer.battery.info.total_capacity_Wh)) { + set_event(EVENT_PERSISTENT_SAVE_INFO, 3); + } + if (!settings.putBool("USE_SCALED_SOC", datalayer.battery.settings.soc_scaling_active)) { + set_event(EVENT_PERSISTENT_SAVE_INFO, 4); + } + if (!settings.putUInt("MAXPERCENTAGE", datalayer.battery.settings.max_percentage / 10)) { + set_event(EVENT_PERSISTENT_SAVE_INFO, 5); + } + if (!settings.putUInt("MINPERCENTAGE", datalayer.battery.settings.min_percentage / 10)) { + set_event(EVENT_PERSISTENT_SAVE_INFO, 6); + } + if (!settings.putUInt("MAXCHARGEAMP", datalayer.battery.settings.max_user_set_charge_dA)) { + set_event(EVENT_PERSISTENT_SAVE_INFO, 7); + } + if (!settings.putUInt("MAXDISCHARGEAMP", datalayer.battery.settings.max_user_set_discharge_dA)) { + set_event(EVENT_PERSISTENT_SAVE_INFO, 8); + } + if (!settings.putBool("USE_VOLTAGE_LIMITS", datalayer.battery.settings.user_set_voltage_limits_active)) { + set_event(EVENT_PERSISTENT_SAVE_INFO, 9); + } + if (!settings.putUInt("TARGETCHARGEVOLTAGE", datalayer.battery.settings.max_user_set_charge_voltage_dV)) { + set_event(EVENT_PERSISTENT_SAVE_INFO, 10); + } + if (!settings.putUInt("TARGETDISCHARGEVOLTAGE", datalayer.battery.settings.max_user_set_discharge_voltage_dV)) { + set_event(EVENT_PERSISTENT_SAVE_INFO, 11); + } + settings.end(); // Close preferences handle } /** Reset reason numbering and description diff --git a/Software/USER_SETTINGS.h b/Software/USER_SETTINGS.h index 2f2cf3fe..792db0bf 100644 --- a/Software/USER_SETTINGS.h +++ b/Software/USER_SETTINGS.h @@ -125,10 +125,16 @@ #define BATTERY_MAXTEMPERATURE 500 // -250 = -25.0 °C , Min temperature (Will produce a battery frozen event if below) #define BATTERY_MINTEMPERATURE -250 -// 300 = 30.0A , BYD CAN specific setting, Max charge in Amp (Some inverters needs to be limited) +// 300 = 30.0A , Max charge in Amp (Some inverters needs to be limited) #define BATTERY_MAX_CHARGE_AMP 300 -// 300 = 30.0A , BYD CAN specific setting, Max discharge in Amp (Some inverters needs to be limited) +// 300 = 30.0A , Max discharge in Amp (Some inverters needs to be limited) #define BATTERY_MAX_DISCHARGE_AMP 300 +// Enable this to manually set voltage limits on how much battery can be discharged/charged. Normally not used. +#define BATTERY_USE_VOLTAGE_LIMITS false +// 5000 = 500.0V , Target charge voltage (Value can be tuned on the fly via webserver). Not used unless BATTERY_USE_VOLTAGE_LIMITS = true +#define BATTERY_MAX_CHARGE_VOLTAGE 5000 +// 3000 = 300.0V, Target discharge voltage (Value can be tuned on the fly via webserver). Not used unless BATTERY_USE_VOLTAGE_LIMITS = true +#define BATTERY_MAX_DISCHARGE_VOLTAGE 3000 /* Do not change any code below this line unless you are sure what you are doing */ /* Only change battery specific settings in "USER_SETTINGS.h" */ diff --git a/Software/src/datalayer/datalayer.h b/Software/src/datalayer/datalayer.h index e6de68ce..6064aa15 100644 --- a/Software/src/datalayer/datalayer.h +++ b/Software/src/datalayer/datalayer.h @@ -107,10 +107,20 @@ typedef struct { * you want the inverter to be able to use. At this real SOC, the inverter * will "see" 100% */ uint16_t max_percentage = BATTERY_MAXPERCENTAGE; + /** The user specified maximum allowed charge rate, in deciAmpere. 300 = 30.0 A */ uint16_t max_user_set_charge_dA = BATTERY_MAX_CHARGE_AMP; /** The user specified maximum allowed discharge rate, in deciAmpere. 300 = 30.0 A */ uint16_t max_user_set_discharge_dA = BATTERY_MAX_DISCHARGE_AMP; + + /** User specified discharge/charge voltages in use. Set to true to use user specified values */ + /** Some inverters like to see a specific target voltage for charge/discharge. Use these values to override automatic voltage limits*/ + bool user_set_voltage_limits_active = BATTERY_USE_VOLTAGE_LIMITS; + /** The user specified maximum allowed charge voltage, in deciVolt. 4000 = 400.0 V */ + uint16_t max_user_set_charge_voltage_dV = BATTERY_MAX_CHARGE_VOLTAGE; + /** The user specified maximum allowed discharge voltage, in deciVolt. 3000 = 300.0 V */ + uint16_t max_user_set_discharge_voltage_dV = BATTERY_MAX_DISCHARGE_VOLTAGE; + } DATALAYER_BATTERY_SETTINGS_TYPE; typedef struct { diff --git a/Software/src/devboard/utils/events.cpp b/Software/src/devboard/utils/events.cpp index b0c3e285..d51bd1c4 100644 --- a/Software/src/devboard/utils/events.cpp +++ b/Software/src/devboard/utils/events.cpp @@ -191,6 +191,7 @@ void init_events(void) { events.entries[EVENT_DUMMY_DEBUG].level = EVENT_LEVEL_DEBUG; events.entries[EVENT_DUMMY_WARNING].level = EVENT_LEVEL_WARNING; events.entries[EVENT_DUMMY_ERROR].level = EVENT_LEVEL_ERROR; + events.entries[EVENT_PERSISTENT_SAVE_INFO].level = EVENT_LEVEL_INFO; events.entries[EVENT_SERIAL_RX_WARNING].level = EVENT_LEVEL_WARNING; events.entries[EVENT_SERIAL_RX_FAILURE].level = EVENT_LEVEL_ERROR; events.entries[EVENT_SERIAL_TX_FAILURE].level = EVENT_LEVEL_ERROR; @@ -368,6 +369,8 @@ const char* get_event_message_string(EVENTS_ENUM_TYPE event) { return "The dummy warning event was set!"; // Don't change this event message! case EVENT_DUMMY_ERROR: return "The dummy error event was set!"; // Don't change this event message! + case EVENT_PERSISTENT_SAVE_INFO: + return "Info: Failed to save user settings. Namespace full?"; case EVENT_SERIAL_RX_WARNING: return "Error in serial function: No data received for some time, see data for minutes"; case EVENT_SERIAL_RX_FAILURE: diff --git a/Software/src/devboard/utils/events.h b/Software/src/devboard/utils/events.h index b50d7de4..d410d66f 100644 --- a/Software/src/devboard/utils/events.h +++ b/Software/src/devboard/utils/events.h @@ -6,7 +6,7 @@ // #define INCLUDE_EVENTS_TEST // Enable to run an event test loop, see events_test_on_target.cpp -#define EE_MAGIC_HEADER_VALUE 0x0017 // 0x0000 to 0xFFFF +#define EE_MAGIC_HEADER_VALUE 0x0018 // 0x0000 to 0xFFFF #define GENERATE_ENUM(ENUM) ENUM, #define GENERATE_STRING(STRING) #STRING, @@ -79,6 +79,7 @@ XX(EVENT_DUMMY_DEBUG) \ XX(EVENT_DUMMY_WARNING) \ XX(EVENT_DUMMY_ERROR) \ + XX(EVENT_PERSISTENT_SAVE_INFO) \ XX(EVENT_SERIAL_RX_WARNING) \ XX(EVENT_SERIAL_RX_FAILURE) \ XX(EVENT_SERIAL_TX_FAILURE) \ diff --git a/Software/src/devboard/webserver/settings_html.cpp b/Software/src/devboard/webserver/settings_html.cpp index 484e5021..5a26b745 100644 --- a/Software/src/devboard/webserver/settings_html.cpp +++ b/Software/src/devboard/webserver/settings_html.cpp @@ -67,6 +67,21 @@ String settings_processor(const String& var) { content += "

Max discharge speed: " + String(datalayer.battery.settings.max_user_set_discharge_dA / 10.0, 1) + " A

"; + content += "

Manual charge voltage limits: " + + String(datalayer.battery.settings.user_set_voltage_limits_active + ? "" + : "") + + "

"; + content += + "

Target charge voltage: " + String(datalayer.battery.settings.max_user_set_charge_voltage_dV / 10.0, 1) + + " V

"; + content += "

Target discharge voltage: " + + String(datalayer.battery.settings.max_user_set_discharge_voltage_dV / 10.0, 1) + + " V

"; // Close the block content += ""; @@ -130,7 +145,9 @@ String settings_processor(const String& var) { "updateBatterySize?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter a value between 1 " "and 120000.');}}}"; content += - "function editUseScaledSOC(){var value=prompt('Should SOC% be scaled? (0 = No, 1 = " + "function editUseScaledSOC(){var value=prompt('Extends battery life by rescaling the SOC within the configured " + "minimum " + "and maximum percentage. Should SOC scaling be applied? (0 = No, 1 = " "Yes):');if(value!==null){if(value==0||value==1){var xhr=new " "XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/" "updateUseScaledSOC?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter a value between 0 " @@ -161,6 +178,33 @@ String settings_processor(const String& var) { "XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/" "updateMaxDischargeA?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter a value between 0 " "and 1000.0');}}}"; + content += + "function editUseVoltageLimit(){var value=prompt('Enable this option to manually restrict charge/discharge to " + "a specific voltage set below." + "If disabled the emulator automatically determines this based on battery limits. Restrict manually? (0 = No, 1 " + "= Yes)" + ":');if(value!==null){if(value==0||value==1){var xhr=new " + "XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/" + "updateUseVoltageLimit?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter a value between " + "0 " + "and 1.');}}}"; + content += + "function editMaxChargeVoltage(){var value=prompt('Some inverters needs to be artificially limited. Enter new " + "voltage setpoint batttery should charge to (0-1000.0):');if(value!==null){if(value>=0&&value<=1000){var " + "xhr=new " + "XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/" + "updateMaxChargeVoltage?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter a value " + "between 0 " + "and 1000.0');}}}"; + content += + "function editMaxDischargeVoltage(){var value=prompt('Some inverters needs to be artificially limited. Enter " + "new " + "voltage setpoint batttery should discharge to (0-1000.0):');if(value!==null){if(value>=0&&value<=1000){var " + "xhr=new " + "XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/" + "updateMaxDischargeVoltage?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter a value " + "between 0 " + "and 1000.0');}}}"; #ifdef TEST_FAKE_BATTERY content += diff --git a/Software/src/devboard/webserver/webserver.cpp b/Software/src/devboard/webserver/webserver.cpp index cfdf2982..ff7770ee 100644 --- a/Software/src/devboard/webserver/webserver.cpp +++ b/Software/src/devboard/webserver/webserver.cpp @@ -272,6 +272,48 @@ void init_webserver() { } }); + // Route for editing BATTERY_USE_VOLTAGE_LIMITS + server.on("/updateUseVoltageLimit", HTTP_GET, [](AsyncWebServerRequest* request) { + if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) + return request->requestAuthentication(); + if (request->hasParam("value")) { + String value = request->getParam("value")->value(); + datalayer.battery.settings.user_set_voltage_limits_active = value.toInt(); + storeSettings(); + request->send(200, "text/plain", "Updated successfully"); + } else { + request->send(400, "text/plain", "Bad Request"); + } + }); + + // Route for editing MaxChargeVoltage + server.on("/updateMaxChargeVoltage", HTTP_GET, [](AsyncWebServerRequest* request) { + if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) + return request->requestAuthentication(); + if (request->hasParam("value")) { + String value = request->getParam("value")->value(); + datalayer.battery.settings.max_user_set_charge_voltage_dV = static_cast(value.toFloat() * 10); + storeSettings(); + request->send(200, "text/plain", "Updated successfully"); + } else { + request->send(400, "text/plain", "Bad Request"); + } + }); + + // Route for editing MaxDischargeVoltage + server.on("/updateMaxDischargeVoltage", HTTP_GET, [](AsyncWebServerRequest* request) { + if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) + return request->requestAuthentication(); + if (request->hasParam("value")) { + String value = request->getParam("value")->value(); + datalayer.battery.settings.max_user_set_discharge_voltage_dV = static_cast(value.toFloat() * 10); + storeSettings(); + request->send(200, "text/plain", "Updated successfully"); + } else { + request->send(400, "text/plain", "Bad Request"); + } + }); + // Route for resetting SOH on Nissan LEAF batteries server.on("/resetSOH", HTTP_GET, [](AsyncWebServerRequest* request) { if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) { diff --git a/Software/src/inverter/BYD-CAN.cpp b/Software/src/inverter/BYD-CAN.cpp index 4ffa7b20..d7a4028f 100644 --- a/Software/src/inverter/BYD-CAN.cpp +++ b/Software/src/inverter/BYD-CAN.cpp @@ -8,6 +8,8 @@ static unsigned long previousMillis2s = 0; // will store last time a 2s CAN Me static unsigned long previousMillis10s = 0; // will store last time a 10s CAN Message was send static unsigned long previousMillis60s = 0; // will store last time a 60s CAN Message was send +#define VOLTAGE_OFFSET_DV 20 + CAN_frame BYD_250 = {.FD = false, .ext_ID = false, .DLC = 8, @@ -98,12 +100,22 @@ void update_values_can_inverter() { //This function maps all the values fetched } //Map values to CAN messages - //Maxvoltage (eg 400.0V = 4000 , 16bits long) - BYD_110.data.u8[0] = (datalayer.battery.info.max_design_voltage_dV >> 8); - BYD_110.data.u8[1] = (datalayer.battery.info.max_design_voltage_dV & 0x00FF); - //Minvoltage (eg 300.0V = 3000 , 16bits long) - BYD_110.data.u8[2] = (datalayer.battery.info.min_design_voltage_dV >> 8); - BYD_110.data.u8[3] = (datalayer.battery.info.min_design_voltage_dV & 0x00FF); + if (datalayer.battery.settings.user_set_voltage_limits_active) { //If user is requesting a specific voltage + //Target charge voltage (eg 400.0V = 4000 , 16bits long) + BYD_110.data.u8[0] = (datalayer.battery.settings.max_user_set_charge_voltage_dV >> 8); + BYD_110.data.u8[1] = (datalayer.battery.settings.max_user_set_charge_voltage_dV & 0x00FF); + //Target discharge voltage (eg 300.0V = 3000 , 16bits long) + BYD_110.data.u8[2] = (datalayer.battery.settings.max_user_set_discharge_voltage_dV >> 8); + BYD_110.data.u8[3] = (datalayer.battery.settings.max_user_set_discharge_voltage_dV & 0x00FF); + } else { //Use the voltage based on battery reported design voltage +- offset to avoid triggering events + //Target charge voltage (eg 400.0V = 4000 , 16bits long) + BYD_110.data.u8[0] = ((datalayer.battery.info.max_design_voltage_dV - VOLTAGE_OFFSET_DV) >> 8); + BYD_110.data.u8[1] = ((datalayer.battery.info.max_design_voltage_dV - VOLTAGE_OFFSET_DV) & 0x00FF); + //Target discharge voltage (eg 300.0V = 3000 , 16bits long) + BYD_110.data.u8[2] = ((datalayer.battery.info.min_design_voltage_dV + VOLTAGE_OFFSET_DV) >> 8); + BYD_110.data.u8[3] = ((datalayer.battery.info.min_design_voltage_dV + VOLTAGE_OFFSET_DV) & 0x00FF); + } + //Maximum discharge power allowed (Unit: A+1) BYD_110.data.u8[4] = (datalayer.battery.status.max_discharge_current_dA >> 8); BYD_110.data.u8[5] = (datalayer.battery.status.max_discharge_current_dA & 0x00FF);