From 79e98c997d9227ee8073033a6c29f9f5d7faf5ea Mon Sep 17 00:00:00 2001 From: Daniel Date: Sun, 28 Jan 2024 22:54:01 +0200 Subject: [PATCH 1/7] Add ability to save parameters to flash --- Software/Software.ino | 46 +++++++++++++++++++ Software/USER_SETTINGS.cpp | 2 +- Software/USER_SETTINGS.h | 5 +- Software/src/devboard/webserver/webserver.cpp | 8 ++++ Software/src/devboard/webserver/webserver.h | 5 +- 5 files changed, 62 insertions(+), 4 deletions(-) diff --git a/Software/Software.ino b/Software/Software.ino index 56418515..3b0cb5db 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -2,6 +2,7 @@ /* Only change battery specific settings in "USER_SETTINGS.h" */ #include +#include #include "HardwareSerial.h" #include "USER_SETTINGS.h" #include "src/battery/BATTERIES.h" @@ -18,6 +19,8 @@ #include "src/devboard/webserver/webserver.h" #endif +Preferences preferences; // Parameter storage + // Interval settings int intervalUpdateValues = 4800; // Interval at which to update inverter values / Modbus registers const int interval10 = 10; // Interval for 10ms tasks @@ -102,6 +105,8 @@ bool inverterAllowsContactorClosing = true; void setup() { init_serial(); + init_storage(); + #ifdef WEBSERVER init_webserver(); #endif @@ -169,6 +174,37 @@ void init_serial() { Serial.println("__ OK __"); } +void init_storage() { + preferences.begin("batterySettings", false); + +#ifdef CLEAR_SAVED_SETTINGS + preferences.clear(); // If this clear function is executed, no parameters will be read from storage +#endif + + static uint16_t temp = 0; + temp = preferences.getUInt("BATTERY_WH_MAX", false); + if (temp != 0) { + BATTERY_WH_MAX = temp; + } + temp = preferences.getUInt("MAXPERCENTAGE", false); + if (temp != 0) { + MAXPERCENTAGE = temp; + } + temp = preferences.getUInt("MINPERCENTAGE", false); + if (temp != 0) { + MINPERCENTAGE = temp; + } + temp = preferences.getUInt("MAXCHARGEAMP", false); + if (temp != 0) { + MAXCHARGEAMP = temp; + } + temp = preferences.getUInt("MAXDISCHARGEAMP", false); + if (temp != 0) { + MAXDISCHARGEAMP = temp; + } + preferences.end(); +} + void init_CAN() { // CAN pins pinMode(CAN_SE_PIN, OUTPUT); @@ -669,3 +705,13 @@ void init_serialDataLink() { Serial2.begin(9600, SERIAL_8N1, RS485_RX_PIN, RS485_TX_PIN); #endif } + +void storeParameters() { + preferences.begin("batterySettings", false); + preferences.putUInt("BATTERY_WH_MAX", BATTERY_WH_MAX); + preferences.putUInt("MAXPERCENTAGE", MAXPERCENTAGE); + preferences.putUInt("MINPERCENTAGE", MINPERCENTAGE); + preferences.putUInt("MAXCHARGEAMP", MAXCHARGEAMP); + preferences.putUInt("MAXDISCHARGEAMP", MAXDISCHARGEAMP); + preferences.end(); +} diff --git a/Software/USER_SETTINGS.cpp b/Software/USER_SETTINGS.cpp index 77c5a7d9..e47eef34 100644 --- a/Software/USER_SETTINGS.cpp +++ b/Software/USER_SETTINGS.cpp @@ -22,5 +22,5 @@ const char* ssid = "REPLACE_WITH_YOUR_SSID"; // Maximum of 63 character const char* password = "REPLACE_WITH_YOUR_PASSWORD"; // Minimum of 8 characters; const char* ssidAP = "Battery Emulator"; // Maximum of 63 characters; const char* passwordAP = "123456789"; // Minimum of 8 characters; set to NULL if you want the access point to be open -const char* versionNumber = "4.4.0"; // The current software version, shown on webserver +const char* versionNumber = "4.5.0"; // The current software version, shown on webserver #endif diff --git a/Software/USER_SETTINGS.h b/Software/USER_SETTINGS.h index ee6f809b..28e32e86 100644 --- a/Software/USER_SETTINGS.h +++ b/Software/USER_SETTINGS.h @@ -17,7 +17,7 @@ //#define RENAULT_ZOE_BATTERY //#define SANTA_FE_PHEV_BATTERY //#define TESLA_MODEL_3_BATTERY -//#define TEST_FAKE_BATTERY +#define TEST_FAKE_BATTERY /* Select inverter communication protocol. See Wiki for which to use with your inverter: https://github.com/dalathegreat/BYD-Battery-Emulator-For-Gen24/wiki */ //#define BYD_CAN //Enable this line to emulate a "BYD Battery-Box Premium HVS" over CAN Bus @@ -36,7 +36,8 @@ //#define DUAL_CAN //Enable this line to activate an isolated secondary CAN Bus using add-on MCP2515 controller (Needed for FoxESS inverters) //#define SERIAL_LINK_RECEIVER //Enable this line to receive battery data over RS485 pins from another Lilygo (This LilyGo interfaces with inverter) //#define SERIAL_LINK_TRANSMITTER //Enable this line to send battery data over RS485 pins to another Lilygo (This LilyGo interfaces with battery) -//#define WEBSERVER //Enable this line to enable WiFi, and to run the webserver. See USER_SETTINGS.cpp for the Wifi settings. +#define WEBSERVER //Enable this line to enable WiFi, and to run the webserver. See USER_SETTINGS.cpp for the Wifi settings. +//#define CLEAR_SAVED_SETTINGS //Enable this line to clear all data that has been saved via the webserver page /* Battery limits: These are set in the USER_SETTINGS.cpp file, or later on via the Webserver */ extern volatile uint16_t BATTERY_WH_MAX; diff --git a/Software/src/devboard/webserver/webserver.cpp b/Software/src/devboard/webserver/webserver.cpp index 6a44cf2d..1ff67d6d 100644 --- a/Software/src/devboard/webserver/webserver.cpp +++ b/Software/src/devboard/webserver/webserver.cpp @@ -1,4 +1,7 @@ #include "webserver.h" +#include + +Preferences preferences3; // Create AsyncWebServer object on port 80 AsyncWebServer server(80); @@ -65,6 +68,7 @@ void init_webserver() { if (request->hasParam("value")) { String value = request->getParam("value")->value(); BATTERY_WH_MAX = value.toInt(); + storeParameters(); request->send(200, "text/plain", "Updated successfully"); } else { request->send(400, "text/plain", "Bad Request"); @@ -76,6 +80,7 @@ void init_webserver() { if (request->hasParam("value")) { String value = request->getParam("value")->value(); MAXPERCENTAGE = value.toInt() * 10; + storeParameters(); request->send(200, "text/plain", "Updated successfully"); } else { request->send(400, "text/plain", "Bad Request"); @@ -87,6 +92,7 @@ void init_webserver() { if (request->hasParam("value")) { String value = request->getParam("value")->value(); MINPERCENTAGE = value.toInt() * 10; + storeParameters(); request->send(200, "text/plain", "Updated successfully"); } else { request->send(400, "text/plain", "Bad Request"); @@ -98,6 +104,7 @@ void init_webserver() { if (request->hasParam("value")) { String value = request->getParam("value")->value(); MAXCHARGEAMP = value.toInt() * 10; + storeParameters(); request->send(200, "text/plain", "Updated successfully"); } else { request->send(400, "text/plain", "Bad Request"); @@ -109,6 +116,7 @@ void init_webserver() { if (request->hasParam("value")) { String value = request->getParam("value")->value(); MAXDISCHARGEAMP = value.toInt() * 10; + storeParameters(); request->send(200, "text/plain", "Updated successfully"); } else { request->send(400, "text/plain", "Bad Request"); diff --git a/Software/src/devboard/webserver/webserver.h b/Software/src/devboard/webserver/webserver.h index 41c5c309..30a14d2c 100644 --- a/Software/src/devboard/webserver/webserver.h +++ b/Software/src/devboard/webserver/webserver.h @@ -1,7 +1,7 @@ #ifndef WEBSERVER_H #define WEBSERVER_H -// Load Wi-Fi library +#include #include #include "../../../USER_SETTINGS.h" // Needed for WiFi ssid and password #include "../../lib/ayushsharma82-ElegantOTA/src/ElegantOTA.h" @@ -128,4 +128,7 @@ void onOTAEnd(bool success); template String formatPowerValue(String label, T value, String unit, int precision); +extern void storeParameters(); +extern void restoreParameters(); + #endif From 5a52b3757c3c7663610b19ec2fafb6ca99a08470 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sun, 28 Jan 2024 23:26:53 +0200 Subject: [PATCH 2/7] Tesla: Improve accuracy on temperature --- Software/src/battery/TESLA-MODEL-3-BATTERY.cpp | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/Software/src/battery/TESLA-MODEL-3-BATTERY.cpp b/Software/src/battery/TESLA-MODEL-3-BATTERY.cpp index a6d620d6..66c3c6de 100644 --- a/Software/src/battery/TESLA-MODEL-3-BATTERY.cpp +++ b/Software/src/battery/TESLA-MODEL-3-BATTERY.cpp @@ -32,8 +32,8 @@ static uint16_t volts = 0; // V static int16_t amps = 0; // A static int16_t power = 0; // W static uint16_t raw_amps = 0; // A -static int16_t max_temp = 6; // C* -static int16_t min_temp = 5; // C* +static int16_t max_temp = 60; // C* +static int16_t min_temp = 50; // C* static uint16_t energy_buffer = 0; static uint16_t energy_to_charge_complete = 0; static uint16_t expected_energy_remaining = 0; @@ -209,10 +209,8 @@ void update_values_tesla_model_3_battery() { //This function maps all the value power = ((volts / 10) * amps); stat_batt_power = convert2unsignedInt16(power); - min_temp = (min_temp * 10); temperature_min = convert2unsignedInt16(min_temp); - max_temp = (max_temp * 10); temperature_max = convert2unsignedInt16(max_temp); cell_max_voltage = cell_max_v; @@ -443,11 +441,8 @@ void receive_can_tesla_model_3_battery(CAN_frame_t rx_frame) { } if (mux == 0) //Temperature sensors { - temp = rx_frame.data.u8[2]; - max_temp = (temp * 0.5) - 40; //in celcius, Example 24 - - temp = rx_frame.data.u8[3]; - min_temp = (temp * 0.5) - 40; //in celcius , Example 24 + max_temp = (rx_frame.data.u8[2] * 5) - 400; //40.0*C offset, value for instance 85 = 8.5*C + min_temp = (rx_frame.data.u8[3] * 5) - 400; //40.0*C offset, value for instance 85 = 8.5*C } break; case 0x2d2: From 93b6a3941eb01b88149da9e26e594ee23970e926 Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 29 Jan 2024 20:03:36 +0200 Subject: [PATCH 3/7] Code review naming --- Software/Software.ino | 40 +++++++++---------- Software/USER_SETTINGS.h | 4 +- Software/src/devboard/webserver/webserver.cpp | 10 ++--- Software/src/devboard/webserver/webserver.h | 4 +- 4 files changed, 29 insertions(+), 29 deletions(-) diff --git a/Software/Software.ino b/Software/Software.ino index 3b0cb5db..664f9852 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -19,7 +19,7 @@ #include "src/devboard/webserver/webserver.h" #endif -Preferences preferences; // Parameter storage +Preferences settings; // Store user settings // Interval settings int intervalUpdateValues = 4800; // Interval at which to update inverter values / Modbus registers @@ -105,7 +105,7 @@ bool inverterAllowsContactorClosing = true; void setup() { init_serial(); - init_storage(); + init_stored_settings(); #ifdef WEBSERVER init_webserver(); @@ -174,35 +174,35 @@ void init_serial() { Serial.println("__ OK __"); } -void init_storage() { - preferences.begin("batterySettings", false); +void init_stored_settings() { + settings.begin("batterySettings", false); -#ifdef CLEAR_SAVED_SETTINGS - preferences.clear(); // If this clear function is executed, no parameters will be read from storage +#ifndef LOAD_SAVED_SETTINGS_ON_BOOT + settings.clear(); // If this clear function is executed, no settings will be read from storage #endif static uint16_t temp = 0; - temp = preferences.getUInt("BATTERY_WH_MAX", false); + temp = settings.getUInt("BATTERY_WH_MAX", false); if (temp != 0) { BATTERY_WH_MAX = temp; } - temp = preferences.getUInt("MAXPERCENTAGE", false); + temp = settings.getUInt("MAXPERCENTAGE", false); if (temp != 0) { MAXPERCENTAGE = temp; } - temp = preferences.getUInt("MINPERCENTAGE", false); + temp = settings.getUInt("MINPERCENTAGE", false); if (temp != 0) { MINPERCENTAGE = temp; } - temp = preferences.getUInt("MAXCHARGEAMP", false); + temp = settings.getUInt("MAXCHARGEAMP", false); if (temp != 0) { MAXCHARGEAMP = temp; } - temp = preferences.getUInt("MAXDISCHARGEAMP", false); + temp = settings.getUInt("MAXDISCHARGEAMP", false); if (temp != 0) { MAXDISCHARGEAMP = temp; } - preferences.end(); + settings.end(); } void init_CAN() { @@ -706,12 +706,12 @@ void init_serialDataLink() { #endif } -void storeParameters() { - preferences.begin("batterySettings", false); - preferences.putUInt("BATTERY_WH_MAX", BATTERY_WH_MAX); - preferences.putUInt("MAXPERCENTAGE", MAXPERCENTAGE); - preferences.putUInt("MINPERCENTAGE", MINPERCENTAGE); - preferences.putUInt("MAXCHARGEAMP", MAXCHARGEAMP); - preferences.putUInt("MAXDISCHARGEAMP", MAXDISCHARGEAMP); - preferences.end(); +void storeSettings() { + settings.begin("batterySettings", false); + settings.putUInt("BATTERY_WH_MAX", BATTERY_WH_MAX); + settings.putUInt("MAXPERCENTAGE", MAXPERCENTAGE); + settings.putUInt("MINPERCENTAGE", MINPERCENTAGE); + settings.putUInt("MAXCHARGEAMP", MAXCHARGEAMP); + settings.putUInt("MAXDISCHARGEAMP", MAXDISCHARGEAMP); + settings.end(); } diff --git a/Software/USER_SETTINGS.h b/Software/USER_SETTINGS.h index 28e32e86..009033c1 100644 --- a/Software/USER_SETTINGS.h +++ b/Software/USER_SETTINGS.h @@ -17,7 +17,7 @@ //#define RENAULT_ZOE_BATTERY //#define SANTA_FE_PHEV_BATTERY //#define TESLA_MODEL_3_BATTERY -#define TEST_FAKE_BATTERY +//#define TEST_FAKE_BATTERY /* Select inverter communication protocol. See Wiki for which to use with your inverter: https://github.com/dalathegreat/BYD-Battery-Emulator-For-Gen24/wiki */ //#define BYD_CAN //Enable this line to emulate a "BYD Battery-Box Premium HVS" over CAN Bus @@ -37,7 +37,7 @@ //#define SERIAL_LINK_RECEIVER //Enable this line to receive battery data over RS485 pins from another Lilygo (This LilyGo interfaces with inverter) //#define SERIAL_LINK_TRANSMITTER //Enable this line to send battery data over RS485 pins to another Lilygo (This LilyGo interfaces with battery) #define WEBSERVER //Enable this line to enable WiFi, and to run the webserver. See USER_SETTINGS.cpp for the Wifi settings. -//#define CLEAR_SAVED_SETTINGS //Enable this line to clear all data that has been saved via the webserver page +#define LOAD_SAVED_SETTINGS_ON_BOOT //Enable this line to read settings stored via the webserver on boot /* Battery limits: These are set in the USER_SETTINGS.cpp file, or later on via the Webserver */ extern volatile uint16_t BATTERY_WH_MAX; diff --git a/Software/src/devboard/webserver/webserver.cpp b/Software/src/devboard/webserver/webserver.cpp index 1ff67d6d..d19cea82 100644 --- a/Software/src/devboard/webserver/webserver.cpp +++ b/Software/src/devboard/webserver/webserver.cpp @@ -68,7 +68,7 @@ void init_webserver() { if (request->hasParam("value")) { String value = request->getParam("value")->value(); BATTERY_WH_MAX = value.toInt(); - storeParameters(); + storeSettings(); request->send(200, "text/plain", "Updated successfully"); } else { request->send(400, "text/plain", "Bad Request"); @@ -80,7 +80,7 @@ void init_webserver() { if (request->hasParam("value")) { String value = request->getParam("value")->value(); MAXPERCENTAGE = value.toInt() * 10; - storeParameters(); + storeSettings(); request->send(200, "text/plain", "Updated successfully"); } else { request->send(400, "text/plain", "Bad Request"); @@ -92,7 +92,7 @@ void init_webserver() { if (request->hasParam("value")) { String value = request->getParam("value")->value(); MINPERCENTAGE = value.toInt() * 10; - storeParameters(); + storeSettings(); request->send(200, "text/plain", "Updated successfully"); } else { request->send(400, "text/plain", "Bad Request"); @@ -104,7 +104,7 @@ void init_webserver() { if (request->hasParam("value")) { String value = request->getParam("value")->value(); MAXCHARGEAMP = value.toInt() * 10; - storeParameters(); + storeSettings(); request->send(200, "text/plain", "Updated successfully"); } else { request->send(400, "text/plain", "Bad Request"); @@ -116,7 +116,7 @@ void init_webserver() { if (request->hasParam("value")) { String value = request->getParam("value")->value(); MAXDISCHARGEAMP = value.toInt() * 10; - storeParameters(); + storeSettings(); request->send(200, "text/plain", "Updated successfully"); } else { request->send(400, "text/plain", "Bad Request"); diff --git a/Software/src/devboard/webserver/webserver.h b/Software/src/devboard/webserver/webserver.h index 30a14d2c..3d3a8ee2 100644 --- a/Software/src/devboard/webserver/webserver.h +++ b/Software/src/devboard/webserver/webserver.h @@ -128,7 +128,7 @@ void onOTAEnd(bool success); template String formatPowerValue(String label, T value, String unit, int precision); -extern void storeParameters(); -extern void restoreParameters(); +extern void storeSettings(); +extern void restoreSettings(); #endif From d56d61913a3667dd090ba6c6475207918e45b81b Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 29 Jan 2024 20:05:17 +0200 Subject: [PATCH 4/7] Pre-commit fix --- Software/USER_SETTINGS.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Software/USER_SETTINGS.h b/Software/USER_SETTINGS.h index 009033c1..89bce0d4 100644 --- a/Software/USER_SETTINGS.h +++ b/Software/USER_SETTINGS.h @@ -37,7 +37,7 @@ //#define SERIAL_LINK_RECEIVER //Enable this line to receive battery data over RS485 pins from another Lilygo (This LilyGo interfaces with inverter) //#define SERIAL_LINK_TRANSMITTER //Enable this line to send battery data over RS485 pins to another Lilygo (This LilyGo interfaces with battery) #define WEBSERVER //Enable this line to enable WiFi, and to run the webserver. See USER_SETTINGS.cpp for the Wifi settings. -#define LOAD_SAVED_SETTINGS_ON_BOOT //Enable this line to read settings stored via the webserver on boot +#define LOAD_SAVED_SETTINGS_ON_BOOT //Enable this line to read settings stored via the webserver on boot /* Battery limits: These are set in the USER_SETTINGS.cpp file, or later on via the Webserver */ extern volatile uint16_t BATTERY_WH_MAX; From 7f3c6adbcdd431d1277d7f58cd71b952c01149d7 Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 29 Jan 2024 20:23:24 +0200 Subject: [PATCH 5/7] Init to 0, update comment --- Software/src/battery/TESLA-MODEL-3-BATTERY.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Software/src/battery/TESLA-MODEL-3-BATTERY.cpp b/Software/src/battery/TESLA-MODEL-3-BATTERY.cpp index 66c3c6de..35d54535 100644 --- a/Software/src/battery/TESLA-MODEL-3-BATTERY.cpp +++ b/Software/src/battery/TESLA-MODEL-3-BATTERY.cpp @@ -32,8 +32,8 @@ static uint16_t volts = 0; // V static int16_t amps = 0; // A static int16_t power = 0; // W static uint16_t raw_amps = 0; // A -static int16_t max_temp = 60; // C* -static int16_t min_temp = 50; // C* +static int16_t max_temp = 0; // C* +static int16_t min_temp = 0; // C* static uint16_t energy_buffer = 0; static uint16_t energy_to_charge_complete = 0; static uint16_t expected_energy_remaining = 0; @@ -441,8 +441,8 @@ void receive_can_tesla_model_3_battery(CAN_frame_t rx_frame) { } if (mux == 0) //Temperature sensors { - max_temp = (rx_frame.data.u8[2] * 5) - 400; //40.0*C offset, value for instance 85 = 8.5*C - min_temp = (rx_frame.data.u8[3] * 5) - 400; //40.0*C offset, value for instance 85 = 8.5*C + max_temp = (rx_frame.data.u8[2] * 5) - 400; //Temperature values have 40.0*C offset, 0.5*C /bit + min_temp = (rx_frame.data.u8[3] * 5) - 400; //Multiply by 5 and remove offset to get C+1 (0x61*5=485-400=8.5*C) } break; case 0x2d2: From f06a9799eaa484216529a496e8aa57c7cdb4b170 Mon Sep 17 00:00:00 2001 From: Daniel Date: Tue, 30 Jan 2024 16:41:38 +0200 Subject: [PATCH 6/7] Remove leftover function call --- Software/src/devboard/webserver/webserver.h | 1 - 1 file changed, 1 deletion(-) diff --git a/Software/src/devboard/webserver/webserver.h b/Software/src/devboard/webserver/webserver.h index 3d3a8ee2..442da3a0 100644 --- a/Software/src/devboard/webserver/webserver.h +++ b/Software/src/devboard/webserver/webserver.h @@ -129,6 +129,5 @@ template String formatPowerValue(String label, T value, String unit, int precision); extern void storeSettings(); -extern void restoreSettings(); #endif From 5f2c11ba35879a13e3c3cb44140effe141eacc5f Mon Sep 17 00:00:00 2001 From: Steven Maresca Date: Fri, 26 Jan 2024 17:25:44 -0500 Subject: [PATCH 7/7] Basic support for the Chevy Volt Gen1 charger, including webserver integration. Intended to enable use of inverters lacking charging functions and standalone charging modes. --- Software/Software.ino | 22 ++ Software/USER_SETTINGS.cpp | 8 + Software/USER_SETTINGS.h | 17 ++ Software/src/charger/CHARGERS.h | 8 + Software/src/charger/chevyvolt.cpp | 198 ++++++++++++++++ Software/src/charger/chevyvolt.h | 21 ++ Software/src/devboard/webserver/webserver.cpp | 221 ++++++++++++++++++ Software/src/devboard/webserver/webserver.h | 8 + 8 files changed, 503 insertions(+) create mode 100644 Software/src/charger/CHARGERS.h create mode 100644 Software/src/charger/chevyvolt.cpp create mode 100644 Software/src/charger/chevyvolt.h diff --git a/Software/Software.ino b/Software/Software.ino index 56418515..f23676bb 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -5,6 +5,7 @@ #include "HardwareSerial.h" #include "USER_SETTINGS.h" #include "src/battery/BATTERIES.h" +#include "src/charger/CHARGERS.h" #include "src/devboard/config.h" #include "src/inverter/INVERTERS.h" #include "src/lib/adafruit-Adafruit_NeoPixel/Adafruit_NeoPixel.h" @@ -68,6 +69,21 @@ uint16_t cell_max_voltage = 3700; // Stores the highest cell voltage value uint16_t cell_min_voltage = 3700; // Stores the minimum cell voltage value in the system bool LFP_Chemistry = false; +// Common charger parameters +volatile float charger_setpoint_HV_VDC = 0.0f; +volatile float charger_setpoint_HV_IDC = 0.0f; +volatile float charger_setpoint_HV_IDC_END = 0.0f; +bool charger_HV_enabled = false; +bool charger_aux12V_enabled = false; + +// Common charger statistics, instantaneous values +float charger_stat_HVcur = 0; +float charger_stat_HVvol = 0; +float charger_stat_ACcur = 0; +float charger_stat_ACvol = 0; +float charger_stat_LVcur = 0; +float charger_stat_LVvol = 0; + // LED parameters Adafruit_NeoPixel pixels(1, WS2812_PIN, NEO_GRB + NEO_KHZ800); static uint8_t brightness = 0; @@ -360,6 +376,9 @@ void receive_can() { // This section checks if we have a complete CAN message i #endif #ifdef SMA_CAN receive_can_sma(rx_frame); +#endif +#ifdef CHEVYVOLT_CHARGER + receive_can_chevyvolt_charger(rx_frame); #endif } else { //printf("New extended frame"); @@ -419,6 +438,9 @@ void send_can() { #ifdef TEST_FAKE_BATTERY send_can_test_battery(); #endif +#ifdef CHEVYVOLT_CHARGER + send_can_chevyvolt_charger(); +#endif } #ifdef DUAL_CAN diff --git a/Software/USER_SETTINGS.cpp b/Software/USER_SETTINGS.cpp index 77c5a7d9..78bcc435 100644 --- a/Software/USER_SETTINGS.cpp +++ b/Software/USER_SETTINGS.cpp @@ -15,6 +15,14 @@ volatile uint16_t MAXCHARGEAMP = volatile uint16_t MAXDISCHARGEAMP = 300; //30.0A , BYD CAN specific setting, Max discharge speed in Amp (Some inverters needs to be artificially limited) +/* Charger settings */ +volatile float CHARGER_SET_HV = 384; // Reasonably appropriate 4.0v per cell charging of a 96s pack +volatile float CHARGER_MAX_HV = 420; // Max permissible output (VDC) of charger +volatile float CHARGER_MIN_HV = 200; // Min permissible output (VDC) of charger +volatile float CHARGER_MAX_POWER = 3300; // Max power capable of charger, as a ceiling for validating config +volatile float CHARGER_MAX_A = 11.5; // Max current output (amps) of charger +volatile float CHARGER_END_A = 1.0; // Current at which charging is considered complete + #ifdef WEBSERVER volatile uint8_t AccessPointEnabled = true; //Set to either true or false incase you want the board to enable a direct wifi access point diff --git a/Software/USER_SETTINGS.h b/Software/USER_SETTINGS.h index ee6f809b..9ed9e043 100644 --- a/Software/USER_SETTINGS.h +++ b/Software/USER_SETTINGS.h @@ -38,6 +38,9 @@ //#define SERIAL_LINK_TRANSMITTER //Enable this line to send battery data over RS485 pins to another Lilygo (This LilyGo interfaces with battery) //#define WEBSERVER //Enable this line to enable WiFi, and to run the webserver. See USER_SETTINGS.cpp for the Wifi settings. +/* Select charger used (Optional) */ +//#define CHEVYVOLT_CHARGER //Enable this line to control a Chevrolet Volt charger connected to battery - for example, when generator charging or using an inverter without a charging function. + /* Battery limits: These are set in the USER_SETTINGS.cpp file, or later on via the Webserver */ extern volatile uint16_t BATTERY_WH_MAX; extern volatile uint16_t MAXPERCENTAGE; @@ -45,4 +48,18 @@ extern volatile uint16_t MINPERCENTAGE; extern volatile uint16_t MAXCHARGEAMP; extern volatile uint16_t MAXDISCHARGEAMP; extern volatile uint8_t AccessPointEnabled; + +/* Charger limits: Set in the USER_SETTINGS.cpp or later in the webserver */ +extern volatile float charger_setpoint_HV_VDC; +extern volatile float charger_setpoint_HV_IDC; +extern volatile float charger_setpoint_HV_IDC_END; +extern volatile float CHARGER_SET_HV; +extern volatile float CHARGER_MAX_HV; +extern volatile float CHARGER_MIN_HV; +extern volatile float CHARGER_MAX_POWER; +extern volatile float CHARGER_MAX_A; +extern volatile float CHARGER_END_A; +extern bool charger_HV_enabled; +extern bool charger_aux12V_enabled; + #endif diff --git a/Software/src/charger/CHARGERS.h b/Software/src/charger/CHARGERS.h new file mode 100644 index 00000000..73bae3a1 --- /dev/null +++ b/Software/src/charger/CHARGERS.h @@ -0,0 +1,8 @@ +#ifndef CHARGERS_H +#define CHARGERS_H + +#ifdef CHEVYVOLT_CHARGER +#include "chevyvolt.h" +#endif + +#endif diff --git a/Software/src/charger/chevyvolt.cpp b/Software/src/charger/chevyvolt.cpp new file mode 100644 index 00000000..a44e59b2 --- /dev/null +++ b/Software/src/charger/chevyvolt.cpp @@ -0,0 +1,198 @@ +#include "chevyvolt.h" +#include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h" +#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h" + +/* This implements Chevy Volt / Ampera charger support (2011-2015 model years). + * + * This code is intended to facilitate battery charging while repurposing inverters + * that lack embedded charging features, to facilitate standalone charging, etc. + * + * Key influence and inspiration informed by prior work by those such as: + * Damien Maguire (evmbw.org, github.com/damienmaguire/AmperaCharger) + * Tom deBree, Arber Kramer, Colin Kidder, EVTV, etc + * Various implementation details aided by openinverter forum discussion/CAN logs + * + * It is very likely that Lear charger support could be added to this with minimal effort + * (similar hardware, different firmware and CAN messages). + * + * 2024 smaresca + */ + +/* CAN cycles and timers */ +static const int interval30ms = 30; // 30ms cycle for keepalive frames +static const int interval200ms = 200; // 200ms cycle for commanding I/V targets +static const int interval5000ms = 5000; // 5s status printout to serial +static unsigned long previousMillis30ms = 0; +static unsigned long previousMillis200ms = 0; +static unsigned long previousMillis5000ms = 0; + +/* voltage and current settings. Validation performed to set ceiling of 3300w vol*cur */ +extern volatile float charger_setpoint_HV_VDC; +extern volatile float charger_setpoint_HV_IDC; +extern volatile float charger_setpoint_HV_IDC_END; +extern bool charger_HV_enabled; +extern bool charger_aux12V_enabled; + +extern float charger_stat_HVcur; +extern float charger_stat_HVvol; +extern float charger_stat_ACcur; +extern float charger_stat_ACvol; +extern float charger_stat_LVcur; +extern float charger_stat_LVvol; + +enum CHARGER_MODES : uint8_t { MODE_DISABLED = 0, MODE_LV, MODE_HV, MODE_HVLV }; + +//Actual content messages +static CAN_frame_t charger_keepalive_frame = {.FIR = {.B = + { + //one byte only, indicating enabled or disabled + .DLC = 1, + .FF = CAN_frame_std, + }}, + .MsgID = 0x30E, + .data = {MODE_DISABLED}}; + +static CAN_frame_t charger_set_targets = {.FIR = {.B = + { + .DLC = 4, + .FF = CAN_frame_std, + }}, + .MsgID = 0x304, + + // data[0] is a static value, meaning unknown + .data = {0x40, 0x00, 0x00, 0x00}}; + +/* We are mostly sending out not receiving */ +void receive_can_chevyvolt_charger(CAN_frame_t rx_frame) { + uint16_t charger_stat_HVcur_temp = 0; + uint16_t charger_stat_HVvol_temp = 0; + uint16_t charger_stat_LVcur_temp = 0; + uint16_t charger_stat_LVvol_temp = 0; + uint16_t charger_stat_ACcur_temp = 0; + uint16_t charger_stat_ACvol_temp = 0; + + switch (rx_frame.MsgID) { + //ID 0x212 conveys instantaneous DC charger stats + case 0x212: + charger_stat_HVcur_temp = (uint16_t)(rx_frame.data.u8[0] << 8 | rx_frame.data.u8[1]); + charger_stat_HVcur = (float)(charger_stat_HVcur_temp >> 3) * 0.05; + + charger_stat_HVvol_temp = (uint16_t)((((rx_frame.data.u8[1] << 8 | rx_frame.data.u8[2])) >> 1) & 0x3ff); + charger_stat_HVvol = (float)(charger_stat_HVvol_temp) * .5; + + charger_stat_LVcur_temp = (uint16_t)(((rx_frame.data.u8[2] << 8 | rx_frame.data.u8[3]) >> 1) & 0x00ff); + charger_stat_LVcur = (float)(charger_stat_LVcur_temp) * .2; + + charger_stat_LVvol_temp = (uint16_t)(((rx_frame.data.u8[3] << 8 | rx_frame.data.u8[4]) >> 1) & 0x00ff); + charger_stat_LVvol = (float)(charger_stat_LVvol_temp) * .1; + + break; + + //ID 0x30A conveys instantaneous AC charger stats + case 0x30A: + charger_stat_ACcur_temp = (uint16_t)((rx_frame.data.u8[0] << 8 | rx_frame.data.u8[1]) >> 4); + charger_stat_ACcur = (float)(charger_stat_ACcur_temp) * 0.2; + + charger_stat_ACvol_temp = (uint16_t)(((rx_frame.data.u8[1] << 8 | rx_frame.data.u8[2]) >> 4) & 0x00ff); + charger_stat_ACvol = (float)(charger_stat_ACvol_temp) * 2; + + break; + + //ID 0x266, 0x268, and 0x308 are regularly emitted by the charger but content is unknown + // 0x266 and 0x308 are len 5 + // 0x268 may be temperature data (len 8). Could resemble the Lear charger equivalent TODO + case 0x266: + break; + case 0x268: + break; + case 0x308: + break; + default: +#ifdef DEBUG_VIA_USB + Serial.printf("CAN Rcv unknown frame MsgID=%x\n", rx_frame.MsgID); +#endif + break; + } +} + +void send_can_chevyvolt_charger() { + unsigned long currentMillis = millis(); + uint16_t Vol_temp = 0; + + uint16_t setpoint_HV_VDC = floor(charger_setpoint_HV_VDC); + uint16_t setpoint_HV_IDC = floor(charger_setpoint_HV_IDC); + uint16_t setpoint_HV_IDC_END = floor(charger_setpoint_HV_IDC_END); + uint8_t charger_mode = MODE_DISABLED; + + /* Send keepalive with mode every 30ms */ + if (currentMillis - previousMillis30ms >= interval30ms) { + previousMillis30ms = currentMillis; + + if (charger_HV_enabled) { + charger_mode += MODE_HV; + + /* disable HV if end amperage reached + * TODO - integration opportunity with battery/inverter code + if (setpoint_HV_IDC_END > 0 && charger_stat_HVcur > setpoint_HV_IDC_END) { + charger_mode -= MODE_HV; + } + */ + } + + if (charger_aux12V_enabled) + charger_mode += MODE_LV; + + charger_keepalive_frame.data.u8[0] = charger_mode; + + ESP32Can.CANWriteFrame(&charger_keepalive_frame); + } + + /* Send current targets every 200ms */ + if (currentMillis - previousMillis200ms >= interval200ms) { + previousMillis200ms = currentMillis; + + /* These values should be and are validated elsewhere, but adjust if needed + * to stay within limits of hardware and user-supplied settings + */ + if (setpoint_HV_VDC > CHARGER_MAX_HV) { + setpoint_HV_VDC = CHEVYVOLT_MAX_HVDC; + } + + if (setpoint_HV_VDC < CHARGER_MIN_HV && setpoint_HV_VDC > 0) { + setpoint_HV_VDC = CHEVYVOLT_MIN_HVDC; + } + + if (setpoint_HV_IDC > CHARGER_MAX_A) { + setpoint_HV_VDC = CHEVYVOLT_MAX_AMP; + } + + /* if power overcommitted, back down to just below while maintaining voltage target */ + if (setpoint_HV_IDC * setpoint_HV_VDC > CHARGER_MAX_POWER) { + setpoint_HV_IDC = floor(CHARGER_MAX_POWER / setpoint_HV_VDC); + } + + /* current setting */ + charger_set_targets.data.u8[1] = setpoint_HV_IDC * 20; + Vol_temp = setpoint_HV_VDC * 2; + + /* first 2 bits are MSB of the voltage command */ + charger_set_targets.data.u8[2] = highByte(Vol_temp); + + /* LSB of the voltage command. Then MSB LSB is divided by 2 */ + charger_set_targets.data.u8[3] = lowByte(Vol_temp); + + ESP32Can.CANWriteFrame(&charger_set_targets); + } + +#ifdef DEBUG_VIA_USB + /* Serial echo every 5s of charger stats */ + if (currentMillis - previousMillis5000ms >= interval5000ms) { + previousMillis5000ms = currentMillis; + Serial.printf("Charger AC in IAC=%fA VAC=%fV\n", charger_stat_ACcur, charger_stat_ACvol); + Serial.printf("Charger HV out IDC=%fA VDC=%fV\n", charger_stat_HVcur, charger_stat_HVvol); + Serial.printf("Charger LV out IDC=%fA VDC=%fV\n", charger_stat_LVcur, charger_stat_LVvol); + Serial.printf("Charger mode=%s\n", (charger_mode > MODE_DISABLED) ? "Enabled" : "Disabled"); + Serial.printf("Charger HVset=%uV,%uA finishCurrent=%uA\n", setpoint_HV_VDC, setpoint_HV_IDC, setpoint_HV_IDC_END); + } +#endif +} diff --git a/Software/src/charger/chevyvolt.h b/Software/src/charger/chevyvolt.h new file mode 100644 index 00000000..522397e6 --- /dev/null +++ b/Software/src/charger/chevyvolt.h @@ -0,0 +1,21 @@ +#ifndef CHEVYVOLT_H +#define CHEVYVOLT_H +#include +#include "../../USER_SETTINGS.h" +#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h" + +/* Charger hardware limits + * + * Relative to runtime settings, expectations are: + * hw minimum <= setting minimum <= setting maximum <= hw max + */ +#define CHEVYVOLT_MAX_HVDC 420.0 +#define CHEVYVOLT_MIN_HVDC 200.0 +#define CHEVYVOLT_MAX_AMP 11.5 +#define CHEVYVOLT_MAX_POWER 3300 + +void update_values_can_chevyvolt_charger(); +void send_can_chevyvolt_charger(); +void receive_can_chevyvolt_charger(CAN_frame_t rx_frame); + +#endif diff --git a/Software/src/devboard/webserver/webserver.cpp b/Software/src/devboard/webserver/webserver.cpp index 6a44cf2d..e739c22a 100644 --- a/Software/src/devboard/webserver/webserver.cpp +++ b/Software/src/devboard/webserver/webserver.cpp @@ -115,6 +115,85 @@ void init_webserver() { } }); +#ifdef CHEVYVOLT_CHARGER + // Route for editing ChargerTargetV + server.on("/updateChargeSetpointV", HTTP_GET, [](AsyncWebServerRequest* request) { + if (!request->hasParam("value")) { + request->send(400, "text/plain", "Bad Request"); + } + + String value = request->getParam("value")->value(); + float val = value.toFloat(); + + if (!(val <= CHARGER_MAX_HV && val >= CHARGER_MIN_HV)) { + request->send(400, "text/plain", "Bad Request"); + } + + if (!(val * charger_setpoint_HV_IDC <= CHARGER_MAX_POWER)) { + request->send(400, "text/plain", "Bad Request"); + } + + charger_setpoint_HV_VDC = val; + + request->send(200, "text/plain", "Updated successfully"); + }); + + // Route for editing ChargerTargetA + server.on("/updateChargeSetpointA", HTTP_GET, [](AsyncWebServerRequest* request) { + if (!request->hasParam("value")) { + request->send(400, "text/plain", "Bad Request"); + } + + String value = request->getParam("value")->value(); + float val = value.toFloat(); + + if (!(val <= MAXCHARGEAMP && val <= CHARGER_MAX_A)) { + request->send(400, "text/plain", "Bad Request"); + } + + if (!(val * charger_setpoint_HV_VDC <= CHARGER_MAX_POWER)) { + request->send(400, "text/plain", "Bad Request"); + } + + charger_setpoint_HV_IDC = value.toFloat(); + + request->send(200, "text/plain", "Updated successfully"); + }); + + // Route for editing ChargerEndA + server.on("/updateChargeEndA", HTTP_GET, [](AsyncWebServerRequest* request) { + if (request->hasParam("value")) { + String value = request->getParam("value")->value(); + charger_setpoint_HV_IDC_END = value.toFloat(); + request->send(200, "text/plain", "Updated successfully"); + } else { + request->send(400, "text/plain", "Bad Request"); + } + }); + + // Route for enabling/disabling HV charger + server.on("/updateChargerHvEnabled", HTTP_GET, [](AsyncWebServerRequest* request) { + if (request->hasParam("value")) { + String value = request->getParam("value")->value(); + charger_HV_enabled = (bool)value.toInt(); + request->send(200, "text/plain", "Updated successfully"); + } else { + request->send(400, "text/plain", "Bad Request"); + } + }); + + // Route for enabling/disabling aux12v charger + server.on("/updateChargerAux12vEnabled", HTTP_GET, [](AsyncWebServerRequest* request) { + if (request->hasParam("value")) { + String value = request->getParam("value")->value(); + charger_aux12V_enabled = (bool)value.toInt(); + request->send(200, "text/plain", "Updated successfully"); + } else { + request->send(400, "text/plain", "Bad Request"); + } + }); +#endif + // Send a GET request to /update server.on("/debug", HTTP_GET, [](AsyncWebServerRequest* request) { request->send(200, "text/plain", "Debug: all OK."); }); @@ -298,6 +377,15 @@ String processor(const String& var) { content += "Fake battery for testing purposes"; #endif content += ""; + +#ifdef CHEVYVOLT_CHARGER + content += "

Charger protocol: "; +#ifdef CHEVYVOLT_CHARGER + content += "Chevy Volt Gen1 Charger"; +#endif + content += "

"; +#endif + // Close the block content += ""; @@ -391,6 +479,42 @@ String processor(const String& var) { content += ""; } +#ifdef CHEVYVOLT_CHARGER + content += "

Charger HV Enabled: "; + if (charger_HV_enabled) { + content += ""; + } else { + content += ""; + } + content += "

"; + + content += "

Charger Aux12v Enabled: "; + if (charger_aux12V_enabled) { + content += ""; + } else { + content += ""; + } + content += "

"; + float chgPwrDC = static_cast(charger_stat_HVcur * charger_stat_HVvol); + float chgPwrAC = static_cast(charger_stat_ACcur * charger_stat_ACvol); + float chgEff = chgPwrDC / chgPwrAC * 100; + float ACcur = charger_stat_ACcur; + float ACvol = charger_stat_ACvol; + float HVvol = charger_stat_HVvol; + float HVcur = charger_stat_HVcur; + float LVvol = charger_stat_LVvol; + float LVcur = charger_stat_LVcur; + + content += formatPowerValue("Charger Output Power", chgPwrDC, "", 1); + content += "

Charger Efficiency: " + String(chgEff) + "%

"; + content += "

Charger HVDC Output V: " + String(HVvol, 2) + "

"; + content += "

Charger HVDC Output I: " + String(HVcur, 2) + "

"; + content += "

Charger LVDC Output I: " + String(LVcur, 2) + "

"; + content += "

Charger LVDC Output V: " + String(LVvol, 2) + "

"; + content += "

Charger AC Input V: " + String(ACvol, 2) + "VAC

"; + content += "

Charger AC Input I: " + String(ACvol, 2) + "VAC

"; +#endif + // Close the block content += ""; @@ -445,6 +569,28 @@ String settings_processor(const String& var) { " A "; content += "

Max discharge speed: " + String(MAXDISCHARGEAMP / 10.0, 1) + " A

"; +#ifdef CHEVYVOLT_CHARGER + content += "

Charger HVDC Enabled: "; + if (charger_HV_enabled) { + content += ""; + } else { + content += ""; + } + content += "

"; + + content += "

Charger Aux12VDC Enabled: "; + if (charger_aux12V_enabled) { + content += ""; + } else { + content += ""; + } + content += "

"; + + content += "

Charger Voltage Setpoint: " + String(charger_setpoint_HV_VDC, 1) + + " V

"; + content += "

Charger Current Setpoint: " + String(charger_setpoint_HV_IDC, 1) + + " A

"; +#endif content += ""; // Close the block diff --git a/Software/src/devboard/webserver/webserver.h b/Software/src/devboard/webserver/webserver.h index 41c5c309..e296c086 100644 --- a/Software/src/devboard/webserver/webserver.h +++ b/Software/src/devboard/webserver/webserver.h @@ -35,6 +35,14 @@ extern const char* ssidAP; extern const char* passwordAP; extern const char* versionNumber; +// Common charger parameters +extern float charger_stat_HVcur; +extern float charger_stat_HVvol; +extern float charger_stat_ACcur; +extern float charger_stat_ACvol; +extern float charger_stat_LVcur; +extern float charger_stat_LVvol; + /** * @brief Initialization function for the webserver. *