mirror of
https://github.com/dalathegreat/Battery-Emulator.git
synced 2025-10-04 18:29:48 +02:00
Merge branch 'main' into feature/double-battery
This commit is contained in:
commit
1f295871b9
111 changed files with 6451 additions and 1698 deletions
16
.github/workflows/compile-all-batteries.yml
vendored
16
.github/workflows/compile-all-batteries.yml
vendored
|
@ -34,19 +34,25 @@ jobs:
|
|||
# These are the batteries for which the code will be compiled.
|
||||
battery:
|
||||
- BMW_I3_BATTERY
|
||||
- BYD_ATTO_3_BATTERY
|
||||
- CHADEMO_BATTERY
|
||||
- IMIEV_CZERO_ION_BATTERY
|
||||
- JAGUAR_IPACE_BATTERY
|
||||
- KIA_HYUNDAI_64_BATTERY
|
||||
- KIA_HYUNDAI_HYBRID_BATTERY
|
||||
- NISSAN_LEAF_BATTERY
|
||||
- PYLON_BATTERY
|
||||
- RENAULT_KANGOO_BATTERY
|
||||
- RENAULT_ZOE_BATTERY
|
||||
- RENAULT_ZOE_GEN1_BATTERY
|
||||
- RENAULT_ZOE_GEN2_BATTERY
|
||||
- TESLA_MODEL_3_BATTERY
|
||||
- VOLVO_SPA_BATTERY
|
||||
- TEST_FAKE_BATTERY
|
||||
- SERIAL_LINK_RECEIVER
|
||||
# These are the emulated inverter communication protocols for which the code will be compiled.
|
||||
inverter:
|
||||
# - BYD_CAN
|
||||
- BYD_MODBUS
|
||||
- BYD_CAN
|
||||
# - BYD_MODBUS
|
||||
# - LUNA2000_MODBUS
|
||||
# - PYLON_CAN
|
||||
# - SMA_CAN
|
||||
|
@ -66,7 +72,7 @@ jobs:
|
|||
# We use the `arduino/setup-arduino-cli` action to install and
|
||||
# configure the Arduino CLI on the system.
|
||||
- name: Setup Arduino CLI
|
||||
uses: arduino/setup-arduino-cli@v1
|
||||
uses: arduino/setup-arduino-cli@v2
|
||||
|
||||
# We then install the platform.
|
||||
- name: Install platform
|
||||
|
@ -78,4 +84,4 @@ jobs:
|
|||
# in the build matrix, and using build flags to define the
|
||||
# battery and inverter set in the build matrix.
|
||||
- name: Compile Sketch
|
||||
run: arduino-cli compile --fqbn ${{ matrix.fqbn }} --build-property "build.extra_flags=-DESP32 -D${{ matrix.battery}} -D${{ matrix.inverter}}" ./Software
|
||||
run: arduino-cli compile --fqbn ${{ matrix.fqbn }} --build-property "build.extra_flags=-Wall -DESP32 -D${{ matrix.battery}} -D${{ matrix.inverter}}" ./Software
|
||||
|
|
10
.github/workflows/compile-all-combinations.yml
vendored
10
.github/workflows/compile-all-combinations.yml
vendored
|
@ -37,12 +37,16 @@ jobs:
|
|||
# These are the batteries for which the code will be compiled.
|
||||
battery:
|
||||
- BMW_I3_BATTERY
|
||||
- BYD_ATTO_3_BATTERY
|
||||
- CHADEMO_BATTERY
|
||||
- IMIEV_CZERO_ION_BATTERY
|
||||
- KIA_HYUNDAI_64_BATTERY
|
||||
- KIA_HYUNDAI_HYBRID_BATTERY
|
||||
- NISSAN_LEAF_BATTERY
|
||||
- PYLON_BATTERY
|
||||
- RENAULT_KANGOO_BATTERY
|
||||
- RENAULT_ZOE_BATTERY
|
||||
- RENAULT_ZOE_GEN1_BATTERY
|
||||
- RENAULT_ZOE_GEN2_BATTERY
|
||||
- TESLA_MODEL_3_BATTERY
|
||||
- VOLVO_SPA_BATTERY
|
||||
- TEST_FAKE_BATTERY
|
||||
|
@ -70,7 +74,7 @@ jobs:
|
|||
# We use the `arduino/setup-arduino-cli` action to install and
|
||||
# configure the Arduino CLI on the system.
|
||||
- name: Setup Arduino CLI
|
||||
uses: arduino/setup-arduino-cli@v1
|
||||
uses: arduino/setup-arduino-cli@v2
|
||||
|
||||
# We then install the platform.
|
||||
- name: Install platform
|
||||
|
@ -82,4 +86,4 @@ jobs:
|
|||
# in the build matrix, and using build flags to define the
|
||||
# battery and inverter set in the build matrix.
|
||||
- name: Compile Sketch
|
||||
run: arduino-cli compile --fqbn ${{ matrix.fqbn }} --build-property "build.extra_flags=-DESP32 -D${{ matrix.battery}} -D${{ matrix.inverter}}" ./Software
|
||||
run: arduino-cli compile --fqbn ${{ matrix.fqbn }} --build-property "build.extra_flags=-Wall -DESP32 -D${{ matrix.battery}} -D${{ matrix.inverter}}" ./Software
|
||||
|
|
6
.github/workflows/compile-all-inverters.yml
vendored
6
.github/workflows/compile-all-inverters.yml
vendored
|
@ -51,7 +51,7 @@ jobs:
|
|||
- SOFAR_CAN
|
||||
- SOLAX_CAN
|
||||
- SERIAL_LINK_TRANSMITTER
|
||||
|
||||
- NISSANLEAF_CHARGER # Last element is not an inverter, but good to also test if charger compiles
|
||||
# This is the platform GitHub will use to run our workflow.
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
|
@ -64,7 +64,7 @@ jobs:
|
|||
# We use the `arduino/setup-arduino-cli` action to install and
|
||||
# configure the Arduino CLI on the system.
|
||||
- name: Setup Arduino CLI
|
||||
uses: arduino/setup-arduino-cli@v1
|
||||
uses: arduino/setup-arduino-cli@v2
|
||||
|
||||
# We then install the platform.
|
||||
- name: Install platform
|
||||
|
@ -76,4 +76,4 @@ jobs:
|
|||
# in the build matrix, and using build flags to define the
|
||||
# battery and inverter set in the build matrix.
|
||||
- name: Compile Sketch
|
||||
run: arduino-cli compile --fqbn ${{ matrix.fqbn }} --build-property "build.extra_flags=-DESP32 -D${{ matrix.battery}} -D${{ matrix.inverter}}" ./Software
|
||||
run: arduino-cli compile --fqbn ${{ matrix.fqbn }} --build-property "build.extra_flags=-Wall -DESP32 -D${{ matrix.battery}} -D${{ matrix.inverter}}" ./Software
|
||||
|
|
25
CONTRIBUTING.md
Normal file
25
CONTRIBUTING.md
Normal file
|
@ -0,0 +1,25 @@
|
|||
### Contributing to the Battery-Emulator project
|
||||
|
||||
## Notes on embedded system
|
||||
The Battery-Emulator is a real-time control system, which performs lots of time critical operations. Some operations, like contactor control, need to complete within 10 milliseconds periodically. The resources of the ESP32 microcontroller is limited, so keeping track of CPU and memory usage is essential. Keep this in mind when coding for the system! Performance profiling the system can be done by enabling the FUNCTION_TIME_MEASUREMENT option in the USER_SETTINGS.h file
|
||||
|
||||
## Code formatting
|
||||
The project enforces a specific code formatting in the workflows. To get your code formatted properly, it is easiest to use a pre-commit hook before pushing the code to a pull request.
|
||||
|
||||
Before you begin, make sure you have installed Python on the system!
|
||||
To install the pre-commit, open the repository via Git Bash/CMD, and run
|
||||
```
|
||||
pip install pre-commit
|
||||
```
|
||||
And then run
|
||||
```
|
||||
pre-commit install
|
||||
```
|
||||
Then you can run the autoformat any time with the command
|
||||
```
|
||||
pre-commit
|
||||
```
|
||||
Or force it to check all files with
|
||||
```
|
||||
pre-commit run --all-files
|
||||
```
|
26
README.md
26
README.md
|
@ -39,7 +39,13 @@ For more examples showing wiring, see each battery types own Wiki page. For inst
|
|||
1. Download the Arduino IDE: https://www.arduino.cc/en/software
|
||||
2. When the Arduino IDE has been started;
|
||||
Click "File" in the upper left corner -> Preferences -> Additional Development >Board Manager URL -> Enter the URL in the input box https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
|
||||
3. Go to "Boards Manager", and install the ESP32 package by Espressif Systems
|
||||
3. Go to "Boards Manager", and install the ESP32 package by Espressif Systems. **NOTE: The version depends on which release of Battery-Emulator you are running!**
|
||||
|
||||
- ⚠️ Make sure to use a 2.x.x version if you are on a release **older** than 6.0.0 (For instance ESP32 v2.0.11 when using Battery-Emulator v5.4.0)
|
||||
- ⚠️ Make sure to use a 3.x.x version if you are on a release **newer** than 6.0.0 (For instance ESP32 v3.0.0 when using Battery-Emulator v6.0.0)
|
||||
|
||||

|
||||
|
||||
4. The arduino settings should be set to "ESP32 Dev Module" with the following settings;
|
||||

|
||||
5. Select which battery type you will use, along with other optional settings. This is done in the USER_SETTINGS.h file.
|
||||
|
@ -52,10 +58,22 @@ This video explains all the above mentioned steps:
|
|||
https://youtu.be/_mH2AjnAjDk
|
||||
|
||||
## Dependencies 📖
|
||||
This code uses two libraries, ESP32-Arduino-CAN (https://github.com/miwagner/ESP32-Arduino-CAN/) slightly modified for this usecase, and the eModbus library (https://github.com/eModbus/eModbus). Both these are already located in the Software folder for an easy start.
|
||||
This code uses the following excellent libraries:
|
||||
- [adafruit/Adafruit_NeoPixel](https://github.com/adafruit/Adafruit_NeoPixel) LGPL-3.0 license
|
||||
- [ayushsharma82/ElegantOTA](https://github.com/ayushsharma82/ElegantOTA) AGPL-3.0 license
|
||||
- [bblanchon/ArduinoJson](https://github.com/bblanchon/ArduinoJson) MIT-License
|
||||
- [eModbus/eModbus](https://github.com/eModbus/eModbus) MIT-License
|
||||
- [knolleary/pubsubclient](https://github.com/knolleary/pubsubclient) MIT-License
|
||||
- [mackelec/SerialDataLink](https://github.com/mackelec/SerialDataLink)
|
||||
- [me-no-dev/AsyncTCP](https://github.com/me-no-dev/AsyncTCP) LGPL-3.0 license
|
||||
- [me-no-dev/ESPAsyncWebServer](https://github.com/me-no-dev/ESPAsyncWebServer)
|
||||
- [miwagner/ESP32-Arduino-CAN](https://github.com/miwagner/ESP32-Arduino-CAN/) MIT-License
|
||||
- [pierremolinaro/acan2515](https://github.com/pierremolinaro/acan2515) MIT-License
|
||||
- [pierremolinaro/acan2517FD](https://github.com/pierremolinaro/acan2517FD) MIT-License
|
||||
- [YiannisBourkelis/Uptime-Library](https://github.com/YiannisBourkelis/Uptime-Library) GPL-3.0 license
|
||||
|
||||
It is also based on the info found in the following excellent repositories/websites:
|
||||
- https://gitlab.com/pelle8/gen24
|
||||
It is also based on the information found in the following excellent repositories/websites:
|
||||
- https://gitlab.com/pelle8/inverter_resources //new url
|
||||
- https://github.com/burra/byd_battery
|
||||
- https://github.com/flodorn/TeslaBMSV2
|
||||
- https://github.com/SunshadeCorp/can-service
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
#include "src/devboard/utils/events.h"
|
||||
#include "src/devboard/utils/led_handler.h"
|
||||
#include "src/devboard/utils/value_mapping.h"
|
||||
#include "src/lib/YiannisBourkelis-Uptime-Library/src/uptime.h"
|
||||
#include "src/lib/YiannisBourkelis-Uptime-Library/src/uptime_formatter.h"
|
||||
#include "src/lib/bblanchon-ArduinoJson/ArduinoJson.h"
|
||||
#include "src/lib/eModbus-eModbus/Logging.h"
|
||||
#include "src/lib/eModbus-eModbus/ModbusServerRTU.h"
|
||||
|
@ -30,7 +32,7 @@
|
|||
|
||||
Preferences settings; // Store user settings
|
||||
// The current software version, shown on webserver
|
||||
const char* version_number = "5.8.dev";
|
||||
const char* version_number = "6.4.dev";
|
||||
|
||||
// Interval settings
|
||||
uint16_t intervalUpdateValues = INTERVAL_5_S; // Interval at which to update inverter values / Modbus registers
|
||||
|
@ -50,10 +52,12 @@ static ACAN2515_Buffer16 gBuffer;
|
|||
#ifdef CAN_FD
|
||||
#include "src/lib/pierremolinaro-ACAN2517FD/ACAN2517FD.h"
|
||||
ACAN2517FD canfd(MCP2517_CS, SPI, MCP2517_INT);
|
||||
#else
|
||||
typedef char CANFDMessage;
|
||||
#endif
|
||||
|
||||
// ModbusRTU parameters
|
||||
#if defined(BYD_MODBUS) || defined(LUNA2000_MODBUS)
|
||||
#ifdef MODBUS_INVERTER_SELECTED
|
||||
#define MB_RTU_NUM_VALUES 30000
|
||||
uint16_t mbPV[MB_RTU_NUM_VALUES]; // Process variable memory
|
||||
// Create a ModbusRTU server instance listening on Serial2 with 2000ms timeout
|
||||
|
@ -79,8 +83,8 @@ float charger_stat_LVvol = 0;
|
|||
int64_t core_task_time_us;
|
||||
MyTimer core_task_timer_10s(INTERVAL_10_S);
|
||||
|
||||
int64_t mqtt_task_time_us;
|
||||
MyTimer mqtt_task_timer_10s(INTERVAL_10_S);
|
||||
int64_t connectivity_task_time_us;
|
||||
MyTimer connectivity_task_timer_10s(INTERVAL_10_S);
|
||||
|
||||
MyTimer loop_task_timer_10s(INTERVAL_10_S);
|
||||
|
||||
|
@ -89,7 +93,10 @@ MyTimer loop_task_timer_10s(INTERVAL_10_S);
|
|||
enum State { DISCONNECTED, PRECHARGE, NEGATIVE, POSITIVE, PRECHARGE_OFF, COMPLETED, SHUTDOWN_REQUESTED };
|
||||
State contactorStatus = DISCONNECTED;
|
||||
|
||||
#define MAX_ALLOWED_FAULT_TICKS 500
|
||||
#define MAX_ALLOWED_FAULT_TICKS 1000
|
||||
/* NOTE: modify the precharge time constant below to account for the resistance and capacitance of the target system.
|
||||
* t=3RC at minimum, t=5RC ideally
|
||||
*/
|
||||
#define PRECHARGE_TIME_MS 160
|
||||
#define NEGATIVE_CONTACTOR_TIME_MS 1000
|
||||
#define POSITIVE_CONTACTOR_TIME_MS 2000
|
||||
|
@ -106,7 +113,7 @@ unsigned long timeSpentInFaultedMode = 0;
|
|||
#endif
|
||||
|
||||
TaskHandle_t main_loop_task;
|
||||
TaskHandle_t mqtt_loop_task;
|
||||
TaskHandle_t connectivity_loop_task;
|
||||
|
||||
// Initialization
|
||||
void setup() {
|
||||
|
@ -115,12 +122,8 @@ void setup() {
|
|||
init_stored_settings();
|
||||
|
||||
#ifdef WEBSERVER
|
||||
init_webserver();
|
||||
init_mDNS();
|
||||
#ifdef MQTT
|
||||
xTaskCreatePinnedToCore((TaskFunction_t)&mqtt_loop, "mqtt_loop", 4096, &mqtt_task_time_us, TASK_CONNECTIVITY_PRIO,
|
||||
&mqtt_loop_task, WIFI_CORE);
|
||||
#endif
|
||||
xTaskCreatePinnedToCore((TaskFunction_t)&connectivity_loop, "connectivity_loop", 4096, &connectivity_task_time_us,
|
||||
TASK_CONNECTIVITY_PRIO, &connectivity_loop_task, WIFI_CORE);
|
||||
#endif
|
||||
|
||||
init_events();
|
||||
|
@ -129,11 +132,11 @@ void setup() {
|
|||
|
||||
init_contactors();
|
||||
|
||||
init_modbus();
|
||||
init_rs485();
|
||||
|
||||
init_serialDataLink();
|
||||
|
||||
inform_user_on_inverter();
|
||||
init_inverter();
|
||||
|
||||
init_battery();
|
||||
|
||||
|
@ -142,6 +145,8 @@ void setup() {
|
|||
|
||||
esp_task_wdt_deinit(); // Disable watchdog
|
||||
|
||||
check_reset_reason();
|
||||
|
||||
xTaskCreatePinnedToCore((TaskFunction_t)&core_loop, "core_loop", 4096, &core_task_time_us, TASK_CORE_PRIO,
|
||||
&main_loop_task, CORE_FUNCTION_CORE);
|
||||
}
|
||||
|
@ -158,19 +163,29 @@ void loop() {
|
|||
#endif
|
||||
}
|
||||
|
||||
#ifdef WEBSERVER
|
||||
void connectivity_loop(void* task_time_us) {
|
||||
// Init
|
||||
init_webserver();
|
||||
init_mDNS();
|
||||
#ifdef MQTT
|
||||
void mqtt_loop(void* task_time_us) {
|
||||
// Init MQTT
|
||||
init_mqtt();
|
||||
#endif
|
||||
|
||||
while (true) {
|
||||
START_TIME_MEASUREMENT(wifi);
|
||||
wifi_monitor();
|
||||
END_TIME_MEASUREMENT_MAX(wifi, datalayer.system.status.wifi_task_10s_max_us);
|
||||
#ifdef MQTT
|
||||
START_TIME_MEASUREMENT(mqtt);
|
||||
mqtt_loop();
|
||||
END_TIME_MEASUREMENT_MAX(mqtt, datalayer.system.status.mqtt_task_10s_max_us);
|
||||
#endif
|
||||
|
||||
#ifdef FUNCTION_TIME_MEASUREMENT
|
||||
if (mqtt_task_timer_10s.elapsed()) {
|
||||
if (connectivity_task_timer_10s.elapsed()) {
|
||||
datalayer.system.status.mqtt_task_10s_max_us = 0;
|
||||
datalayer.system.status.wifi_task_10s_max_us = 0;
|
||||
}
|
||||
#endif
|
||||
delay(1);
|
||||
|
@ -182,12 +197,8 @@ void core_loop(void* task_time_us) {
|
|||
TickType_t xLastWakeTime = xTaskGetTickCount();
|
||||
const TickType_t xFrequency = pdMS_TO_TICKS(1); // Convert 1ms to ticks
|
||||
led_init();
|
||||
int64_t prev_wake;
|
||||
|
||||
while (true) {
|
||||
int64_t now = esp_timer_get_time();
|
||||
int64_t wake_period = now - prev_wake;
|
||||
prev_wake = now;
|
||||
START_TIME_MEASUREMENT(all);
|
||||
START_TIME_MEASUREMENT(comm);
|
||||
// Input
|
||||
|
@ -203,10 +214,9 @@ void core_loop(void* task_time_us) {
|
|||
#endif
|
||||
END_TIME_MEASUREMENT_MAX(comm, datalayer.system.status.time_comm_us);
|
||||
#ifdef WEBSERVER
|
||||
START_TIME_MEASUREMENT(wifi_ota);
|
||||
wifi_monitor();
|
||||
START_TIME_MEASUREMENT(ota);
|
||||
ElegantOTA.loop();
|
||||
END_TIME_MEASUREMENT_MAX(wifi_ota, datalayer.system.status.time_wifi_us);
|
||||
END_TIME_MEASUREMENT_MAX(ota, datalayer.system.status.time_ota_us);
|
||||
#endif
|
||||
|
||||
START_TIME_MEASUREMENT(time_10ms);
|
||||
|
@ -226,9 +236,16 @@ void core_loop(void* task_time_us) {
|
|||
START_TIME_MEASUREMENT(time_5s);
|
||||
if (millis() - previousMillisUpdateVal >= intervalUpdateValues) // Every 5s normally
|
||||
{
|
||||
previousMillisUpdateVal = millis();
|
||||
previousMillisUpdateVal = millis(); // Order matters on the update_loop!
|
||||
update_values_battery(); // Fetch battery values
|
||||
#ifdef DOUBLE_BATTERY
|
||||
update_values_battery2();
|
||||
#endif
|
||||
update_SOC(); // Check if real or calculated SOC% value should be sent
|
||||
update_values(); // Update values heading towards inverter. Prepare for sending on CAN, or write directly to Modbus.
|
||||
#ifndef SERIAL_LINK_RECEIVER
|
||||
update_machineryprotection(); // Check safeties (Not on serial link reciever board)
|
||||
#endif
|
||||
update_values_inverter(); // Update values heading towards inverter
|
||||
if (DUMMY_EVENT_ENABLED) {
|
||||
set_event(EVENT_DUMMY_ERROR, (uint8_t)millis());
|
||||
}
|
||||
|
@ -253,13 +270,13 @@ void core_loop(void* task_time_us) {
|
|||
datalayer.system.status.time_snap_10ms_us = datalayer.system.status.time_10ms_us;
|
||||
datalayer.system.status.time_snap_5s_us = datalayer.system.status.time_5s_us;
|
||||
datalayer.system.status.time_snap_cantx_us = datalayer.system.status.time_cantx_us;
|
||||
datalayer.system.status.time_snap_wifi_us = datalayer.system.status.time_wifi_us;
|
||||
datalayer.system.status.time_snap_ota_us = datalayer.system.status.time_ota_us;
|
||||
}
|
||||
|
||||
datalayer.system.status.core_task_max_us =
|
||||
MAX(datalayer.system.status.core_task_10s_max_us, datalayer.system.status.core_task_max_us);
|
||||
if (core_task_timer_10s.elapsed()) {
|
||||
datalayer.system.status.time_wifi_us = 0;
|
||||
datalayer.system.status.time_ota_us = 0;
|
||||
datalayer.system.status.time_comm_us = 0;
|
||||
datalayer.system.status.time_10ms_us = 0;
|
||||
datalayer.system.status.time_5s_us = 0;
|
||||
|
@ -309,6 +326,19 @@ void init_stored_settings() {
|
|||
settings.clear(); // If this clear function is executed, no settings will be read from storage
|
||||
#endif
|
||||
|
||||
char tempSSIDstring[63]; // Allocate buffer with sufficient size
|
||||
size_t lengthSSID = settings.getString("SSID", tempSSIDstring, sizeof(tempSSIDstring));
|
||||
if (lengthSSID > 0) { // Successfully read the string from memory. Set it to SSID!
|
||||
ssid = tempSSIDstring;
|
||||
} else { // Reading from settings failed. Do nothing with SSID. Raise event?
|
||||
}
|
||||
char tempPasswordString[63]; // Allocate buffer with sufficient size
|
||||
size_t lengthPassword = settings.getString("PASSWORD", tempPasswordString, sizeof(tempPasswordString));
|
||||
if (lengthPassword > 7) { // Successfully read the string from memory. Set it to password!
|
||||
password = tempPasswordString;
|
||||
} else { // Reading from settings failed. Do nothing with SSID. Raise event?
|
||||
}
|
||||
|
||||
static uint32_t temp = 0;
|
||||
temp = settings.getUInt("BATTERY_WH_MAX", false);
|
||||
if (temp != 0) {
|
||||
|
@ -337,9 +367,11 @@ void init_stored_settings() {
|
|||
}
|
||||
|
||||
void init_CAN() {
|
||||
// CAN pins
|
||||
// CAN pins
|
||||
#ifdef CAN_SE_PIN
|
||||
pinMode(CAN_SE_PIN, OUTPUT);
|
||||
digitalWrite(CAN_SE_PIN, LOW);
|
||||
#endif
|
||||
CAN_cfg.speed = CAN_SPEED_500KBPS;
|
||||
CAN_cfg.tx_pin_id = GPIO_NUM_27;
|
||||
CAN_cfg.rx_pin_id = GPIO_NUM_26;
|
||||
|
@ -367,6 +399,7 @@ void init_CAN() {
|
|||
DataBitRateFactor::x4); // Arbitration bit rate: 500 kbit/s, data bit rate: 2 Mbit/s
|
||||
settings.mRequestedMode = ACAN2517FDSettings::NormalFD; // ListenOnly / Normal20B / NormalFD
|
||||
const uint32_t errorCode = canfd.begin(settings, [] { canfd.isr(); });
|
||||
canfd.poll();
|
||||
if (errorCode == 0) {
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.print("Bit Rate prescaler: ");
|
||||
|
@ -404,28 +437,39 @@ void init_contactors() {
|
|||
pinMode(NEGATIVE_CONTACTOR_PIN, OUTPUT);
|
||||
digitalWrite(NEGATIVE_CONTACTOR_PIN, LOW);
|
||||
#ifdef PWM_CONTACTOR_CONTROL
|
||||
ledcSetup(POSITIVE_PWM_Ch, PWM_Freq, PWM_Res); // Setup PWM Channel Frequency and Resolution
|
||||
ledcSetup(NEGATIVE_PWM_Ch, PWM_Freq, PWM_Res); // Setup PWM Channel Frequency and Resolution
|
||||
ledcAttachPin(POSITIVE_CONTACTOR_PIN, POSITIVE_PWM_Ch); // Attach Positive Contactor Pin to Hardware PWM Channel
|
||||
ledcAttachPin(NEGATIVE_CONTACTOR_PIN, NEGATIVE_PWM_Ch); // Attach Positive Contactor Pin to Hardware PWM Channel
|
||||
ledcAttachChannel(POSITIVE_CONTACTOR_PIN, PWM_Freq, PWM_Res,
|
||||
POSITIVE_PWM_Ch); // Setup PWM Channel Frequency and Resolution
|
||||
ledcAttachChannel(NEGATIVE_CONTACTOR_PIN, PWM_Freq, PWM_Res,
|
||||
NEGATIVE_PWM_Ch); // Setup PWM Channel Frequency and Resolution
|
||||
ledcWrite(POSITIVE_PWM_Ch, 0); // Set Positive PWM to 0%
|
||||
ledcWrite(NEGATIVE_PWM_Ch, 0); // Set Negative PWM to 0%
|
||||
#endif
|
||||
pinMode(PRECHARGE_PIN, OUTPUT);
|
||||
digitalWrite(PRECHARGE_PIN, LOW);
|
||||
#endif
|
||||
// Init BMS contactor
|
||||
#ifdef HW_STARK // TODO: Rewrite this so LilyGo can aslo handle this BMS contactor
|
||||
pinMode(BMS_POWER, OUTPUT);
|
||||
digitalWrite(BMS_POWER, HIGH);
|
||||
#endif
|
||||
}
|
||||
|
||||
void init_modbus() {
|
||||
#if defined(BYD_MODBUS) || defined(LUNA2000_MODBUS)
|
||||
// Set up Modbus RTU Server
|
||||
void init_rs485() {
|
||||
// Set up Modbus RTU Server
|
||||
#ifdef RS485_EN_PIN
|
||||
pinMode(RS485_EN_PIN, OUTPUT);
|
||||
digitalWrite(RS485_EN_PIN, HIGH);
|
||||
#endif
|
||||
#ifdef RS485_SE_PIN
|
||||
pinMode(RS485_SE_PIN, OUTPUT);
|
||||
digitalWrite(RS485_SE_PIN, HIGH);
|
||||
#endif
|
||||
#ifdef PIN_5V_EN
|
||||
pinMode(PIN_5V_EN, OUTPUT);
|
||||
digitalWrite(PIN_5V_EN, HIGH);
|
||||
#endif
|
||||
|
||||
#ifdef MODBUS_INVERTER_SELECTED
|
||||
#ifdef BYD_MODBUS
|
||||
// Init Static data to the RTU Modbus
|
||||
handle_static_data_modbus_byd();
|
||||
|
@ -444,64 +488,44 @@ void init_modbus() {
|
|||
#endif
|
||||
}
|
||||
|
||||
void inform_user_on_inverter() {
|
||||
// Inform user what Inverter is used
|
||||
#ifdef BYD_CAN
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("BYD CAN protocol selected");
|
||||
#endif
|
||||
#endif
|
||||
#ifdef BYD_MODBUS
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("BYD Modbus RTU protocol selected");
|
||||
#endif
|
||||
#endif
|
||||
#ifdef LUNA2000_MODBUS
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("Luna2000 Modbus RTU protocol selected");
|
||||
#endif
|
||||
#endif
|
||||
#ifdef PYLON_CAN
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("PYLON CAN protocol selected");
|
||||
#endif
|
||||
#endif
|
||||
#ifdef SMA_CAN
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("SMA CAN protocol selected");
|
||||
#endif
|
||||
#endif
|
||||
#ifdef SMA_TRIPOWER_CAN
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("SMA Tripower CAN protocol selected");
|
||||
#endif
|
||||
#endif
|
||||
#ifdef SOFAR_CAN
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("SOFAR CAN protocol selected");
|
||||
#endif
|
||||
#endif
|
||||
void init_inverter() {
|
||||
#ifdef SOLAX_CAN
|
||||
datalayer.system.status.inverter_allows_contactor_closing =
|
||||
false; // The inverter needs to allow first on this protocol
|
||||
datalayer.system.status.inverter_allows_contactor_closing = false; // The inverter needs to allow first
|
||||
intervalUpdateValues = 800; // This protocol also requires the values to be updated faster
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("SOLAX CAN protocol selected");
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
void init_battery() {
|
||||
// Inform user what battery is used and perform setup
|
||||
setup_battery();
|
||||
|
||||
#ifdef CHADEMO_BATTERY
|
||||
intervalUpdateValues = 800; // This mode requires the values to be updated faster
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef CAN_FD
|
||||
// Functions
|
||||
#ifdef DEBUG_CANFD_DATA
|
||||
void print_canfd_frame(CANFDMessage rx_frame) {
|
||||
int i = 0;
|
||||
Serial.print(rx_frame.id, HEX);
|
||||
Serial.print(" ");
|
||||
for (i = 0; i < rx_frame.len; i++) {
|
||||
Serial.print(rx_frame.data[i] < 16 ? "0" : "");
|
||||
Serial.print(rx_frame.data[i], HEX);
|
||||
Serial.print(" ");
|
||||
}
|
||||
Serial.println(" ");
|
||||
}
|
||||
#endif
|
||||
void receive_canfd() { // This section checks if we have a complete CAN-FD message incoming
|
||||
CANFDMessage frame;
|
||||
if (canfd.available()) {
|
||||
canfd.receive(frame);
|
||||
#ifdef DEBUG_CANFD_DATA
|
||||
print_canfd_frame(frame);
|
||||
#endif
|
||||
receive_canfd_battery(frame);
|
||||
}
|
||||
}
|
||||
|
@ -511,108 +535,65 @@ void receive_can() { // This section checks if we have a complete CAN message i
|
|||
// Depending on which battery/inverter is selected, we forward this to their respective CAN routines
|
||||
CAN_frame_t rx_frame;
|
||||
if (xQueueReceive(CAN_cfg.rx_queue, &rx_frame, 0) == pdTRUE) {
|
||||
if (rx_frame.FIR.B.FF == CAN_frame_std) { // New standard frame
|
||||
// Battery
|
||||
#ifndef SERIAL_LINK_RECEIVER
|
||||
|
||||
// Battery
|
||||
#ifndef SERIAL_LINK_RECEIVER // Only needs to see inverter
|
||||
receive_can_battery(rx_frame);
|
||||
#endif
|
||||
// Inverter
|
||||
#ifdef BYD_CAN
|
||||
receive_can_byd(rx_frame);
|
||||
#endif
|
||||
#ifdef SMA_CAN
|
||||
receive_can_sma(rx_frame);
|
||||
#endif
|
||||
#ifdef SMA_TRIPOWER_CAN
|
||||
receive_can_sma_tripower(rx_frame);
|
||||
#ifdef CAN_INVERTER_SELECTED
|
||||
receive_can_inverter(rx_frame);
|
||||
#endif
|
||||
// Charger
|
||||
#ifdef CHEVYVOLT_CHARGER
|
||||
receive_can_chevyvolt_charger(rx_frame);
|
||||
#ifdef CHARGER_SELECTED
|
||||
receive_can_charger(rx_frame);
|
||||
#endif
|
||||
#ifdef NISSANLEAF_CHARGER
|
||||
receive_can_nissanleaf_charger(rx_frame);
|
||||
#endif
|
||||
} else { // New extended frame
|
||||
#ifdef PYLON_CAN
|
||||
receive_can_pylon(rx_frame);
|
||||
#endif
|
||||
#ifdef SOFAR_CAN
|
||||
receive_can_sofar(rx_frame);
|
||||
#endif
|
||||
#ifdef SOLAX_CAN
|
||||
receive_can_solax(rx_frame);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void send_can() {
|
||||
// Send CAN messages
|
||||
// Inverter
|
||||
#ifdef BYD_CAN
|
||||
send_can_byd();
|
||||
#endif
|
||||
#ifdef SMA_CAN
|
||||
send_can_sma();
|
||||
#endif
|
||||
#ifdef SMA_TRIPOWER_CAN
|
||||
send_can_sma_tripower();
|
||||
#endif
|
||||
#ifdef SOFAR_CAN
|
||||
send_can_sofar();
|
||||
#endif
|
||||
// Battery
|
||||
send_can_battery();
|
||||
// Charger
|
||||
#ifdef CHEVYVOLT_CHARGER
|
||||
send_can_chevyvolt_charger();
|
||||
// Inverter
|
||||
#ifdef CAN_INVERTER_SELECTED
|
||||
send_can_inverter();
|
||||
#endif
|
||||
#ifdef NISSANLEAF_CHARGER
|
||||
send_can_nissanleaf_charger();
|
||||
// Charger
|
||||
#ifdef CHARGER_SELECTED
|
||||
send_can_charger();
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef DUAL_CAN
|
||||
void receive_can2() { // This function is similar to receive_can, but just takes care of inverters in the 2nd bus.
|
||||
void receive_can2() { // This function is similar to receive_can, but just takes care of inverters in the 2nd bus OR double battery
|
||||
// Depending on which inverter is selected, we forward this to their respective CAN routines
|
||||
CAN_frame_t rx_frame2; // Struct with ESP32Can library format, compatible with the rest of the program
|
||||
CAN_frame_t rx_frame_can2; // Struct with ESP32Can library format, compatible with the rest of the program
|
||||
CANMessage MCP2515Frame; // Struct with ACAN2515 library format, needed to use thw MCP2515 library
|
||||
|
||||
if (can.available()) {
|
||||
can.receive(MCP2515Frame);
|
||||
|
||||
rx_frame2.MsgID = MCP2515Frame.id;
|
||||
rx_frame2.FIR.B.FF = MCP2515Frame.ext ? CAN_frame_ext : CAN_frame_std;
|
||||
rx_frame2.FIR.B.RTR = MCP2515Frame.rtr ? CAN_RTR : CAN_no_RTR;
|
||||
rx_frame2.FIR.B.DLC = MCP2515Frame.len;
|
||||
rx_frame_can2.MsgID = MCP2515Frame.id;
|
||||
rx_frame_can2.FIR.B.FF = MCP2515Frame.ext ? CAN_frame_ext : CAN_frame_std;
|
||||
rx_frame_can2.FIR.B.RTR = MCP2515Frame.rtr ? CAN_RTR : CAN_no_RTR;
|
||||
rx_frame_can2.FIR.B.DLC = MCP2515Frame.len;
|
||||
for (uint8_t i = 0; i < MCP2515Frame.len; i++) {
|
||||
rx_frame2.data.u8[i] = MCP2515Frame.data[i];
|
||||
rx_frame_can2.data.u8[i] = MCP2515Frame.data[i];
|
||||
}
|
||||
|
||||
if (rx_frame2.FIR.B.FF == CAN_frame_std) { // New standard frame
|
||||
#ifdef BYD_CAN
|
||||
receive_can_byd(rx_frame2);
|
||||
#ifdef CAN_INVERTER_SELECTED
|
||||
receive_can_inverter(rx_frame_can2);
|
||||
#endif
|
||||
#ifdef DOUBLE_BATTERY
|
||||
receive_can_battery2(rx_frame2);
|
||||
receive_can_battery2(rx_frame_can2);
|
||||
#endif
|
||||
} else { // New extended frame
|
||||
#ifdef PYLON_CAN
|
||||
receive_can_pylon(rx_frame2);
|
||||
#endif
|
||||
#ifdef SOLAX_CAN
|
||||
receive_can_solax(rx_frame2);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void send_can2() {
|
||||
// Send CAN
|
||||
// Inverter
|
||||
#ifdef BYD_CAN
|
||||
send_can_byd();
|
||||
#ifdef CAN_INVERTER_SELECTED
|
||||
send_can_inverter(); //Note this will only send to CAN1, unless we use SOLAX
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
@ -651,6 +632,7 @@ void handle_contactors() {
|
|||
digitalWrite(PRECHARGE_PIN, LOW);
|
||||
digitalWrite(NEGATIVE_CONTACTOR_PIN, LOW);
|
||||
digitalWrite(POSITIVE_CONTACTOR_PIN, LOW);
|
||||
set_event(EVENT_ERROR_OPEN_CONTACTOR, 0);
|
||||
return; // A fault scenario latches the contactor control. It is not possible to recover without a powercycle (and investigation why fault occured)
|
||||
}
|
||||
|
||||
|
@ -779,41 +761,12 @@ void update_SOC() {
|
|||
#endif //TODO: Constrain according to the user settings. Help wanted on algoritm to use.
|
||||
}
|
||||
|
||||
void summarize_battery_values() {
|
||||
// TODO: What needs to be summed?
|
||||
}
|
||||
|
||||
void update_values() {
|
||||
// Battery
|
||||
update_values_battery();
|
||||
#ifdef DOUBLE_BATTERY
|
||||
update_values_battery2();
|
||||
summarize_battery_values();
|
||||
void update_values_inverter() {
|
||||
#ifdef CAN_INVERTER_SELECTED
|
||||
update_values_can_inverter();
|
||||
#endif
|
||||
// Inverter
|
||||
#ifdef BYD_CAN
|
||||
update_values_can_byd();
|
||||
#endif
|
||||
#ifdef BYD_MODBUS
|
||||
update_modbus_registers_byd();
|
||||
#endif
|
||||
#ifdef LUNA2000_MODBUS
|
||||
update_modbus_registers_luna2000();
|
||||
#endif
|
||||
#ifdef PYLON_CAN
|
||||
update_values_can_pylon();
|
||||
#endif
|
||||
#ifdef SMA_CAN
|
||||
update_values_can_sma();
|
||||
#endif
|
||||
#ifdef SMA_TRIPOWER_CAN
|
||||
update_values_can_sma_tripower();
|
||||
#endif
|
||||
#ifdef SOFAR_CAN
|
||||
update_values_can_sofar();
|
||||
#endif
|
||||
#ifdef SOLAX_CAN
|
||||
update_values_can_solax();
|
||||
#ifdef MODBUS_INVERTER_SELECTED
|
||||
update_modbus_registers_inverter();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -842,6 +795,8 @@ void init_serialDataLink() {
|
|||
|
||||
void storeSettings() {
|
||||
settings.begin("batterySettings", false);
|
||||
settings.putString("SSID", String(ssid.c_str()));
|
||||
settings.putString("PASSWORD", String(password.c_str()));
|
||||
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
|
||||
|
@ -850,6 +805,82 @@ void storeSettings() {
|
|||
settings.putUInt("MAXCHARGEAMP", datalayer.battery.info.max_charge_amp_dA);
|
||||
settings.putUInt("MAXDISCHARGEAMP", datalayer.battery.info.max_discharge_amp_dA);
|
||||
settings.putBool("USE_SCALED_SOC", datalayer.battery.settings.soc_scaling_active);
|
||||
|
||||
settings.end();
|
||||
}
|
||||
|
||||
/** Reset reason numbering and description
|
||||
*
|
||||
typedef enum {
|
||||
ESP_RST_UNKNOWN, //!< 0 Reset reason can not be determined
|
||||
ESP_RST_POWERON, //!< 1 OK Reset due to power-on event
|
||||
ESP_RST_EXT, //!< 2 Reset by external pin (not applicable for ESP32)
|
||||
ESP_RST_SW, //!< 3 OK Software reset via esp_restart
|
||||
ESP_RST_PANIC, //!< 4 Software reset due to exception/panic
|
||||
ESP_RST_INT_WDT, //!< 5 Reset (software or hardware) due to interrupt watchdog
|
||||
ESP_RST_TASK_WDT, //!< 6 Reset due to task watchdog
|
||||
ESP_RST_WDT, //!< 7 Reset due to other watchdogs
|
||||
ESP_RST_DEEPSLEEP, //!< 8 Reset after exiting deep sleep mode
|
||||
ESP_RST_BROWNOUT, //!< 9 Brownout reset (software or hardware)
|
||||
ESP_RST_SDIO, //!< 10 Reset over SDIO
|
||||
ESP_RST_USB, //!< 11 Reset by USB peripheral
|
||||
ESP_RST_JTAG, //!< 12 Reset by JTAG
|
||||
ESP_RST_EFUSE, //!< 13 Reset due to efuse error
|
||||
ESP_RST_PWR_GLITCH, //!< 14 Reset due to power glitch detected
|
||||
ESP_RST_CPU_LOCKUP, //!< 15 Reset due to CPU lock up
|
||||
} esp_reset_reason_t;
|
||||
*/
|
||||
void check_reset_reason() {
|
||||
esp_reset_reason_t reason = esp_reset_reason();
|
||||
switch (reason) {
|
||||
case ESP_RST_UNKNOWN:
|
||||
set_event(EVENT_RESET_UNKNOWN, reason);
|
||||
break;
|
||||
case ESP_RST_POWERON:
|
||||
set_event(EVENT_RESET_POWERON, reason);
|
||||
break;
|
||||
case ESP_RST_EXT:
|
||||
set_event(EVENT_RESET_EXT, reason);
|
||||
break;
|
||||
case ESP_RST_SW:
|
||||
set_event(EVENT_RESET_SW, reason);
|
||||
break;
|
||||
case ESP_RST_PANIC:
|
||||
set_event(EVENT_RESET_PANIC, reason);
|
||||
break;
|
||||
case ESP_RST_INT_WDT:
|
||||
set_event(EVENT_RESET_INT_WDT, reason);
|
||||
break;
|
||||
case ESP_RST_TASK_WDT:
|
||||
set_event(EVENT_RESET_TASK_WDT, reason);
|
||||
break;
|
||||
case ESP_RST_WDT:
|
||||
set_event(EVENT_RESET_WDT, reason);
|
||||
break;
|
||||
case ESP_RST_DEEPSLEEP:
|
||||
set_event(EVENT_RESET_DEEPSLEEP, reason);
|
||||
break;
|
||||
case ESP_RST_BROWNOUT:
|
||||
set_event(EVENT_RESET_BROWNOUT, reason);
|
||||
break;
|
||||
case ESP_RST_SDIO:
|
||||
set_event(EVENT_RESET_SDIO, reason);
|
||||
break;
|
||||
case ESP_RST_USB:
|
||||
set_event(EVENT_RESET_USB, reason);
|
||||
break;
|
||||
case ESP_RST_JTAG:
|
||||
set_event(EVENT_RESET_JTAG, reason);
|
||||
break;
|
||||
case ESP_RST_EFUSE:
|
||||
set_event(EVENT_RESET_EFUSE, reason);
|
||||
break;
|
||||
case ESP_RST_PWR_GLITCH:
|
||||
set_event(EVENT_RESET_PWR_GLITCH, reason);
|
||||
break;
|
||||
case ESP_RST_CPU_LOCKUP:
|
||||
set_event(EVENT_RESET_CPU_LOCKUP, reason);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#include "USER_SETTINGS.h"
|
||||
|
||||
#include <string>
|
||||
/* This file contains all the battery settings and limits */
|
||||
/* They can be defined here, or later on in the WebUI */
|
||||
|
||||
|
@ -12,10 +12,9 @@ volatile float CHARGER_MAX_A = 11.5; // Max current output (amps) of charge
|
|||
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
|
||||
const char* ssid = "ZTE_5G_VACCINE"; // Maximum of 63 characters;
|
||||
const char* password = "secretpassword"; // Minimum of 8 characters;
|
||||
volatile uint8_t AccessPointEnabled = true; //Set to either true/false incase you want to enable direct wifi access point
|
||||
std::string ssid = "REPLACE_WITH_YOUR_SSID"; // Maximum of 63 characters;
|
||||
std::string 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 uint8_t wifi_channel = 0; // set to 0 for automatic channel selection
|
||||
|
|
|
@ -8,14 +8,20 @@
|
|||
/* To edit battery specific limits, see also the USER_SETTINGS.cpp file*/
|
||||
|
||||
/* Select battery used */
|
||||
#define BMW_I3_BATTERY
|
||||
//#define CHADEMO_BATTERY
|
||||
//#define BMW_I3_BATTERY
|
||||
//#define BYD_ATTO_3_BATTERY
|
||||
//#define CHADEMO_BATTERY //NOTE: inherently enables CONTACTOR_CONTROL below
|
||||
//#define IMIEV_CZERO_ION_BATTERY
|
||||
//#define JAGUAR_IPACE_BATTERY
|
||||
//#define KIA_HYUNDAI_64_BATTERY
|
||||
//#define KIA_E_GMP_BATTERY
|
||||
//#define KIA_HYUNDAI_HYBRID_BATTERY
|
||||
//#define MG_5_BATTERY
|
||||
//#define NISSAN_LEAF_BATTERY
|
||||
//#define PYLON_BATTERY
|
||||
//#define RENAULT_KANGOO_BATTERY
|
||||
//#define RENAULT_ZOE_BATTERY
|
||||
//#define RENAULT_ZOE_GEN1_BATTERY
|
||||
//#define RENAULT_ZOE_GEN2_BATTERY
|
||||
//#define SANTA_FE_PHEV_BATTERY
|
||||
//#define TESLA_MODEL_3_BATTERY
|
||||
//#define VOLVO_SPA_BATTERY
|
||||
|
@ -32,8 +38,13 @@
|
|||
//#define SOFAR_CAN //Enable this line to emulate a "Sofar Energy Storage Inverter High Voltage BMS General Protocol (Extended Frame)" over CAN bus
|
||||
//#define SOLAX_CAN //Enable this line to emulate a "SolaX Triple Power LFP" over CAN bus
|
||||
|
||||
/* Select hardware used for Battery-Emulator */
|
||||
#define HW_LILYGO
|
||||
//#define HW_STARK
|
||||
|
||||
/* Other options */
|
||||
//#define DEBUG_VIA_USB //Enable this line to have the USB port output serial diagnostic data while program runs (WARNING, raises CPU load, do not use for production)
|
||||
//#define DEBUG_CANFD_DATA //Enable this line to have the USB port output CAN-FD data while program runs (WARNING, raises CPU load, do not use for production)
|
||||
//#define INTERLOCK_REQUIRED //Nissan LEAF specific setting, if enabled requires both high voltage conenctors to be seated before starting
|
||||
//#define CONTACTOR_CONTROL //Enable this line to have pins 25,32,33 handle automatic precharge/contactor+/contactor- closing sequence
|
||||
//#define PWM_CONTACTOR_CONTROL //Enable this line to use PWM logic for contactors, which lower power consumption and heat generation
|
||||
|
@ -42,13 +53,11 @@
|
|||
//#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 (overrides any battery settings set in USER_SETTINGS.cpp)
|
||||
#define LOAD_SAVED_SETTINGS_ON_BOOT //Enable this line to read settings stored via the webserver on boot (overrides Wifi/battery settings set below)
|
||||
//#define FUNCTION_TIME_MEASUREMENT // Enable this to record execution times and present them in the web UI (WARNING, raises CPU load, do not use for production)
|
||||
|
||||
/* MQTT options */
|
||||
// #define MQTT // Enable this line to enable MQTT
|
||||
#define MQTT_SUBSCRIPTIONS \
|
||||
{ "my/topic/abc", "my/other/topic" }
|
||||
#define MQTT_SERVER "192.168.xxx.yyy"
|
||||
#define MQTT_PORT 1883
|
||||
|
||||
|
|
|
@ -4,55 +4,79 @@
|
|||
#include "../../USER_SETTINGS.h"
|
||||
|
||||
#ifdef BMW_I3_BATTERY
|
||||
#include "BMW-I3-BATTERY.h" //See this file for more i3 battery settings
|
||||
#include "BMW-I3-BATTERY.h"
|
||||
#endif
|
||||
|
||||
#ifdef BYD_ATTO_3_BATTERY
|
||||
#include "BYD-ATTO-3-BATTERY.h"
|
||||
#endif
|
||||
|
||||
#ifdef CHADEMO_BATTERY
|
||||
#include "CHADEMO-BATTERY.h" //See this file for more Chademo settings
|
||||
#include "CHADEMO-BATTERY.h"
|
||||
#endif
|
||||
|
||||
#ifdef IMIEV_CZERO_ION_BATTERY
|
||||
#include "IMIEV-CZERO-ION-BATTERY.h" //See this file for more triplet battery settings
|
||||
#include "IMIEV-CZERO-ION-BATTERY.h"
|
||||
#endif
|
||||
|
||||
#ifdef JAGUAR_IPACE_BATTERY
|
||||
#include "JAGUAR-IPACE-BATTERY.h"
|
||||
#endif
|
||||
|
||||
#ifdef KIA_E_GMP_BATTERY
|
||||
#include "KIA-E-GMP-BATTERY.h" //See this file for more GMP battery settings
|
||||
#include "KIA-E-GMP-BATTERY.h"
|
||||
#endif
|
||||
|
||||
#ifdef KIA_HYUNDAI_64_BATTERY
|
||||
#include "KIA-HYUNDAI-64-BATTERY.h" //See this file for more 64kWh battery settings
|
||||
#include "KIA-HYUNDAI-64-BATTERY.h"
|
||||
#endif
|
||||
|
||||
#ifdef KIA_HYUNDAI_HYBRID_BATTERY
|
||||
#include "KIA-HYUNDAI-HYBRID-BATTERY.h"
|
||||
#endif
|
||||
|
||||
#ifdef MG_5_BATTERY
|
||||
#include "MG-5-BATTERY.h"
|
||||
#endif
|
||||
|
||||
#ifdef NISSAN_LEAF_BATTERY
|
||||
#include "NISSAN-LEAF-BATTERY.h" //See this file for more LEAF battery settings
|
||||
#include "NISSAN-LEAF-BATTERY.h"
|
||||
#endif
|
||||
|
||||
#ifdef PYLON_BATTERY
|
||||
#include "PYLON-BATTERY.h"
|
||||
#endif
|
||||
|
||||
#ifdef RENAULT_KANGOO_BATTERY
|
||||
#include "RENAULT-KANGOO-BATTERY.h" //See this file for more Kangoo battery settings
|
||||
#include "RENAULT-KANGOO-BATTERY.h"
|
||||
#endif
|
||||
|
||||
#ifdef RENAULT_ZOE_BATTERY
|
||||
#include "RENAULT-ZOE-BATTERY.h" //See this file for more Zoe battery settings
|
||||
#ifdef RENAULT_ZOE_GEN1_BATTERY
|
||||
#include "RENAULT-ZOE-GEN1-BATTERY.h"
|
||||
#endif
|
||||
|
||||
#ifdef RENAULT_ZOE_GEN2_BATTERY
|
||||
#include "RENAULT-ZOE-GEN2-BATTERY.h"
|
||||
#endif
|
||||
|
||||
#ifdef SANTA_FE_PHEV_BATTERY
|
||||
#include "SANTA-FE-PHEV-BATTERY.h" //See this file for more Santa Fe PHEV battery settings
|
||||
#include "SANTA-FE-PHEV-BATTERY.h"
|
||||
#endif
|
||||
|
||||
#ifdef TESLA_MODEL_3_BATTERY
|
||||
#include "TESLA-MODEL-3-BATTERY.h" //See this file for more Tesla battery settings
|
||||
#include "TESLA-MODEL-3-BATTERY.h"
|
||||
#endif
|
||||
|
||||
#ifdef TEST_FAKE_BATTERY
|
||||
#include "TEST-FAKE-BATTERY.h" //See this file for more Fake battery settings
|
||||
#include "TEST-FAKE-BATTERY.h"
|
||||
#endif
|
||||
|
||||
#ifdef VOLVO_SPA_BATTERY
|
||||
#include "VOLVO-SPA-BATTERY.h" //See this file for more XC40 Recharge/Polestar2 settings
|
||||
#include "VOLVO-SPA-BATTERY.h"
|
||||
#endif
|
||||
|
||||
#ifdef SERIAL_LINK_RECEIVER
|
||||
#include "SERIAL-LINK-RECEIVER-FROM-BATTERY.h" //See this file for more Serial-battery settings
|
||||
#include "SERIAL-LINK-RECEIVER-FROM-BATTERY.h"
|
||||
#endif
|
||||
|
||||
#ifdef SERIAL_LINK_RECEIVER // The serial thing does its thing
|
||||
|
|
|
@ -15,11 +15,12 @@ static unsigned long previousMillis640 = 0; // will store last time a 600ms C
|
|||
static unsigned long previousMillis1000 = 0; // will store last time a 1000ms CAN Message was send
|
||||
static unsigned long previousMillis5000 = 0; // will store last time a 5000ms CAN Message was send
|
||||
static unsigned long previousMillis10000 = 0; // will store last time a 10000ms CAN Message was send
|
||||
static uint8_t CANstillAlive = 12; // counter for checking if CAN is still alive
|
||||
static uint8_t CAN2stillAlive = 12; // counter for checking if CAN2 is still alive
|
||||
static uint16_t CANerror = 0; // counter on how many CAN errors encountered
|
||||
|
||||
#define ALIVE_MAX_VALUE 14 // BMW CAN messages contain alive counter, goes from 0...14
|
||||
|
||||
enum BatterySize { BATTERY_60AH, BATTERY_94AH, BATTERY_120AH };
|
||||
static BatterySize detectedBattery = BATTERY_60AH;
|
||||
|
||||
enum CmdState { SOH, CELL_VOLTAGE, SOC, CELL_VOLTAGE_AVG };
|
||||
static CmdState cmdState = SOH;
|
||||
|
||||
|
@ -318,6 +319,7 @@ static uint8_t BMW_380_counter = 0;
|
|||
static uint32_t BMW_328_counter = 0;
|
||||
static bool battery_awake = false;
|
||||
static bool battery2_awake = false;
|
||||
static bool battery_info_available = false;
|
||||
|
||||
static uint32_t battery_serial_number = 0;
|
||||
static uint32_t battery_available_power_shortterm_charge = 0;
|
||||
|
@ -386,7 +388,7 @@ static uint8_t battery_status_diagnosis_powertrain_maximum_multiplexer = 0;
|
|||
static uint8_t battery_status_diagnosis_powertrain_immediate_multiplexer = 0;
|
||||
static uint8_t battery_ID2 = 0;
|
||||
static uint8_t battery_cellvoltage_mux = 0;
|
||||
static uint8_t battery_soh = 0;
|
||||
static uint8_t battery_soh = 99;
|
||||
|
||||
static uint32_t battery2_serial_number = 0;
|
||||
static uint32_t battery2_available_power_shortterm_charge = 0;
|
||||
|
@ -488,6 +490,9 @@ void CAN_WriteFrame(CAN_frame_t* tx_frame) {
|
|||
}
|
||||
|
||||
void update_values_battery2() { //This function maps all the values fetched via CAN2 to the battery2 datalayer
|
||||
if (!battery2_awake) {
|
||||
return;
|
||||
}
|
||||
|
||||
datalayer.battery2.status.real_soc = (battery2_HVBatt_SOC * 10);
|
||||
|
||||
|
@ -520,23 +525,12 @@ void update_values_battery2() { //This function maps all the values fetched via
|
|||
|
||||
datalayer.battery2.status.cell_min_voltage_mV = datalayer.battery2.status.cell_voltages_mV[0];
|
||||
datalayer.battery2.status.cell_max_voltage_mV = datalayer.battery2.status.cell_voltages_mV[1];
|
||||
|
||||
/* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/
|
||||
if (!CAN2stillAlive) {
|
||||
set_event(EVENT_CAN2_RX_FAILURE, 2);
|
||||
datalayer.battery2.status.bms_status = FAULT; //TODO: Refactor handling of event for battery2
|
||||
datalayer.system.status.battery2_allows_contactor_closing = false;
|
||||
} else {
|
||||
CAN2stillAlive--;
|
||||
clear_event(EVENT_CAN2_RX_FAILURE);
|
||||
}
|
||||
// Check if we have encountered any malformed CAN messages
|
||||
if (CANerror > MAX_CAN_FAILURES) {
|
||||
set_event(EVENT_CAN_RX_WARNING, 2);
|
||||
}
|
||||
}
|
||||
|
||||
void update_values_battery() { //This function maps all the values fetched via CAN to the battery datalayer
|
||||
if (!battery_awake) {
|
||||
return;
|
||||
}
|
||||
|
||||
datalayer.battery.status.real_soc = (battery_HVBatt_SOC * 10);
|
||||
|
||||
|
@ -548,16 +542,9 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
|
||||
datalayer.battery.status.soh_pptt = battery_soh * 100;
|
||||
|
||||
if (battery_BEV_available_power_longterm_discharge > 65000) {
|
||||
datalayer.battery.status.max_discharge_power_W = 65000;
|
||||
} else {
|
||||
datalayer.battery.status.max_discharge_power_W = battery_BEV_available_power_longterm_discharge;
|
||||
}
|
||||
if (battery_BEV_available_power_longterm_charge > 65000) {
|
||||
datalayer.battery.status.max_charge_power_W = 65000;
|
||||
} else {
|
||||
|
||||
datalayer.battery.status.max_charge_power_W = battery_BEV_available_power_longterm_charge;
|
||||
}
|
||||
|
||||
battery_power = (datalayer.battery.status.current_dA * (datalayer.battery.status.voltage_dV / 100));
|
||||
|
||||
|
@ -567,19 +554,53 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
|
||||
datalayer.battery.status.temperature_max_dC = battery_temperature_max * 10; // Add a decimal
|
||||
|
||||
if (datalayer.battery.status.cell_voltages_mV[0] > 0 && datalayer.battery.status.cell_voltages_mV[2] > 0) {
|
||||
datalayer.battery.status.cell_min_voltage_mV = datalayer.battery.status.cell_voltages_mV[0];
|
||||
datalayer.battery.status.cell_max_voltage_mV = datalayer.battery.status.cell_voltages_mV[1];
|
||||
|
||||
/* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/
|
||||
if (!CANstillAlive) {
|
||||
set_event(EVENT_CAN_RX_FAILURE, 0);
|
||||
} else {
|
||||
CANstillAlive--;
|
||||
clear_event(EVENT_CAN_RX_FAILURE);
|
||||
datalayer.battery.status.cell_max_voltage_mV = datalayer.battery.status.cell_voltages_mV[2];
|
||||
}
|
||||
// Check if we have encountered any malformed CAN messages
|
||||
if (CANerror > MAX_CAN_FAILURES) {
|
||||
set_event(EVENT_CAN_RX_WARNING, 0);
|
||||
|
||||
if (battery_info_available) {
|
||||
// Start checking safeties. First up, cellvoltages!
|
||||
if (detectedBattery == BATTERY_60AH) {
|
||||
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_60AH;
|
||||
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_60AH;
|
||||
if (datalayer.battery.status.cell_max_voltage_mV >= MAX_CELL_VOLTAGE_60AH) {
|
||||
set_event(EVENT_CELL_OVER_VOLTAGE, 0);
|
||||
}
|
||||
if (datalayer.battery.status.cell_min_voltage_mV <= MIN_CELL_VOLTAGE_60AH) {
|
||||
set_event(EVENT_CELL_UNDER_VOLTAGE, 0);
|
||||
}
|
||||
} else if (detectedBattery == BATTERY_94AH) {
|
||||
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_94AH;
|
||||
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_94AH;
|
||||
if (datalayer.battery.status.cell_max_voltage_mV >= MAX_CELL_VOLTAGE_94AH) {
|
||||
set_event(EVENT_CELL_OVER_VOLTAGE, 0);
|
||||
}
|
||||
if (datalayer.battery.status.cell_min_voltage_mV <= MIN_CELL_VOLTAGE_94AH) {
|
||||
set_event(EVENT_CELL_UNDER_VOLTAGE, 0);
|
||||
}
|
||||
} else { // BATTERY_120AH
|
||||
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_120AH;
|
||||
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_120AH;
|
||||
if (datalayer.battery.status.cell_max_voltage_mV >= MAX_CELL_VOLTAGE_120AH) {
|
||||
set_event(EVENT_CELL_OVER_VOLTAGE, 0);
|
||||
}
|
||||
if (datalayer.battery.status.cell_min_voltage_mV <= MIN_CELL_VOLTAGE_120AH) {
|
||||
set_event(EVENT_CELL_UNDER_VOLTAGE, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Perform other safety checks
|
||||
if (battery_status_error_locking == 2) { // HVIL seated?
|
||||
set_event(EVENT_HVIL_FAILURE, 0);
|
||||
} else {
|
||||
clear_event(EVENT_HVIL_FAILURE);
|
||||
}
|
||||
if (battery_status_precharge_locked == 2) { // Capacitor seated?
|
||||
set_event(EVENT_PRECHARGE_FAILURE, 0);
|
||||
} else {
|
||||
clear_event(EVENT_PRECHARGE_FAILURE);
|
||||
}
|
||||
|
||||
#ifdef DEBUG_VIA_USB
|
||||
|
@ -612,7 +633,8 @@ void receive_can_battery(CAN_frame_t rx_frame) {
|
|||
switch (rx_frame.MsgID) {
|
||||
case 0x112: //BMS [10ms] Status Of High-Voltage Battery - 2
|
||||
battery_awake = true;
|
||||
CANstillAlive = 12; //This message is only sent if 30C (Wakeup pin on battery) is energized with 12V
|
||||
datalayer.battery.status.CAN_battery_still_alive =
|
||||
CAN_STILL_ALIVE; //This message is only sent if 30C (Wakeup pin on battery) is energized with 12V
|
||||
battery_current = (rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]) - 8192; //deciAmps (-819.2 to 819.0A)
|
||||
battery_volts = (rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]); //500.0 V
|
||||
datalayer.battery.status.voltage_dV = battery_volts; // Update the datalayer as soon as possible with this info
|
||||
|
@ -650,7 +672,7 @@ void receive_can_battery(CAN_frame_t rx_frame) {
|
|||
battery_awake = true;
|
||||
if (calculateCRC(rx_frame, rx_frame.FIR.B.DLC, 0x15) != rx_frame.data.u8[0]) {
|
||||
//If calculated CRC does not match transmitted CRC, increase CANerror counter
|
||||
CANerror++;
|
||||
datalayer.battery.status.CAN_error_counter++;
|
||||
break;
|
||||
}
|
||||
battery_status_diagnostics_HV = (rx_frame.data.u8[2] & 0x0F);
|
||||
|
@ -689,18 +711,6 @@ void receive_can_battery(CAN_frame_t rx_frame) {
|
|||
case 0x41C: //BMS [1s] Operating Mode Status Of Hybrid - 2
|
||||
battery_status_cooling_HV = (rx_frame.data.u8[1] & 0x03);
|
||||
break;
|
||||
case 0x426: // TODO: Figure out how to trigger sending of this. Does the SME require some CAN command?
|
||||
battery_cellvoltage_mux = rx_frame.data.u8[0];
|
||||
if (battery_cellvoltage_mux == 0) {
|
||||
datalayer.battery.status.cell_voltages_mV[0] = ((rx_frame.data.u8[1] * 10) + 1800);
|
||||
datalayer.battery.status.cell_voltages_mV[1] = ((rx_frame.data.u8[2] * 10) + 1800);
|
||||
datalayer.battery.status.cell_voltages_mV[2] = ((rx_frame.data.u8[3] * 10) + 1800);
|
||||
datalayer.battery.status.cell_voltages_mV[3] = ((rx_frame.data.u8[4] * 10) + 1800);
|
||||
datalayer.battery.status.cell_voltages_mV[4] = ((rx_frame.data.u8[5] * 10) + 1800);
|
||||
datalayer.battery.status.cell_voltages_mV[5] = ((rx_frame.data.u8[6] * 10) + 1800);
|
||||
datalayer.battery.status.cell_voltages_mV[5] = ((rx_frame.data.u8[7] * 10) + 1800);
|
||||
}
|
||||
break;
|
||||
case 0x430: //BMS [1s] - Charging status of high-voltage battery - 2
|
||||
battery_prediction_voltage_shortterm_charge = (rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
|
||||
battery_prediction_voltage_shortterm_discharge = (rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
|
||||
|
@ -714,6 +724,13 @@ void receive_can_battery(CAN_frame_t rx_frame) {
|
|||
battery_prediction_duration_charging_minutes = (rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
|
||||
battery_prediction_time_end_of_charging_minutes = rx_frame.data.u8[4];
|
||||
battery_energy_content_maximum_kWh = (((rx_frame.data.u8[6] & 0x0F) << 8 | rx_frame.data.u8[5])) / 50;
|
||||
if (battery_energy_content_maximum_kWh > 37) {
|
||||
detectedBattery = BATTERY_120AH;
|
||||
} else if (battery_energy_content_maximum_kWh > 25) {
|
||||
detectedBattery = BATTERY_94AH;
|
||||
} else {
|
||||
detectedBattery = BATTERY_60AH;
|
||||
}
|
||||
break;
|
||||
case 0x432: //BMS [200ms] SOC% info
|
||||
battery_request_operating_mode = (rx_frame.data.u8[0] & 0x03);
|
||||
|
@ -758,6 +775,7 @@ void receive_can_battery(CAN_frame_t rx_frame) {
|
|||
case SOH:
|
||||
if (next_data >= 4) {
|
||||
battery_soh = message_data[3];
|
||||
battery_info_available = true;
|
||||
}
|
||||
break;
|
||||
case SOC:
|
||||
|
@ -778,7 +796,8 @@ void receive_can_battery2(CAN_frame_t rx_frame) {
|
|||
switch (rx_frame.MsgID) {
|
||||
case 0x112: //BMS [10ms] Status Of High-Voltage Battery - 2
|
||||
battery2_awake = true;
|
||||
CAN2stillAlive = 12; //This message is only sent if 30C (Wakeup pin on battery) is energized with 12V
|
||||
datalayer.battery2.status.CAN_battery_still_alive =
|
||||
CAN_STILL_ALIVE; //This message is only sent if 30C (Wakeup pin on battery) is energized with 12V
|
||||
battery2_current = (rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]) - 8192; //deciAmps (-819.2 to 819.0A)
|
||||
battery2_volts = (rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]); //500.0 V
|
||||
datalayer.battery2.status.voltage_dV =
|
||||
|
@ -817,7 +836,7 @@ void receive_can_battery2(CAN_frame_t rx_frame) {
|
|||
battery2_awake = true;
|
||||
if (calculateCRC(rx_frame, rx_frame.FIR.B.DLC, 0x15) != rx_frame.data.u8[0]) {
|
||||
//If calculated CRC does not match transmitted CRC, increase CANerror counter
|
||||
CANerror++;
|
||||
datalayer.battery2.status.CAN_error_counter++;
|
||||
break;
|
||||
}
|
||||
battery2_status_diagnostics_HV = (rx_frame.data.u8[2] & 0x0F);
|
||||
|
@ -950,6 +969,8 @@ void send_can_battery() {
|
|||
// Check if sending of CAN messages has been delayed too much.
|
||||
if ((currentMillis - previousMillis20 >= INTERVAL_20_MS_DELAYED) && (currentMillis > BOOTUP_TIME)) {
|
||||
set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis20));
|
||||
} else {
|
||||
clear_event(EVENT_CAN_OVERRUN);
|
||||
}
|
||||
previousMillis20 = currentMillis;
|
||||
|
||||
|
@ -1093,6 +1114,38 @@ void send_can_battery() {
|
|||
|
||||
BMW_433.data.u8[1] = 0x01; // First 433 message byte1 we send is unique, once we sent initial value send this
|
||||
BMW_3E8.data.u8[0] = 0xF1; // First 3E8 message byte0 we send is unique, once we sent initial value send this
|
||||
|
||||
next_data = 0;
|
||||
switch (cmdState) {
|
||||
case SOC:
|
||||
ESP32Can.CANWriteFrame(&BMW_6F1_CELL);
|
||||
#ifdef DOUBLE_BATTERY
|
||||
CAN_WriteFrame(&BMW_6F1_CELL);
|
||||
#endif
|
||||
cmdState = CELL_VOLTAGE;
|
||||
break;
|
||||
case CELL_VOLTAGE:
|
||||
ESP32Can.CANWriteFrame(&BMW_6F1_SOH);
|
||||
#ifdef DOUBLE_BATTERY
|
||||
CAN_WriteFrame(&BMW_6F1_SOH);
|
||||
#endif
|
||||
cmdState = SOH;
|
||||
break;
|
||||
case SOH:
|
||||
ESP32Can.CANWriteFrame(&BMW_6F1_CELL_VOLTAGE_AVG);
|
||||
#ifdef DOUBLE_BATTERY
|
||||
CAN_WriteFrame(&BMW_6F1_CELL_VOLTAGE_AVG);
|
||||
#endif
|
||||
cmdState = CELL_VOLTAGE_AVG;
|
||||
break;
|
||||
case CELL_VOLTAGE_AVG:
|
||||
ESP32Can.CANWriteFrame(&BMW_6F1_SOC);
|
||||
#ifdef DOUBLE_BATTERY
|
||||
CAN_WriteFrame(&BMW_6F1_SOC);
|
||||
#endif
|
||||
cmdState = SOC;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Send 5000ms CAN Message
|
||||
if (currentMillis - previousMillis5000 >= INTERVAL_5_S) {
|
||||
|
@ -1137,14 +1190,17 @@ void send_can_battery() {
|
|||
CAN_WriteFrame(&BMW_37B);
|
||||
#endif
|
||||
|
||||
next_data = 0;
|
||||
ESP32Can.CANWriteFrame(&BMW_6F1_CELL);
|
||||
#ifdef DOUBLE_BATTERY
|
||||
CAN_WriteFrame(&BMW_6F1_CELL);
|
||||
#endif
|
||||
|
||||
BMW_3E5.data.u8[0] = 0xFD; // First 3E5 message byte0 we send is unique, once we sent initial value send this
|
||||
}
|
||||
} else {
|
||||
previousMillis20 = currentMillis;
|
||||
previousMillis100 = currentMillis;
|
||||
previousMillis200 = currentMillis;
|
||||
previousMillis500 = currentMillis;
|
||||
previousMillis640 = currentMillis;
|
||||
previousMillis1000 = currentMillis;
|
||||
previousMillis5000 = currentMillis;
|
||||
previousMillis10000 = currentMillis;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1153,9 +1209,9 @@ void setup_battery(void) { // Performs one time setup at startup
|
|||
Serial.println("BMW i3 battery selected");
|
||||
#endif
|
||||
|
||||
datalayer.battery.info.max_design_voltage_dV =
|
||||
4040; // 404.4V, over this, charging is not possible (goes into forced discharge)
|
||||
datalayer.battery.info.min_design_voltage_dV = 2800; // 280.0V under this, discharging further is disabled
|
||||
//Before we have started up and detected which battery is in use, use 60AH values
|
||||
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_60AH;
|
||||
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_60AH;
|
||||
|
||||
datalayer.system.status.battery_allows_contactor_closing = true;
|
||||
|
||||
|
|
|
@ -10,6 +10,19 @@ extern ACAN2515 can;
|
|||
#define BATTERY_SELECTED
|
||||
|
||||
#define WUP_PIN 25
|
||||
#define MAX_CELL_VOLTAGE_60AH 4110 // Battery is put into emergency stop if one cell goes over this value
|
||||
#define MIN_CELL_VOLTAGE_60AH 2700 // Battery is put into emergency stop if one cell goes below this value
|
||||
#define MAX_CELL_VOLTAGE_94AH 4140 // Battery is put into emergency stop if one cell goes over this value
|
||||
#define MIN_CELL_VOLTAGE_94AH 2700 // Battery is put into emergency stop if one cell goes below this value
|
||||
#define MAX_CELL_VOLTAGE_120AH 4190 // Battery is put into emergency stop if one cell goes over this value
|
||||
#define MIN_CELL_VOLTAGE_120AH 2790 // Battery is put into emergency stop if one cell goes below this value
|
||||
#define MAX_CELL_DEVIATION_MV 250 // LED turns yellow on the board if mv delta exceeds this value
|
||||
#define MAX_PACK_VOLTAGE_60AH 3950 // Charge stops if pack voltage exceeds this value
|
||||
#define MIN_PACK_VOLTAGE_60AH 2590 // Discharge stops if pack voltage exceeds this value
|
||||
#define MAX_PACK_VOLTAGE_94AH 3980 // Charge stops if pack voltage exceeds this value
|
||||
#define MIN_PACK_VOLTAGE_94AH 2590 // Discharge stops if pack voltage exceeds this value
|
||||
#define MAX_PACK_VOLTAGE_120AH 4030 // Charge stops if pack voltage exceeds this value
|
||||
#define MIN_PACK_VOLTAGE_120AH 2680 // Discharge stops if pack voltage exceeds this value
|
||||
void setup_battery(void);
|
||||
|
||||
#endif
|
||||
|
|
367
Software/src/battery/BYD-ATTO-3-BATTERY.cpp
Normal file
367
Software/src/battery/BYD-ATTO-3-BATTERY.cpp
Normal file
|
@ -0,0 +1,367 @@
|
|||
#include "../include.h"
|
||||
#ifdef BYD_ATTO_3_BATTERY
|
||||
#include "../datalayer/datalayer.h"
|
||||
#include "../devboard/utils/events.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
#include "BYD-ATTO-3-BATTERY.h"
|
||||
|
||||
/* TODO:
|
||||
- Get contactor closing working
|
||||
- NOTE: Some packs can be locked hard? after a crash has occured. Bypassing contactors manually might be required?
|
||||
- Figure out which CAN messages need to be sent towards the battery to keep it alive
|
||||
-Maybe already enough with 0x12D and 0x411? Plus the PID polls might keep it alive.
|
||||
- Map all values from battery CAN messages
|
||||
-SOC% still not found (Lets take it from PID poll, not working right yet)
|
||||
-SOC% is now ESTIMATED. This is bad, and should be fixed as soon as possible with the real value from CAN
|
||||
*/
|
||||
|
||||
/* Do not change code below unless you are sure what you are doing */
|
||||
static unsigned long previousMillis50 = 0; // will store last time a 50ms CAN Message was send
|
||||
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
|
||||
static uint8_t counter_50ms = 0;
|
||||
static uint8_t counter_100ms = 0;
|
||||
static uint8_t frame6_counter = 0xB;
|
||||
static uint8_t frame7_counter = 0x5;
|
||||
|
||||
static int16_t temperature_ambient = 0;
|
||||
static int16_t daughterboard_temperatures[10];
|
||||
static int16_t lowest_temperature = 0;
|
||||
static int16_t highest_temperature = 0;
|
||||
static int16_t calc_min_temperature = 0;
|
||||
static int16_t calc_max_temperature = 0;
|
||||
|
||||
static uint16_t BMS_SOC = 0;
|
||||
static uint16_t BMS_voltage = 0;
|
||||
static int16_t BMS_current = 0;
|
||||
static int16_t BMS_lowest_cell_temperature = 0;
|
||||
static int16_t BMS_highest_cell_temperature = 0;
|
||||
static int16_t BMS_average_cell_temperature = 0;
|
||||
static uint16_t BMS_lowest_cell_voltage_mV = 3300;
|
||||
static uint16_t BMS_highest_cell_voltage_mV = 3300;
|
||||
|
||||
#define POLL_FOR_BATTERY_SOC 0x05
|
||||
#define POLL_FOR_BATTERY_VOLTAGE 0x08
|
||||
#define POLL_FOR_BATTERY_CURRENT 0x09
|
||||
#define POLL_FOR_LOWEST_TEMP_CELL 0x2f
|
||||
#define POLL_FOR_HIGHEST_TEMP_CELL 0x31
|
||||
#define POLL_FOR_BATTERY_PACK_AVG_TEMP 0x32
|
||||
#define POLL_FOR_BATTERY_CELL_MV_MAX 0x2D
|
||||
#define POLL_FOR_BATTERY_CELL_MV_MIN 0x2B
|
||||
#define UNKNOWN_POLL_1 0xFC
|
||||
|
||||
CAN_frame_t ATTO_3_12D = {.FIR = {.B =
|
||||
{
|
||||
.DLC = 8,
|
||||
.FF = CAN_frame_std,
|
||||
}},
|
||||
.MsgID = 0x12D,
|
||||
.data = {0xA0, 0x28, 0x02, 0xA0, 0x0C, 0x71, 0xCF, 0x49}};
|
||||
CAN_frame_t ATTO_3_411 = {.FIR = {.B =
|
||||
{
|
||||
.DLC = 8,
|
||||
.FF = CAN_frame_std,
|
||||
}},
|
||||
.MsgID = 0x411,
|
||||
.data = {0x98, 0x3A, 0x88, 0x13, 0x9D, 0x00, 0xFF, 0x8C}};
|
||||
|
||||
CAN_frame_t ATTO_3_7E7_POLL = {
|
||||
.FIR = {.B =
|
||||
{
|
||||
.DLC = 8,
|
||||
.FF = CAN_frame_std,
|
||||
}},
|
||||
.MsgID = 0x7E7,
|
||||
.data = {0x03, 0x22, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00}}; //Poll PID 03 22 00 05 (POLL_FOR_BATTERY_SOC)
|
||||
|
||||
// Define the data points for %SOC depending on pack voltage
|
||||
const uint8_t numPoints = 14;
|
||||
const uint16_t SOC[numPoints] = {10000, 9970, 9490, 8470, 7750, 6790, 5500, 4900, 3910, 3000, 2280, 1600, 480, 0};
|
||||
const uint16_t voltage[numPoints] = {4400, 4230, 4180, 4171, 4169, 4160, 4130,
|
||||
4121, 4119, 4100, 4070, 4030, 3950, 3800};
|
||||
|
||||
uint16_t estimateSOC(uint16_t packVoltage) { // Linear interpolation function
|
||||
if (packVoltage >= voltage[0]) {
|
||||
return SOC[0];
|
||||
}
|
||||
if (packVoltage <= voltage[numPoints - 1]) {
|
||||
return SOC[numPoints - 1];
|
||||
}
|
||||
|
||||
for (int i = 1; i < numPoints; ++i) {
|
||||
if (packVoltage >= voltage[i]) {
|
||||
double t = (packVoltage - voltage[i]) / (voltage[i - 1] - voltage[i]);
|
||||
return SOC[i] + t * (SOC[i - 1] - SOC[i]);
|
||||
}
|
||||
}
|
||||
return 0; // Default return for safety, should never reach here
|
||||
}
|
||||
|
||||
void update_values_battery() { //This function maps all the values fetched via CAN to the correct parameters used for modbus
|
||||
|
||||
datalayer.battery.status.voltage_dV = BMS_voltage * 10;
|
||||
|
||||
//datalayer.battery.status.real_soc = BMS_SOC * 100; //TODO: This is not yet found!
|
||||
// We instead estimate the SOC% based on the battery voltage
|
||||
// This is a very bad solution, and as soon as an usable SOC% value has been found on CAN, we should switch to that!
|
||||
datalayer.battery.status.real_soc = estimateSOC(datalayer.battery.status.voltage_dV);
|
||||
|
||||
datalayer.battery.status.current_dA = -BMS_current;
|
||||
|
||||
datalayer.battery.status.remaining_capacity_Wh = static_cast<uint32_t>(
|
||||
(static_cast<double>(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh);
|
||||
|
||||
datalayer.battery.status.max_discharge_power_W = 10000; //TODO: Map from CAN later on
|
||||
|
||||
datalayer.battery.status.max_charge_power_W = 10000; //TODO: Map from CAN later on
|
||||
|
||||
datalayer.battery.status.active_power_W =
|
||||
(datalayer.battery.status.current_dA * (datalayer.battery.status.voltage_dV / 100));
|
||||
|
||||
datalayer.battery.status.cell_max_voltage_mV = BMS_highest_cell_voltage_mV;
|
||||
|
||||
datalayer.battery.status.cell_min_voltage_mV = BMS_lowest_cell_voltage_mV;
|
||||
|
||||
// Initialize min and max variables
|
||||
calc_min_temperature = daughterboard_temperatures[0];
|
||||
calc_max_temperature = daughterboard_temperatures[0];
|
||||
|
||||
// Loop through the array of daughterboard temps to find the smallest and largest values
|
||||
for (int i = 1; i < 10; i++) {
|
||||
if (daughterboard_temperatures[i] < calc_min_temperature) {
|
||||
calc_min_temperature = daughterboard_temperatures[i];
|
||||
}
|
||||
if (daughterboard_temperatures[i] > calc_max_temperature) {
|
||||
calc_max_temperature = daughterboard_temperatures[i];
|
||||
}
|
||||
}
|
||||
|
||||
datalayer.battery.status.temperature_min_dC = calc_min_temperature * 10; // Add decimals
|
||||
datalayer.battery.status.temperature_max_dC = calc_max_temperature * 10;
|
||||
|
||||
#ifdef DEBUG_VIA_USB
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
void receive_can_battery(CAN_frame_t rx_frame) {
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
switch (rx_frame.MsgID) { //Log values taken with 422V from battery
|
||||
case 0x244: //00,00,00,04,41,0F,20,8B - Static, values never changes between logs
|
||||
break;
|
||||
case 0x245: //01,00,02,19,3A,25,90,F4 Seems to have a mux in frame0
|
||||
//02,00,90,01,79,79,90,EA // Point of interest, went from 7E,75 to 7B,7C when discharging
|
||||
//03,C6,88,12,FD,48,90,5C
|
||||
//04,00,FF,FF,00,00,90,6D
|
||||
if (rx_frame.data.u8[0] == 0x01) {
|
||||
temperature_ambient = (rx_frame.data.u8[4] - 40); // TODO, check if this is actually temperature_ambient
|
||||
}
|
||||
break;
|
||||
case 0x286: //01,FF,FF,FF,FF,FF,FF,04 - Static, values never changes between logs
|
||||
break;
|
||||
case 0x334: //FF,FF,FF,FC,3F,00,F0,D7 - Static, values never changes between logs
|
||||
break;
|
||||
case 0x338: //01,52,02,00,88,13,00,0F
|
||||
//01,51,02,00,88,13,00,10 407.5V 18deg
|
||||
//01,4F,02,00,88,13,00,12 408.5V 14deg
|
||||
break;
|
||||
case 0x344: //00,52,02,CC,1F,FF,04,BD
|
||||
break;
|
||||
case 0x345: //27,0B,00,00,00,E0,01,EC - Static, values never changes between logs
|
||||
break;
|
||||
case 0x347: //FF,00,00,F9,FF,FF,FF,0A - Static, values never changes between logs
|
||||
break;
|
||||
case 0x34A: //00,52,02,CC,1F,FF,04,BD
|
||||
//00,51,02,CC,1F,FF,04,BE //407.5V 18deg
|
||||
//00,4F,02,CC,1F,FF,04,C0 //408.5V 14deg
|
||||
break;
|
||||
case 0x35E: //01,00,C8,32,00,63,00,A1 - Flickering between A0 and A1, Could be temperature?
|
||||
//01,00,64,01,10,63,00,26 //407.5V 18deg
|
||||
//01,00,64,1C,10,63,00,0B //408.5V 14deg
|
||||
break;
|
||||
case 0x360: //30,19,DE,D1,0B,C3,4B,EE - Static, values never changes between logs, Last and first byte has F-0 counters
|
||||
break;
|
||||
case 0x36C: //01,57,13,DC,08,70,17,29 Seems to have a mux in frame0 , first message is static, never changes between logs
|
||||
//02,03,DC,05,C0,0F,0F,3B - Static, values never changes between logs
|
||||
//03,86,01,40,06,5C,02,D1 - Static, values never changes between logs
|
||||
//04,57,13,73,04,01,FF,1A - Static, values never changes between logs
|
||||
//05,FF,FF,FF,FF,FF,FF,00 - Static, values never changes between logs
|
||||
break;
|
||||
case 0x438: //55,55,01,F6,47,2E,10,D9 - 0x10D9 = 4313
|
||||
//55,55,01,F6,47,FD,0F,0B //407.5V 18deg
|
||||
//55,55,01,F6,47,15,10,F2 //408.5V 14deg
|
||||
break;
|
||||
case 0x43A: //7E,0A,B0,1C,63,E1,03,64
|
||||
//7E,0A,E0,1E,63,E1,03,32 //407.5V 18deg
|
||||
//7E,0A,66,1C,63,E1,03,AE //408.5V 14deg
|
||||
break;
|
||||
case 0x43B: //01,3B,06,39,FF,64,64,BD
|
||||
//01,3B,06,38,FF,64,64,BE
|
||||
break;
|
||||
case 0x43C: // Daughterboard temperatures reside in this CAN message
|
||||
if (rx_frame.data.u8[0] == 0x00) {
|
||||
daughterboard_temperatures[0] = (rx_frame.data.u8[1] - 40);
|
||||
daughterboard_temperatures[1] = (rx_frame.data.u8[2] - 40);
|
||||
daughterboard_temperatures[2] = (rx_frame.data.u8[3] - 40);
|
||||
daughterboard_temperatures[3] = (rx_frame.data.u8[4] - 40);
|
||||
daughterboard_temperatures[4] = (rx_frame.data.u8[5] - 40);
|
||||
daughterboard_temperatures[5] = (rx_frame.data.u8[6] - 40);
|
||||
}
|
||||
if (rx_frame.data.u8[0] == 0x01) {
|
||||
daughterboard_temperatures[6] = (rx_frame.data.u8[1] - 40);
|
||||
daughterboard_temperatures[7] = (rx_frame.data.u8[2] - 40);
|
||||
daughterboard_temperatures[8] = (rx_frame.data.u8[3] - 40);
|
||||
daughterboard_temperatures[9] = (rx_frame.data.u8[4] - 40);
|
||||
}
|
||||
break;
|
||||
case 0x43D: //Varies a lot
|
||||
break;
|
||||
case 0x444: //9E,01,88,13,64,64,98,65
|
||||
//9A,01,B6,13,64,64,98,3B //407.5V 18deg
|
||||
//9B,01,B8,13,64,64,98,38 //408.5V 14deg
|
||||
break;
|
||||
case 0x445: //00,98,FF,FF,63,20,4E,98 - Static, values never changes between logs
|
||||
break;
|
||||
case 0x446: //2C,D4,0C,4D,21,DC,0C,9D - 0,1,7th frame varies a lot
|
||||
break;
|
||||
case 0x447: // Seems to contain more temperatures, highest and lowest?
|
||||
//06,38,01,3B,E0,03,39,69
|
||||
//06,36,02,36,E0,03,36,72,
|
||||
lowest_temperature = (rx_frame.data.u8[1] - 40); //Best guess for now
|
||||
highest_temperature = (rx_frame.data.u8[3] - 40); //Best guess for now
|
||||
break;
|
||||
case 0x47B: //01,FF,FF,FF,FF,FF,FF,FF - Static, values never changes between logs
|
||||
break;
|
||||
case 0x524: //24,40,00,00,00,00,00,9B - Static, values never changes between logs
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; // Let system know battery is sending CAN
|
||||
|
||||
//This message transmits every 5?seconds. Seems like suitable place to poll for a PID
|
||||
ESP32Can.CANWriteFrame(&ATTO_3_7E7_POLL);
|
||||
|
||||
switch (ATTO_3_7E7_POLL.data.u8[3]) {
|
||||
case POLL_FOR_BATTERY_SOC:
|
||||
ATTO_3_7E7_POLL.data.u8[3] = POLL_FOR_BATTERY_VOLTAGE;
|
||||
break;
|
||||
case POLL_FOR_BATTERY_VOLTAGE:
|
||||
ATTO_3_7E7_POLL.data.u8[3] = POLL_FOR_BATTERY_CURRENT;
|
||||
break;
|
||||
case POLL_FOR_BATTERY_CURRENT:
|
||||
ATTO_3_7E7_POLL.data.u8[3] = POLL_FOR_LOWEST_TEMP_CELL;
|
||||
break;
|
||||
case POLL_FOR_LOWEST_TEMP_CELL:
|
||||
ATTO_3_7E7_POLL.data.u8[3] = POLL_FOR_HIGHEST_TEMP_CELL;
|
||||
break;
|
||||
case POLL_FOR_HIGHEST_TEMP_CELL:
|
||||
ATTO_3_7E7_POLL.data.u8[3] = POLL_FOR_BATTERY_PACK_AVG_TEMP;
|
||||
break;
|
||||
case POLL_FOR_BATTERY_PACK_AVG_TEMP:
|
||||
ATTO_3_7E7_POLL.data.u8[3] = POLL_FOR_BATTERY_CELL_MV_MAX;
|
||||
break;
|
||||
case POLL_FOR_BATTERY_CELL_MV_MAX:
|
||||
ATTO_3_7E7_POLL.data.u8[3] = POLL_FOR_BATTERY_CELL_MV_MIN;
|
||||
break;
|
||||
case POLL_FOR_BATTERY_CELL_MV_MIN:
|
||||
ATTO_3_7E7_POLL.data.u8[3] = POLL_FOR_BATTERY_VOLTAGE;
|
||||
break;
|
||||
default: //Something went wrong with logic, request voltage
|
||||
ATTO_3_7E7_POLL.data.u8[3] = POLL_FOR_BATTERY_VOLTAGE;
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
case 0x7EF: //OBD2 PID reply from battery
|
||||
switch (rx_frame.data.u8[3]) {
|
||||
case POLL_FOR_BATTERY_SOC:
|
||||
BMS_SOC = rx_frame.data.u8[4];
|
||||
break;
|
||||
case POLL_FOR_BATTERY_VOLTAGE:
|
||||
BMS_voltage = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4];
|
||||
break;
|
||||
case POLL_FOR_BATTERY_CURRENT:
|
||||
BMS_current = ((rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4]) - 5000;
|
||||
break;
|
||||
case POLL_FOR_LOWEST_TEMP_CELL:
|
||||
BMS_lowest_cell_temperature = (rx_frame.data.u8[4] - 40);
|
||||
break;
|
||||
case POLL_FOR_HIGHEST_TEMP_CELL:
|
||||
BMS_highest_cell_temperature = (rx_frame.data.u8[4] - 40);
|
||||
break;
|
||||
case POLL_FOR_BATTERY_PACK_AVG_TEMP:
|
||||
BMS_average_cell_temperature = (rx_frame.data.u8[4] - 40);
|
||||
break;
|
||||
case POLL_FOR_BATTERY_CELL_MV_MAX:
|
||||
BMS_highest_cell_voltage_mV = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4];
|
||||
break;
|
||||
case POLL_FOR_BATTERY_CELL_MV_MIN:
|
||||
BMS_lowest_cell_voltage_mV = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4];
|
||||
break;
|
||||
default: //Unrecognized reply
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
void send_can_battery() {
|
||||
unsigned long currentMillis = millis();
|
||||
//Send 50ms message
|
||||
if (currentMillis - previousMillis50 >= INTERVAL_50_MS) {
|
||||
// Check if sending of CAN messages has been delayed too much.
|
||||
if ((currentMillis - previousMillis50 >= INTERVAL_50_MS_DELAYED) && (currentMillis > BOOTUP_TIME)) {
|
||||
set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis50));
|
||||
} else {
|
||||
clear_event(EVENT_CAN_OVERRUN);
|
||||
}
|
||||
previousMillis50 = currentMillis;
|
||||
|
||||
counter_50ms++;
|
||||
if (counter_50ms > 23) {
|
||||
ATTO_3_12D.data.u8[2] = 0x00; // Goes from 02->00
|
||||
ATTO_3_12D.data.u8[3] = 0x22; // Goes from A0->22
|
||||
ATTO_3_12D.data.u8[5] = 0x31; // Goes from 71->31
|
||||
// TODO: handle more variations after more seconds have passed if needed
|
||||
}
|
||||
|
||||
// Update the counters in frame 6 & 7 (they are not in sync)
|
||||
if (frame6_counter == 0x0) {
|
||||
frame6_counter = 0xF; // Reset to 0xF after reaching 0x0
|
||||
} else {
|
||||
frame6_counter--; // Decrement the counter
|
||||
}
|
||||
if (frame7_counter == 0x0) {
|
||||
frame7_counter = 0xF; // Reset to 0xF after reaching 0x0
|
||||
} else {
|
||||
frame7_counter--; // Decrement the counter
|
||||
}
|
||||
|
||||
ATTO_3_12D.data.u8[6] = (0x0F | (frame6_counter << 4));
|
||||
ATTO_3_12D.data.u8[7] = (0x09 | (frame7_counter << 4));
|
||||
|
||||
ESP32Can.CANWriteFrame(&ATTO_3_12D);
|
||||
}
|
||||
// Send 100ms CAN Message
|
||||
if (currentMillis - previousMillis100 >= INTERVAL_100_MS) {
|
||||
previousMillis100 = currentMillis;
|
||||
|
||||
counter_100ms++;
|
||||
|
||||
if (counter_100ms > 3) {
|
||||
ATTO_3_411.data.u8[5] = 0x01;
|
||||
ATTO_3_411.data.u8[7] = 0xF5;
|
||||
}
|
||||
|
||||
ESP32Can.CANWriteFrame(&ATTO_3_411);
|
||||
}
|
||||
}
|
||||
|
||||
void setup_battery(void) { // Performs one time setup at startup
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("BYD Atto 3 battery selected");
|
||||
#endif
|
||||
|
||||
datalayer.battery.info.max_design_voltage_dV = 4410; // Over this charging is not possible
|
||||
datalayer.battery.info.min_design_voltage_dV = 3800; // Under this discharging is disabled
|
||||
}
|
||||
|
||||
#endif
|
12
Software/src/battery/BYD-ATTO-3-BATTERY.h
Normal file
12
Software/src/battery/BYD-ATTO-3-BATTERY.h
Normal file
|
@ -0,0 +1,12 @@
|
|||
#ifndef ATTO_3_BATTERY_H
|
||||
#define ATTO_3_BATTERY_H
|
||||
#include <Arduino.h>
|
||||
#include "../include.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
|
||||
#define BATTERY_SELECTED
|
||||
#define MAX_CELL_DEVIATION_MV 150
|
||||
|
||||
void setup_battery(void);
|
||||
|
||||
#endif
|
233
Software/src/battery/CHADEMO-BATTERY-INTERNAL.h
Normal file
233
Software/src/battery/CHADEMO-BATTERY-INTERNAL.h
Normal file
|
@ -0,0 +1,233 @@
|
|||
#ifndef CHADEMO_BATTERY_TYPES_H
|
||||
#define CHADEMO_BATTERY_TYPES_H
|
||||
|
||||
#define MAX_EVSE_POWER_CHARGING 3300
|
||||
#define MAX_EVSE_OUTPUT_VOLTAGE 410
|
||||
#define MAX_EVSE_OUTPUT_CURRENT 11
|
||||
|
||||
enum CHADEMO_STATE {
|
||||
CHADEMO_FAULT,
|
||||
CHADEMO_STOP,
|
||||
CHADEMO_IDLE,
|
||||
CHADEMO_CONNECTED,
|
||||
CHADEMO_INIT, // intermediate state indicating CAN from Vehicle not yet received after connection
|
||||
CHADEMO_NEGOTIATE,
|
||||
CHADEMO_EV_ALLOWED,
|
||||
CHADEMO_EVSE_PREPARE,
|
||||
CHADEMO_EVSE_START,
|
||||
CHADEMO_EVSE_CONTACTORS_ENABLED,
|
||||
CHADEMO_POWERFLOW,
|
||||
};
|
||||
|
||||
enum Mode { CHADEMO_CHARGE, CHADEMO_DISCHARGE, CHADEMO_BIDIRECTIONAL };
|
||||
|
||||
/* Charge/discharge sequence, indicating applicable V2H guideline
|
||||
* If sequence number is not agreed upon via H201/H209 between EVSE and Vehicle,
|
||||
* V2H 1.1 is assumed, which..is somehow between 0x0 and 0x1 ? TODO: better understanding here
|
||||
* Use CHADEMO_seq to decide whether emitting 209 is necessary
|
||||
* 0x0 1.0 and earlier
|
||||
* 0x1 2.0 appendix A
|
||||
* 0x2 2.0 appendix B
|
||||
* TODO: is this influenced by x109->CHADEMO_protocol_number, or x102->ControlProtocolNumberEV ??
|
||||
* Unused for now.
|
||||
uint8_t CHADEMO_seq = 0x0;
|
||||
*/
|
||||
|
||||
/*----------- CHARGING SUPPORT V2X --------------------------------------------------------------*/
|
||||
/* ---------- VEHICLE Data structures */
|
||||
|
||||
//H100 - Vehicle - Minimum charging expectations
|
||||
//TODO decide whether default values for vehicle-origin frames is even appropriate
|
||||
struct x100_Vehicle_Charging_Limits {
|
||||
uint8_t MinimumChargeCurrent = 0;
|
||||
uint16_t MinimumBatteryVoltage = 300;
|
||||
uint16_t MaximumBatteryVoltage = 402;
|
||||
uint8_t ConstantOfChargingRateIndication = 0;
|
||||
};
|
||||
//H101 - Vehicle - Maximum charging expectations
|
||||
struct x101_Vehicle_Charging_Estimate {
|
||||
uint8_t MaxChargingTime10sBit = 0;
|
||||
uint8_t MaxChargingTime1minBit = 0;
|
||||
uint8_t EstimatedChargingTime = 0;
|
||||
uint16_t RatedBatteryCapacity = 0;
|
||||
};
|
||||
|
||||
//H102 - Vehicle - Charging targets and Status
|
||||
// peer to x109 from EVSE
|
||||
// termination triggers in both
|
||||
// TODO see also Table A.26—Charge control termination command patterns
|
||||
struct x102_Vehicle_Charging_Session { //Frame byte
|
||||
uint8_t ControlProtocolNumberEV = 0; // 0
|
||||
uint16_t TargetBatteryVoltage = 0; // 1-2
|
||||
uint8_t ChargingCurrentRequest = 0; // 3 Note: per spec, units for this changed from kWh --> %
|
||||
|
||||
union {
|
||||
uint8_t faults;
|
||||
struct {
|
||||
bool unused_3 : 1;
|
||||
bool unused_2 : 1;
|
||||
bool unused_1 : 1;
|
||||
bool FaultBatteryVoltageDeviation : 1; // 4
|
||||
bool FaultHighBatteryTemperature : 1; // 3
|
||||
bool FaultBatteryCurrentDeviation : 1; // 2
|
||||
bool FaultBatteryUnderVoltage : 1; // 1
|
||||
bool FaultBatteryOverVoltage : 1; // 0
|
||||
} fault;
|
||||
} f;
|
||||
|
||||
union {
|
||||
uint8_t packed;
|
||||
struct {
|
||||
bool StatusVehicleDischargeCompatible : 1; //5.7
|
||||
bool unused_2 : 1; //5.6
|
||||
bool unused_1 : 1; //5.5
|
||||
bool StatusNormalStopRequest : 1; //5.4
|
||||
bool StatusVehicle : 1; //5.3
|
||||
bool StatusChargingError : 1; //5.2
|
||||
bool StatusVehicleShifterPosition : 1; //5.1
|
||||
bool StatusVehicleChargingEnabled : 1; //5.0 - bit zero is TODO. Vehicle charging enabled ==1 *AND* charge
|
||||
// permission signal k needs to be active for charging to be
|
||||
// permitted -- TODO document bits per byte for these flags
|
||||
// and update variables to be more appropriate
|
||||
} status;
|
||||
} s;
|
||||
|
||||
uint8_t StateOfCharge = 0; //6 state of charge?
|
||||
};
|
||||
|
||||
/* ---------- CHARGING: EVSE Data structures */
|
||||
struct x108_EVSE_Capabilities { // Frame byte
|
||||
bool contactor_weld_detection = 1; // 0
|
||||
uint16_t available_output_voltage = MAX_EVSE_OUTPUT_VOLTAGE; // 1,2
|
||||
uint8_t available_output_current = MAX_EVSE_OUTPUT_CURRENT; // 3
|
||||
uint16_t threshold_voltage = 297; // 4,5 voltage that EVSE will stop if car fails to
|
||||
// perhaps vehicle minus 3%, hardcoded initially to 96*2.95
|
||||
// 6,7 = unused
|
||||
};
|
||||
|
||||
/* Does double duty for charging and discharging */
|
||||
struct x109_EVSE_Status { // Frame byte
|
||||
uint8_t CHADEMO_protocol_number = 0x02; // 0
|
||||
uint16_t setpoint_HV_VDC =
|
||||
0; // 1,2 NOTE: charger_setpoint_HV_VDC elsewhere is a float. THIS is protocol-defined as an int. cast float->int and lose some precision for protocol adherence
|
||||
uint8_t setpoint_HV_IDC = 0; // 3
|
||||
//
|
||||
bool discharge_compatible = true; // 4, bit 0. bits
|
||||
// 4, bit 7-6 (?) unused. spec typo? maybe 1-7 unused
|
||||
union {
|
||||
uint8_t packed;
|
||||
struct {
|
||||
bool EVSE_status : 1; // 5, bit 0
|
||||
bool EVSE_error : 1; // 5, bit 1
|
||||
bool connector_locked : 1; // 5, bit 2 //NOTE: treated as connector_lock during discharge, but
|
||||
// seen as 'energizing' during charging mode
|
||||
|
||||
bool battery_incompatible : 1; // 5, bit 3
|
||||
bool ChgDischError : 1; // 5, bit 4
|
||||
|
||||
bool ChgDischStopControl : 1; // 5, bit 5 - set to false for initialization to indicate 'preparing to charge'
|
||||
// set to false when ready to charge/discharge
|
||||
|
||||
} status;
|
||||
} s;
|
||||
|
||||
// Either, or; not both.
|
||||
// seconds field set to 0xFF by default
|
||||
// indicating only the minutes field is used instead
|
||||
// BOTH observed initially set to 0xFF in logs, so use
|
||||
// that as the initialzed value
|
||||
uint8_t remaining_time_10s = 0xFF; // 6
|
||||
uint8_t remaining_time_1m = 0xFF; // 7
|
||||
};
|
||||
|
||||
/*----------- DISCHARGING SUPPORT V2X --------------------------------------------------------------*/
|
||||
/* ---------- VEHICLE Data structures */
|
||||
//H200 - Vehicle - Discharge limits
|
||||
struct x200_Vehicle_Discharge_Limits {
|
||||
uint8_t MaximumDischargeCurrent = 0xFF;
|
||||
uint16_t MinimumDischargeVoltage = 0;
|
||||
uint16_t MinimumBatteryDischargeLevel = 0;
|
||||
uint16_t MaxRemainingCapacityForCharging = 0;
|
||||
};
|
||||
|
||||
/* TODO When charge/discharge sequence control number (ID201/209) is not received, the vehicle or the EVSE
|
||||
should determine that the other is the EVSE or the vehicle of the model before the V2H guideline 1.1. */
|
||||
//H201 - Vehicle - Estimated capacity available
|
||||
// Intended primarily for display purposes.
|
||||
// Peer to H209
|
||||
// NOTE: in available CAN logs from a Leaf, 209 is sent with no 201 reply, so < 1.1 must be the inferred version
|
||||
struct x201_Vehicle_Discharge_Estimate {
|
||||
uint8_t V2HchargeDischargeSequenceNum = 0;
|
||||
uint16_t ApproxDischargeCompletionTime = 0;
|
||||
uint16_t AvailableVehicleEnergy = 0;
|
||||
};
|
||||
|
||||
/* ---------- EVSE Data structures */
|
||||
struct x208_EVSE_Discharge_Capability { // Frame byte
|
||||
uint8_t present_discharge_current = 0xFF; // 0
|
||||
uint16_t available_input_voltage = 500; // 1,2 -- poorly named as both 'available' and minimum input voltage
|
||||
uint16_t available_input_current = 250; // 3 -- poorly named as both 'available' and maximum input current
|
||||
// spec idiosyncracy in naming/description
|
||||
// 4,5 = unused
|
||||
uint16_t lower_threshold_voltage = 0; // 6,7
|
||||
};
|
||||
|
||||
// H209 - EVSE - Estimated Discharge Duration
|
||||
// peer to Vehicle's 201 event (Note: 209 seen
|
||||
// in CAN logs even when 201 is not)
|
||||
struct x209_EVSE_Discharge_Estimate { // Frame byte
|
||||
uint8_t sequence_control_number = 0x2; // 0
|
||||
uint16_t remaining_discharge_time = 0x0000; // 0x0000 == unused
|
||||
};
|
||||
|
||||
/*----------- DYNAMIC CONTROL SUPPORT --------------------------------------------------------------*/
|
||||
/* ---------- VEHICLE Data structures */
|
||||
struct x110_Vehicle_Dynamic_Control { //Frame byte
|
||||
union {
|
||||
uint8_t packed;
|
||||
struct {
|
||||
bool PermissionResetMaxChgTime : 1; // bit 5 or 6? is this only x118 not x110?
|
||||
bool unused_3 : 1;
|
||||
bool unused_2 : 1;
|
||||
bool unused_1 : 1;
|
||||
bool HighVoltageControlStatus : 1; // bit 2 = High voltage control support
|
||||
bool HighCurrentControlStatus : 1; // bit 1 = High current control support
|
||||
// rate of change is -20A/s to 20A/s relative to 102.3
|
||||
bool DynamicControlStatus : 1; // bit 0 = Dynamic Control support
|
||||
} status;
|
||||
} u;
|
||||
};
|
||||
|
||||
/* ---------- EVSE Data structures */
|
||||
// TODO 118
|
||||
//H118
|
||||
//see also table a.59 page 104 IEEE
|
||||
struct x118_EVSE_Dynamic_Control { // Frame byte
|
||||
union {
|
||||
uint8_t packed;
|
||||
struct {
|
||||
bool PermissionResetMaxChgTime : 1; // bit 5 or 6?
|
||||
bool unused_3 : 1;
|
||||
bool unused_2 : 1;
|
||||
bool unused_1 : 1;
|
||||
bool HighVoltageControlStatus : 1; // bit 2 = High voltage control support
|
||||
bool HighCurrentControlStatus : 1; // bit 1 = High current control support
|
||||
// rate of change is -20A/s to 20A/s relative to 102.3
|
||||
bool DynamicControlStatus : 1; // bit 0 = Dynamic Control support
|
||||
} status;
|
||||
} u;
|
||||
};
|
||||
|
||||
/*----------- MANUFACTURER ID SUPPORT --------------------------------------------------------------*/
|
||||
/* ---------- VEHICLE Data structures */
|
||||
//H700 - Vehicle - Manufacturer identification
|
||||
//Peer to H708
|
||||
//Used to adapt to manufacturer-prescribed optional specification
|
||||
struct x700_Vehicle_Vendor_ID {
|
||||
uint8_t AutomakerCode = 0; // 0 = set to 0x0 to indicate incompatibility. Best as a starting place
|
||||
uint8_t OptionalContent = 0; // 1-7, variable per vendor spec
|
||||
};
|
||||
|
||||
void handle_chademo_sequence();
|
||||
|
||||
#endif
|
File diff suppressed because it is too large
Load diff
|
@ -5,6 +5,14 @@
|
|||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
|
||||
#define BATTERY_SELECTED
|
||||
#define MAX_CELL_DEVIATION_MV 9999
|
||||
|
||||
//Contactor control is required for CHADEMO support
|
||||
#define CONTACTOR_CONTROL
|
||||
|
||||
//ISA shunt is currently required for CHADEMO support
|
||||
// other measurement sources may be added in the future
|
||||
#define ISA_SHUNT
|
||||
|
||||
void setup_battery(void);
|
||||
|
||||
|
|
354
Software/src/battery/CHADEMO-SHUNTS.cpp
Normal file
354
Software/src/battery/CHADEMO-SHUNTS.cpp
Normal file
|
@ -0,0 +1,354 @@
|
|||
/* Portions of this file are an adaptation of the SimpleISA library, originally authored by Jack Rickard.
|
||||
*
|
||||
* At present, this code supports the Scale IVT Modular current/voltage sensor device.
|
||||
* These devices measure current, up to three voltages, and provide temperature compensation.
|
||||
* Additional sensors are planned to provide flexibility/lower BOM costs.
|
||||
*
|
||||
* Original license/copyright header of SimpleISA is shown below:
|
||||
* This library was written by Jack Rickard of EVtv - http://www.evtv.me
|
||||
* copyright 2014
|
||||
* You are licensed to use this library for any purpose, commercial or private,
|
||||
* without restriction.
|
||||
*
|
||||
* 2024 - Modified to make use of ESP32-Arduino-CAN by miwagner
|
||||
*
|
||||
*/
|
||||
#include "../include.h"
|
||||
#ifdef CHADEMO_BATTERY
|
||||
#include "../datalayer/datalayer.h"
|
||||
#include "../devboard/utils/events.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
#include "CHADEMO-BATTERY-INTERNAL.h"
|
||||
#include "CHADEMO-BATTERY.h"
|
||||
#include "CHADEMO-SHUNTS.h"
|
||||
|
||||
/* Initial frames received from ISA shunts provide invalid during initialization */
|
||||
static int framecount = 0;
|
||||
|
||||
/* original variables/names/types from SimpleISA. These warrant refinement */
|
||||
float Amperes; // Floating point with current in Amperes
|
||||
double AH; //Floating point with accumulated ampere-hours
|
||||
double KW;
|
||||
double KWH;
|
||||
|
||||
double Voltage;
|
||||
double Voltage1;
|
||||
double Voltage2;
|
||||
double Voltage3;
|
||||
double VoltageHI;
|
||||
double Voltage1HI;
|
||||
double Voltage2HI;
|
||||
double Voltage3HI;
|
||||
double VoltageLO;
|
||||
double Voltage1LO;
|
||||
double Voltage2LO;
|
||||
double Voltage3LO;
|
||||
|
||||
double Temperature;
|
||||
|
||||
bool firstframe;
|
||||
double milliamps;
|
||||
long watt;
|
||||
long As;
|
||||
long lastAs;
|
||||
long wh;
|
||||
long lastWh;
|
||||
|
||||
/* Output command frame used to alter or initialize ISA shunt behavior
|
||||
* Please note that all delay/sleep operations are solely in this section of code,
|
||||
* not used during normal operation. Such delays are currently commented out.
|
||||
*/
|
||||
CAN_frame_t outframe = {.FIR = {.B =
|
||||
{
|
||||
.DLC = 8,
|
||||
.unknown_2 = 0,
|
||||
.RTR = CAN_no_RTR,
|
||||
.FF = CAN_frame_std,
|
||||
}},
|
||||
|
||||
.MsgID = 0x411,
|
||||
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
|
||||
uint16_t get_measured_voltage() {
|
||||
return (uint16_t)Voltage;
|
||||
}
|
||||
|
||||
uint16_t get_measured_current() {
|
||||
return (uint16_t)Amperes;
|
||||
}
|
||||
|
||||
//This is our CAN interrupt service routine to catch inbound frames
|
||||
inline void ISA_handleFrame(CAN_frame_t* frame) {
|
||||
|
||||
if (frame->MsgID < 0x521 || frame->MsgID > 0x528) {
|
||||
return;
|
||||
}
|
||||
|
||||
framecount++;
|
||||
|
||||
switch (frame->MsgID) {
|
||||
case 0x511:
|
||||
break;
|
||||
|
||||
case 0x521:
|
||||
ISA_handle521(frame);
|
||||
break;
|
||||
|
||||
case 0x522:
|
||||
ISA_handle522(frame);
|
||||
break;
|
||||
|
||||
case 0x523:
|
||||
ISA_handle523(frame);
|
||||
break;
|
||||
|
||||
case 0x524:
|
||||
ISA_handle524(frame);
|
||||
break;
|
||||
|
||||
case 0x525:
|
||||
ISA_handle525(frame);
|
||||
break;
|
||||
|
||||
case 0x526:
|
||||
ISA_handle526(frame);
|
||||
break;
|
||||
|
||||
case 0x527:
|
||||
ISA_handle527(frame);
|
||||
break;
|
||||
|
||||
case 0x528:
|
||||
ISA_handle528(frame);
|
||||
break;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
//handle frame for Amperes
|
||||
inline void ISA_handle521(CAN_frame_t* frame) {
|
||||
long current = 0;
|
||||
current =
|
||||
(long)((frame->data.u8[5] << 24) | (frame->data.u8[4] << 16) | (frame->data.u8[3] << 8) | (frame->data.u8[2]));
|
||||
|
||||
milliamps = current;
|
||||
Amperes = current / 1000.0f;
|
||||
}
|
||||
|
||||
//handle frame for Voltage
|
||||
inline void ISA_handle522(CAN_frame_t* frame) {
|
||||
long volt =
|
||||
(long)((frame->data.u8[5] << 24) | (frame->data.u8[4] << 16) | (frame->data.u8[3] << 8) | (frame->data.u8[2]));
|
||||
|
||||
Voltage = volt / 1000.0f;
|
||||
Voltage1 = Voltage - (Voltage2 + Voltage3);
|
||||
|
||||
if (framecount < 150) {
|
||||
VoltageLO = Voltage;
|
||||
Voltage1LO = Voltage1;
|
||||
} else {
|
||||
if (Voltage < VoltageLO)
|
||||
VoltageLO = Voltage;
|
||||
if (Voltage > VoltageHI)
|
||||
VoltageHI = Voltage;
|
||||
if (Voltage1 < Voltage1LO)
|
||||
Voltage1LO = Voltage1;
|
||||
if (Voltage1 > Voltage1HI)
|
||||
Voltage1HI = Voltage1;
|
||||
}
|
||||
}
|
||||
|
||||
//handle frame for Voltage 2
|
||||
inline void ISA_handle523(CAN_frame_t* frame) {
|
||||
long volt =
|
||||
(long)((frame->data.u8[5] << 24) | (frame->data.u8[4] << 16) | (frame->data.u8[3] << 8) | (frame->data.u8[2]));
|
||||
|
||||
Voltage2 = volt / 1000.0f;
|
||||
if (Voltage2 > 3)
|
||||
Voltage2 -= Voltage3;
|
||||
|
||||
if (framecount < 150) {
|
||||
Voltage2LO = Voltage2;
|
||||
} else {
|
||||
if (Voltage2 < Voltage2LO)
|
||||
Voltage2LO = Voltage2;
|
||||
if (Voltage2 > Voltage2HI)
|
||||
Voltage2HI = Voltage2;
|
||||
}
|
||||
}
|
||||
|
||||
//handle frame for Voltage3
|
||||
inline void ISA_handle524(CAN_frame_t* frame) {
|
||||
long volt =
|
||||
(long)((frame->data.u8[5] << 24) | (frame->data.u8[4] << 16) | (frame->data.u8[3] << 8) | (frame->data.u8[2]));
|
||||
|
||||
Voltage3 = volt / 1000.0f;
|
||||
|
||||
if (framecount < 150) {
|
||||
Voltage3LO = Voltage3;
|
||||
} else {
|
||||
if (Voltage3 < Voltage3LO && Voltage3 > 10)
|
||||
Voltage3LO = Voltage3;
|
||||
if (Voltage3 > Voltage3HI)
|
||||
Voltage3HI = Voltage3;
|
||||
}
|
||||
}
|
||||
|
||||
//handle frame for Temperature
|
||||
inline void ISA_handle525(CAN_frame_t* frame) {
|
||||
long temp = 0;
|
||||
temp = (long)((frame->data.u8[5] << 24) | (frame->data.u8[4] << 16) | (frame->data.u8[3] << 8) | (frame->data.u8[2]));
|
||||
|
||||
Temperature = temp / 10;
|
||||
}
|
||||
|
||||
//handle frame for Kilowatts
|
||||
inline void ISA_handle526(CAN_frame_t* frame) {
|
||||
watt = 0;
|
||||
watt = (long)((frame->data.u8[5] << 24) | (frame->data.u8[4] << 16) | (frame->data.u8[3] << 8) | (frame->data.u8[2]));
|
||||
KW = watt / 1000.0f;
|
||||
}
|
||||
|
||||
//handle frame for Ampere-Hours
|
||||
inline void ISA_handle527(CAN_frame_t* frame) {
|
||||
As = 0;
|
||||
As = (frame->data.u8[5] << 24) | (frame->data.u8[4] << 16) | (frame->data.u8[3] << 8) | (frame->data.u8[2]);
|
||||
|
||||
AH += (As - lastAs) / 3600.0f;
|
||||
lastAs = As;
|
||||
}
|
||||
|
||||
//handle frame for kiloWatt-hours
|
||||
inline void ISA_handle528(CAN_frame_t* frame) {
|
||||
wh = (long)((frame->data.u8[5] << 24) | (frame->data.u8[4] << 16) | (frame->data.u8[3] << 8) | (frame->data.u8[2]));
|
||||
KWH += (wh - lastWh) / 1000.0f;
|
||||
lastWh = wh;
|
||||
}
|
||||
|
||||
/*
|
||||
void ISA_initialize() {
|
||||
firstframe=false;
|
||||
STOP();
|
||||
delay(700);
|
||||
for(int i=0;i<9;i++) {
|
||||
Serial.println("initialization \n");
|
||||
|
||||
outframe.data.u8[0]=(0x20+i);
|
||||
outframe.data.u8[1]=0x42;
|
||||
outframe.data.u8[2]=0x02;
|
||||
outframe.data.u8[3]=(0x60+(i*18));
|
||||
outframe.data.u8[4]=0x00;
|
||||
outframe.data.u8[5]=0x00;
|
||||
outframe.data.u8[6]=0x00;
|
||||
outframe.data.u8[7]=0x00;
|
||||
|
||||
ESP32Can.CANWriteFrame(&outframe);
|
||||
|
||||
delay(500);
|
||||
|
||||
sendSTORE();
|
||||
delay(500);
|
||||
}
|
||||
|
||||
START();
|
||||
delay(500);
|
||||
lastAs=As;
|
||||
lastWh=wh;
|
||||
|
||||
}
|
||||
|
||||
void ISA_STOP() {
|
||||
outframe.data.u8[0]=0x34;
|
||||
outframe.data.u8[1]=0x00;
|
||||
outframe.data.u8[2]=0x01;
|
||||
outframe.data.u8[3]=0x00;
|
||||
outframe.data.u8[4]=0x00;
|
||||
outframe.data.u8[5]=0x00;
|
||||
outframe.data.u8[6]=0x00;
|
||||
outframe.data.u8[7]=0x00;
|
||||
ESP32Can.CANWriteFrame(&outframe);
|
||||
|
||||
}
|
||||
|
||||
void ISA_sendSTORE() {
|
||||
outframe.data.u8[0]=0x32;
|
||||
outframe.data.u8[1]=0x00;
|
||||
outframe.data.u8[2]=0x00;
|
||||
outframe.data.u8[3]=0x00;
|
||||
outframe.data.u8[4]=0x00;
|
||||
outframe.data.u8[5]=0x00;
|
||||
outframe.data.u8[6]=0x00;
|
||||
outframe.data.u8[7]=0x00;
|
||||
ESP32Can.CANWriteFrame(&outframe);
|
||||
}
|
||||
|
||||
void ISA_START() {
|
||||
outframe.data.u8[0]=0x34;
|
||||
outframe.data.u8[1]=0x01;
|
||||
outframe.data.u8[2]=0x01;
|
||||
outframe.data.u8[3]=0x00;
|
||||
outframe.data.u8[4]=0x00;
|
||||
outframe.data.u8[5]=0x00;
|
||||
outframe.data.u8[6]=0x00;
|
||||
outframe.data.u8[7]=0x00;
|
||||
ESP32Can.CANWriteFrame(&outframe);
|
||||
}
|
||||
|
||||
void ISA_RESTART() {
|
||||
//Has the effect of zeroing AH and KWH
|
||||
outframe.data.u8[0]=0x3F;
|
||||
outframe.data.u8[1]=0x00;
|
||||
outframe.data.u8[2]=0x00;
|
||||
outframe.data.u8[3]=0x00;
|
||||
outframe.data.u8[4]=0x00;
|
||||
outframe.data.u8[5]=0x00;
|
||||
outframe.data.u8[6]=0x00;
|
||||
outframe.data.u8[7]=0x00;
|
||||
ESP32Can.CANWriteFrame(&outframe);
|
||||
}
|
||||
|
||||
void ISA_deFAULT() {
|
||||
//Returns module to original defaults
|
||||
outframe.data.u8[0]=0x3D;
|
||||
outframe.data.u8[1]=0x00;
|
||||
outframe.data.u8[2]=0x00;
|
||||
outframe.data.u8[3]=0x00;
|
||||
outframe.data.u8[4]=0x00;
|
||||
outframe.data.u8[5]=0x00;
|
||||
outframe.data.u8[6]=0x00;
|
||||
outframe.data.u8[7]=0x00;
|
||||
ESP32Can.CANWriteFrame(&outframe);
|
||||
}
|
||||
|
||||
void ISA_initCurrent() {
|
||||
STOP();
|
||||
delay(500);
|
||||
|
||||
Serial.println("initialization \n");
|
||||
|
||||
outframe.data.u8[0]=0x21;
|
||||
outframe.data.u8[1]=0x42;
|
||||
outframe.data.u8[2]=0x01;
|
||||
outframe.data.u8[3]=0x61;
|
||||
outframe.data.u8[4]=0x00;
|
||||
outframe.data.u8[5]=0x00;
|
||||
outframe.data.u8[6]=0x00;
|
||||
outframe.data.u8[7]=0x00;
|
||||
|
||||
ESP32Can.CANWriteFrame(&outframe);
|
||||
|
||||
delay(500);
|
||||
|
||||
sendSTORE();
|
||||
delay(500);
|
||||
|
||||
START();
|
||||
delay(500);
|
||||
lastAs=As;
|
||||
lastWh=wh;
|
||||
}
|
||||
*/
|
||||
|
||||
#endif
|
16
Software/src/battery/CHADEMO-SHUNTS.h
Normal file
16
Software/src/battery/CHADEMO-SHUNTS.h
Normal file
|
@ -0,0 +1,16 @@
|
|||
#ifndef CHADEMO_SHUNTS_H
|
||||
#define CHADEMO_SHUNTS_H
|
||||
|
||||
uint16_t get_measured_voltage();
|
||||
uint16_t get_measured_current();
|
||||
inline void ISA_handler(CAN_frame_t* frame);
|
||||
inline void ISA_handle521(CAN_frame_t* frame);
|
||||
inline void ISA_handle522(CAN_frame_t* frame);
|
||||
inline void ISA_handle523(CAN_frame_t* frame);
|
||||
inline void ISA_handle524(CAN_frame_t* frame);
|
||||
inline void ISA_handle525(CAN_frame_t* frame);
|
||||
inline void ISA_handle526(CAN_frame_t* frame);
|
||||
inline void ISA_handle527(CAN_frame_t* frame);
|
||||
inline void ISA_handle528(CAN_frame_t* frame);
|
||||
|
||||
#endif
|
|
@ -10,7 +10,6 @@
|
|||
//Figure out if CAN messages need to be sent to keep the system happy?
|
||||
|
||||
/* Do not change code below unless you are sure what you are doing */
|
||||
static uint8_t CANstillAlive = 12; //counter for checking if CAN is still alive
|
||||
static uint8_t errorCode = 0; //stores if we have an error code active from battery control logic
|
||||
static uint8_t BMU_Detected = 0;
|
||||
static uint8_t CMU_Detected = 0;
|
||||
|
@ -49,19 +48,10 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
datalayer.battery.status.remaining_capacity_Wh = static_cast<uint32_t>(
|
||||
(static_cast<double>(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh);
|
||||
|
||||
//We do not know if the max charge power is sent by the battery. So we estimate the value based on SOC%
|
||||
if (datalayer.battery.status.reported_soc == 10000) { //100.00%
|
||||
datalayer.battery.status.max_charge_power_W = 0; //When battery is 100% full, set allowed charge W to 0
|
||||
} else {
|
||||
datalayer.battery.status.max_charge_power_W = 10000; //Otherwise we can push 10kW into the pack!
|
||||
}
|
||||
//We do not know the max charge/discharge power is sent by the battery. We hardcode value for now.
|
||||
datalayer.battery.status.max_charge_power_W = 10000; // 10kW //TODO: Fix when CAN is decoded
|
||||
|
||||
if (datalayer.battery.status.reported_soc < 200) { //2.00%
|
||||
datalayer.battery.status.max_discharge_power_W =
|
||||
0; //When battery is empty (below 2%), set allowed discharge W to 0
|
||||
} else {
|
||||
datalayer.battery.status.max_discharge_power_W = 10000; //Otherwise we can discharge 10kW from the pack!
|
||||
}
|
||||
datalayer.battery.status.max_discharge_power_W = 10000; // 10kW //TODO: Fix when CAN is decoded
|
||||
|
||||
datalayer.battery.status.active_power_W = BMU_Power; //TODO: Scaling?
|
||||
|
||||
|
@ -108,14 +98,6 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
|
||||
datalayer.battery.status.temperature_min_dC = (int16_t)(max_temp_cel * 10);
|
||||
|
||||
/* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/
|
||||
if (!CANstillAlive) {
|
||||
set_event(EVENT_CAN_RX_FAILURE, 0);
|
||||
} else {
|
||||
CANstillAlive--;
|
||||
clear_event(EVENT_CAN_RX_FAILURE);
|
||||
}
|
||||
|
||||
if (!BMU_Detected) {
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("BMU not detected, check wiring!");
|
||||
|
@ -144,8 +126,8 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
}
|
||||
|
||||
void receive_can_battery(CAN_frame_t rx_frame) {
|
||||
CANstillAlive =
|
||||
12; //TODO: move this inside a known message ID to prevent CAN inverter from keeping battery alive detection going
|
||||
datalayer.battery.status.CAN_battery_still_alive =
|
||||
CAN_STILL_ALIVE; //TODO: move this inside a known message ID to prevent CAN inverter from keeping battery alive detection going
|
||||
switch (rx_frame.MsgID) {
|
||||
case 0x374: //BMU message, 10ms - SOC
|
||||
temp_value = ((rx_frame.data.u8[1] - 10) / 2);
|
||||
|
@ -206,6 +188,8 @@ void send_can_battery() {
|
|||
// Check if sending of CAN messages has been delayed too much.
|
||||
if ((currentMillis - previousMillis100 >= INTERVAL_100_MS_DELAYED) && (currentMillis > BOOTUP_TIME)) {
|
||||
set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis100));
|
||||
} else {
|
||||
clear_event(EVENT_CAN_OVERRUN);
|
||||
}
|
||||
previousMillis100 = currentMillis;
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
|
||||
#define BATTERY_SELECTED
|
||||
#define MAX_CELL_DEVIATION_MV 500
|
||||
|
||||
void setup_battery(void);
|
||||
|
||||
|
|
324
Software/src/battery/JAGUAR-IPACE-BATTERY.cpp
Normal file
324
Software/src/battery/JAGUAR-IPACE-BATTERY.cpp
Normal file
|
@ -0,0 +1,324 @@
|
|||
#include "../include.h"
|
||||
#ifdef JAGUAR_IPACE_BATTERY
|
||||
#include "../datalayer/datalayer.h"
|
||||
#include "../devboard/utils/events.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
#include "JAGUAR-IPACE-BATTERY.h"
|
||||
|
||||
/* Do not change code below unless you are sure what you are doing */
|
||||
static unsigned long previousMillis10 = 0; // will store last time a 10ms CAN Message was send
|
||||
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
|
||||
static unsigned long previousMillis500 = 0; // will store last time a 500ms CAN Message was send
|
||||
|
||||
static uint8_t HVBattAvgSOC = 0;
|
||||
static uint8_t HVBattFastChgCounter = 0;
|
||||
static uint8_t HVBattTempColdCellID = 0;
|
||||
static uint8_t HVBatTempHotCellID = 0;
|
||||
static uint8_t HVBattVoltMaxCellID = 0;
|
||||
static uint8_t HVBattVoltMinCellID = 0;
|
||||
static uint8_t HVBattPwerGPCS = 0;
|
||||
static uint8_t HVBattPwrGpCounter = 0;
|
||||
static int8_t HVBattCurrentTR = 0;
|
||||
static uint16_t HVBattCellVoltageMaxMv = 3700;
|
||||
static uint16_t HVBattCellVoltageMinMv = 3700;
|
||||
static uint16_t HVBattEnergyAvailable = 0;
|
||||
static uint16_t HVBattEnergyUsableMax = 0;
|
||||
static uint16_t HVBattTotalCapacityWhenNew = 0;
|
||||
static uint16_t HVBattDischargeContiniousPowerLimit = 0;
|
||||
static uint16_t HVBattDischargePowerLimitExt = 0;
|
||||
static uint16_t HVBattDischargeVoltageLimit = 0;
|
||||
static uint16_t HVBattVoltageExt = 0;
|
||||
static uint16_t HVBatteryVoltageOC = 0;
|
||||
static uint16_t HVBatteryChgCurrentLimit = 0;
|
||||
static uint16_t HVBattChargeContiniousPowerLimit = 0;
|
||||
static int16_t HVBattAverageTemperature = 0;
|
||||
static int16_t HVBattCellTempAverage = 0;
|
||||
static int16_t HVBattCellTempColdest = 0;
|
||||
static int16_t HVBattCellTempHottest = 0;
|
||||
static int16_t HVBattInletCoolantTemp = 0;
|
||||
static bool HVBatteryContactorStatus = false;
|
||||
static bool HVBatteryContactorStatusT = false;
|
||||
static bool HVBattHVILError = false;
|
||||
static bool HVILBattIsolationError = false;
|
||||
static bool HVIsolationTestStatus = false;
|
||||
|
||||
/* TODO: Actually use a proper keepalive message */
|
||||
CAN_frame_t ipace_keep_alive = {.FIR = {.B =
|
||||
{
|
||||
.DLC = 8,
|
||||
.FF = CAN_frame_std,
|
||||
}},
|
||||
.MsgID = 0x063,
|
||||
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
|
||||
CAN_frame_t ipace_7e4 = {.FIR = {.B =
|
||||
{
|
||||
.DLC = 8,
|
||||
.FF = CAN_frame_std,
|
||||
}},
|
||||
.MsgID = 0x7e4,
|
||||
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
|
||||
void print_units(char* header, int value, char* units) {
|
||||
Serial.print(header);
|
||||
Serial.print(value);
|
||||
Serial.print(units);
|
||||
}
|
||||
|
||||
void update_values_battery() {
|
||||
|
||||
datalayer.battery.status.real_soc = HVBattAvgSOC * 100; //Add two decimals
|
||||
|
||||
datalayer.battery.status.soh_pptt = 9900; //TODO: Map
|
||||
|
||||
datalayer.battery.status.voltage_dV = HVBattVoltageExt * 10; //TODO: This value OK?
|
||||
|
||||
datalayer.battery.status.current_dA = HVBattCurrentTR * 10; //TODO: This value OK?
|
||||
|
||||
datalayer.battery.info.total_capacity_Wh = HVBattEnergyUsableMax * 100; // kWh+1 to Wh
|
||||
|
||||
datalayer.battery.status.remaining_capacity_Wh = static_cast<uint32_t>(
|
||||
(static_cast<double>(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh);
|
||||
|
||||
datalayer.battery.status.cell_max_voltage_mV = HVBattCellVoltageMaxMv;
|
||||
|
||||
datalayer.battery.status.cell_min_voltage_mV = HVBattCellVoltageMinMv;
|
||||
|
||||
//Power in watts, Negative = charging batt
|
||||
datalayer.battery.status.active_power_W =
|
||||
((datalayer.battery.status.voltage_dV * datalayer.battery.status.current_dA) / 100);
|
||||
|
||||
datalayer.battery.status.temperature_min_dC = HVBattCellTempColdest * 10; // C to dC
|
||||
|
||||
datalayer.battery.status.temperature_max_dC = HVBattCellTempHottest * 10; // C to dC
|
||||
|
||||
datalayer.battery.status.max_discharge_power_W =
|
||||
HVBattDischargeContiniousPowerLimit * 10; // kWh+2 to W (TODO: Check that scaling is right way)
|
||||
|
||||
datalayer.battery.status.max_charge_power_W =
|
||||
HVBattChargeContiniousPowerLimit * 10; // kWh+2 to W (TODO: Check that scaling is right way)
|
||||
|
||||
if (HVBattHVILError) { // Alert user incase the high voltage interlock is not OK
|
||||
set_event(EVENT_HVIL_FAILURE, 0);
|
||||
} else {
|
||||
clear_event(EVENT_HVIL_FAILURE);
|
||||
}
|
||||
|
||||
if (HVILBattIsolationError) { // Alert user incase battery reports isolation error
|
||||
set_event(EVENT_BATTERY_ISOLATION, 0);
|
||||
} else {
|
||||
clear_event(EVENT_BATTERY_ISOLATION);
|
||||
}
|
||||
|
||||
/*Finally print out values to serial if configured to do so*/
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("Values going to inverter");
|
||||
print_units("SOH%: ", (datalayer.battery.status.soh_pptt * 0.01), "% ");
|
||||
print_units(", SOC%: ", (datalayer.battery.status.reported_soc * 0.01), "% ");
|
||||
print_units(", Voltage: ", (datalayer.battery.status.voltage_dV * 0.1), "V ");
|
||||
print_units(", Max discharge power: ", datalayer.battery.status.max_discharge_power_W, "W ");
|
||||
print_units(", Max charge power: ", datalayer.battery.status.max_charge_power_W, "W ");
|
||||
print_units(", Max temp: ", (datalayer.battery.status.temperature_max_dC * 0.1), "°C ");
|
||||
print_units(", Min temp: ", (datalayer.battery.status.temperature_min_dC * 0.1), "°C ");
|
||||
print_units(", Max cell voltage: ", datalayer.battery.status.cell_max_voltage_mV, "mV ");
|
||||
print_units(", Min cell voltage: ", datalayer.battery.status.cell_min_voltage_mV, "mV ");
|
||||
Serial.println("");
|
||||
#endif
|
||||
}
|
||||
|
||||
void receive_can_battery(CAN_frame_t rx_frame) {
|
||||
|
||||
// Do not log noisy startup messages - there are many !
|
||||
if (rx_frame.MsgID == 0 && rx_frame.FIR.B.DLC == 8 && rx_frame.data.u8[0] == 0 && rx_frame.data.u8[1] == 0 &&
|
||||
rx_frame.data.u8[2] == 0 && rx_frame.data.u8[3] == 0 && rx_frame.data.u8[4] == 0 && rx_frame.data.u8[5] == 0 &&
|
||||
rx_frame.data.u8[6] == 0x80 && rx_frame.data.u8[7] == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (rx_frame.MsgID) { // These messages are periodically transmitted by the battery
|
||||
case 0x080:
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
HVBatteryContactorStatus = ((rx_frame.data.u8[0] & 0x80) >> 7);
|
||||
HVBattHVILError = ((rx_frame.data.u8[0] & 0x40) >> 6);
|
||||
HVILBattIsolationError = ((rx_frame.data.u8[0] & 0x20) >> 5);
|
||||
break;
|
||||
case 0x100:
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
HVBattDischargeContiniousPowerLimit =
|
||||
((rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]); // 0x3269 = 12905 = 129.05kW
|
||||
HVBattDischargePowerLimitExt = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); // 0x7BD5 = 31701 = 317.01kW
|
||||
HVBattDischargeVoltageLimit =
|
||||
((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]); // Lowest voltage the pack can go to
|
||||
break;
|
||||
case 0x102:
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
HVBattPwrGpCounter = ((rx_frame.data.u8[1] & 0x3C) >> 2); // Loops 0-F-0
|
||||
HVBattPwerGPCS = rx_frame.data.u8[0]; // SAE J1850 CRC8 Checksum.
|
||||
//TODO: Add function that checks if CRC is correct. We can use this to detect corrupted CAN messages
|
||||
//HVBattCurrentExt = //Used only on 2018+
|
||||
HVBattVoltageExt = (((rx_frame.data.u8[1] & 0x03) << 8) | rx_frame.data.u8[2]);
|
||||
break;
|
||||
case 0x104:
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
HVBatteryContactorStatusT = ((rx_frame.data.u8[2] & 0x80) >> 7);
|
||||
HVIsolationTestStatus = ((rx_frame.data.u8[2] & 0x10) >> 4);
|
||||
HVBatteryVoltageOC = (((rx_frame.data.u8[2] & 0x03) << 8) | rx_frame.data.u8[3]);
|
||||
HVBatteryChgCurrentLimit = (((rx_frame.data.u8[6] & 0x03) << 8) | rx_frame.data.u8[7]);
|
||||
break;
|
||||
case 0x10A:
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
HVBattChargeContiniousPowerLimit = ((rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]);
|
||||
break;
|
||||
case 0x198:
|
||||
break;
|
||||
case 0x1C4:
|
||||
HVBattCurrentTR = rx_frame.data.u8[0]; //TODO: scaling?
|
||||
//HVBattPwrExtGPCounter = (rx_frame.data.u8[2] & 0xF0) >> 4; // not needed
|
||||
//HVBattPwrExtGPCS = rx_frame.data.u8[1]; // Checksum, not needed
|
||||
//HVBattVoltageBusTF Frame 3/4/5?
|
||||
//HVBattVoltageBusTR Frame 3/4/5?
|
||||
break;
|
||||
case 0x220:
|
||||
break;
|
||||
case 0x222:
|
||||
HVBattAvgSOC = rx_frame.data.u8[4];
|
||||
HVBattAverageTemperature = (rx_frame.data.u8[5] / 2) - 40;
|
||||
HVBattFastChgCounter = rx_frame.data.u8[7];
|
||||
//HVBattLogEvent
|
||||
HVBattTempColdCellID = rx_frame.data.u8[0];
|
||||
HVBatTempHotCellID = rx_frame.data.u8[1];
|
||||
HVBattVoltMaxCellID = rx_frame.data.u8[2];
|
||||
HVBattVoltMinCellID = rx_frame.data.u8[3];
|
||||
break;
|
||||
case 0x248:
|
||||
break;
|
||||
case 0x308:
|
||||
break;
|
||||
case 0x424:
|
||||
HVBattCellVoltageMaxMv = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
|
||||
HVBattCellVoltageMinMv = ((rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]);
|
||||
//HVBattHeatPowerGenChg // kW
|
||||
//HVBattHeatPowerGenDcg // kW
|
||||
//HVBattWarmupRateChg // degC/minute
|
||||
//HVBattWarmupRateDcg // degC/minute
|
||||
break;
|
||||
case 0x448:
|
||||
HVBattCellTempAverage = (rx_frame.data.u8[0] / 2) - 40;
|
||||
HVBattCellTempColdest = (rx_frame.data.u8[1] / 2) - 40;
|
||||
HVBattCellTempHottest = (rx_frame.data.u8[2] / 2) - 40;
|
||||
//HVBattCIntPumpDiagStat // Pump OK / NOK
|
||||
//HVBattCIntPumpDiagStat_UB // True/False
|
||||
//HVBattCoolantLevel // Coolant level OK / NOK
|
||||
//HVBattHeaterCtrlStat // Off / On
|
||||
HVBattInletCoolantTemp = (rx_frame.data.u8[5] / 2) - 40;
|
||||
//HVBattInlentCoolantTemp_UB // True/False
|
||||
//HVBattMILRequested // True/False
|
||||
//HVBattThermalOvercheck // OK / NOK
|
||||
break;
|
||||
case 0x449:
|
||||
break;
|
||||
case 0x464:
|
||||
HVBattEnergyAvailable =
|
||||
((rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]) / 2; // 0x0198 = 408 / 2 = 204 = 20.4kWh
|
||||
HVBattEnergyUsableMax =
|
||||
((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]) / 2; // 0x06EA = 1770 / 2 = 885 = 88.5kWh
|
||||
HVBattTotalCapacityWhenNew = ((rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]); //0x0395 = 917 = 91.7kWh
|
||||
break;
|
||||
case 0x522:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Discard non-interesting can messages so they do not get logged via serial
|
||||
if (rx_frame.MsgID < 0x500) {
|
||||
return;
|
||||
}
|
||||
|
||||
// All CAN messages recieved will be logged via serial
|
||||
Serial.print(millis()); // Example printout, time, ID, length, data: 7553 1DB 8 FF C0 B9 EA 0 0 2 5D
|
||||
Serial.print(" ");
|
||||
Serial.print(rx_frame.MsgID, HEX);
|
||||
Serial.print(" ");
|
||||
Serial.print(rx_frame.FIR.B.DLC);
|
||||
Serial.print(" ");
|
||||
for (int i = 0; i < rx_frame.FIR.B.DLC; ++i) {
|
||||
Serial.print(rx_frame.data.u8[i], HEX);
|
||||
Serial.print(" ");
|
||||
}
|
||||
Serial.println("");
|
||||
}
|
||||
|
||||
int state = 0;
|
||||
|
||||
void send_can_battery() {
|
||||
unsigned long currentMillis = millis();
|
||||
// Send 100ms CAN Message
|
||||
if (currentMillis - previousMillis100 >= INTERVAL_100_MS) {
|
||||
previousMillis100 = currentMillis;
|
||||
|
||||
//ESP32Can.CANWriteFrame(&ipace_keep_alive);
|
||||
}
|
||||
|
||||
// Send 500ms CAN Message
|
||||
if (currentMillis - previousMillis500 >= INTERVAL_500_MS) {
|
||||
previousMillis500 = currentMillis;
|
||||
|
||||
CAN_frame_t msg;
|
||||
int err;
|
||||
|
||||
switch (state) {
|
||||
case 0:
|
||||
|
||||
// car response: 7ec 07 59 02 8f c0 64 88 28
|
||||
// response: 7EC 07 59 02 8F F0 01 00 28
|
||||
// response: 7EC 03 59 02 8F 00 00 00 00
|
||||
// 7EC 8 3 7F 19 11 0 0 0 0
|
||||
msg = {.FIR = {.B =
|
||||
{
|
||||
.DLC = 8,
|
||||
.FF = CAN_frame_std,
|
||||
}},
|
||||
.MsgID = 0x7e4,
|
||||
.data = {0x03, 0x19, 0x02, 0x8f, 0x00, 0x00, 0x00, 0x00}};
|
||||
err = ESP32Can.CANWriteFrame(&msg);
|
||||
if (err == 0)
|
||||
state++;
|
||||
|
||||
break;
|
||||
case 1:
|
||||
// car response: 7EC 11 fa 59 04 c0 64 88 28
|
||||
// response:
|
||||
|
||||
msg = {.FIR = {.B =
|
||||
{
|
||||
.DLC = 8,
|
||||
.FF = CAN_frame_std,
|
||||
}},
|
||||
.MsgID = 0x7e4,
|
||||
.data = {0x06, 0x19, 0x04, 0xc0, 0x64, 0x88, 0xff, 0x00}};
|
||||
err = ESP32Can.CANWriteFrame(&msg);
|
||||
if (err == 0)
|
||||
state++;
|
||||
break;
|
||||
case 2:
|
||||
/* reset */
|
||||
state = 0;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void setup_battery(void) { // Performs one time setup at startup
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("Jaguar iPace 90kWh battery selected");
|
||||
#endif
|
||||
|
||||
datalayer.battery.info.number_of_cells = 108;
|
||||
datalayer.battery.info.max_design_voltage_dV = 4546;
|
||||
datalayer.battery.info.min_design_voltage_dV = 3370;
|
||||
}
|
||||
|
||||
#endif
|
10
Software/src/battery/JAGUAR-IPACE-BATTERY.h
Normal file
10
Software/src/battery/JAGUAR-IPACE-BATTERY.h
Normal file
|
@ -0,0 +1,10 @@
|
|||
#ifndef JAGUAR_IPACE_BATTERY_H
|
||||
#define JAGUAR_IPACE_BATTERY_H
|
||||
#include "../include.h"
|
||||
|
||||
#define BATTERY_SELECTED
|
||||
#define MAX_CELL_DEVIATION_MV 9999 // TODO is this ok ?
|
||||
|
||||
void setup_battery(void);
|
||||
|
||||
#endif
|
|
@ -9,11 +9,9 @@
|
|||
|
||||
/* Do not change code below unless you are sure what you are doing */
|
||||
static unsigned long previousMillis500ms = 0; // will store last time a 500ms CAN Message was send
|
||||
static uint8_t CANstillAlive = 12; //counter for checking if CAN is still alive
|
||||
|
||||
#define MAX_CELL_VOLTAGE 4250 //Battery is put into emergency stop if one cell goes over this value
|
||||
#define MIN_CELL_VOLTAGE 2950 //Battery is put into emergency stop if one cell goes below this value
|
||||
#define MAX_CELL_DEVIATION 150 //LED turns yellow on the board if mv delta exceeds this value
|
||||
|
||||
static uint16_t inverterVoltageFrameHigh = 0;
|
||||
static uint16_t inverterVoltage = 0;
|
||||
|
@ -23,7 +21,6 @@ static uint16_t SOC_Display = 0;
|
|||
static uint16_t batterySOH = 1000;
|
||||
static uint16_t CellVoltMax_mV = 3700;
|
||||
static uint16_t CellVoltMin_mV = 3700;
|
||||
static uint16_t cell_deviation_mV = 0;
|
||||
static uint16_t batteryVoltage = 0;
|
||||
static int16_t leadAcidBatteryVoltage = 120;
|
||||
static int16_t batteryAmps = 0;
|
||||
|
@ -51,8 +48,10 @@ CANFDMessage EGMP_7E4_ack;
|
|||
|
||||
void set_cell_voltages(CANFDMessage frame, int start, int length, int startCell) {
|
||||
for (size_t i = 0; i < length; i++) {
|
||||
if ((frame.data[start + i] * 20) > 1000) {
|
||||
datalayer.battery.status.cell_voltages_mV[startCell + i] = (frame.data[start + i] * 20);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void update_values_battery() { //This function maps all the values fetched via CAN to the correct parameters used for modbus
|
||||
|
@ -63,24 +62,18 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
|
||||
datalayer.battery.status.voltage_dV = batteryVoltage; //value is *10 (3700 = 370.0)
|
||||
|
||||
datalayer.battery.status.current_dA = batteryAmps; //value is *10 (150 = 15.0)
|
||||
datalayer.battery.status.current_dA = -batteryAmps; //value is *10 (150 = 15.0)
|
||||
|
||||
datalayer.battery.status.remaining_capacity_Wh = static_cast<uint32_t>(
|
||||
(static_cast<double>(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh);
|
||||
|
||||
//datalayer.battery.status.max_charge_power_W = (uint16_t)allowedChargePower * 10; //From kW*100 to Watts
|
||||
//The allowed charge power is not available. We estimate this value
|
||||
if (datalayer.battery.status.reported_soc == 10000) { // When scaled SOC is 100%, set allowed charge power to 0
|
||||
datalayer.battery.status.max_charge_power_W = 0;
|
||||
} else { // No limits, max charging power allowed
|
||||
//The allowed charge power is not available. We hardcode this value for now
|
||||
datalayer.battery.status.max_charge_power_W = MAXCHARGEPOWERALLOWED;
|
||||
}
|
||||
|
||||
//datalayer.battery.status.max_discharge_power_W = (uint16_t)allowedDischargePower * 10; //From kW*100 to Watts
|
||||
if (datalayer.battery.status.reported_soc < 100) { // When scaled SOC is <1%, set allowed charge power to 0
|
||||
datalayer.battery.status.max_discharge_power_W = 0;
|
||||
} else { // No limits, max charging power allowed
|
||||
//The allowed discharge power is not available. We hardcode this value for now
|
||||
datalayer.battery.status.max_discharge_power_W = MAXDISCHARGEPOWERALLOWED;
|
||||
}
|
||||
|
||||
powerWatt = ((batteryVoltage * batteryAmps) / 100);
|
||||
|
||||
|
@ -95,10 +88,10 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
datalayer.battery.status.cell_min_voltage_mV = CellVoltMin_mV;
|
||||
|
||||
/* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/
|
||||
if (!CANstillAlive) {
|
||||
if (!datalayer.battery.status.CAN_battery_still_alive) {
|
||||
set_event(EVENT_CANFD_RX_FAILURE, 0);
|
||||
} else {
|
||||
CANstillAlive--;
|
||||
datalayer.battery.status.CAN_battery_still_alive--;
|
||||
clear_event(EVENT_CANFD_RX_FAILURE);
|
||||
}
|
||||
|
||||
|
@ -111,25 +104,12 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
}
|
||||
|
||||
// Check if cell voltages are within allowed range
|
||||
cell_deviation_mV = (datalayer.battery.status.cell_max_voltage_mV - datalayer.battery.status.cell_min_voltage_mV);
|
||||
|
||||
if (CellVoltMax_mV >= MAX_CELL_VOLTAGE) {
|
||||
set_event(EVENT_CELL_OVER_VOLTAGE, 0);
|
||||
}
|
||||
if (CellVoltMin_mV <= MIN_CELL_VOLTAGE) {
|
||||
set_event(EVENT_CELL_UNDER_VOLTAGE, 0);
|
||||
}
|
||||
if (cell_deviation_mV > MAX_CELL_DEVIATION) {
|
||||
set_event(EVENT_CELL_DEVIATION_HIGH, 0);
|
||||
} else {
|
||||
clear_event(EVENT_CELL_DEVIATION_HIGH);
|
||||
}
|
||||
|
||||
if (datalayer.battery.status.bms_status ==
|
||||
FAULT) { //Incase we enter a critical fault state, zero out the allowed limits
|
||||
datalayer.battery.status.max_charge_power_W = 0;
|
||||
datalayer.battery.status.max_discharge_power_W = 0;
|
||||
}
|
||||
|
||||
/* Safeties verified. Perform USB serial printout if configured to do so */
|
||||
|
||||
|
@ -193,17 +173,29 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
#endif
|
||||
}
|
||||
|
||||
void send_canfd_frame(CANFDMessage frame) {
|
||||
#ifdef DEBUG_VIA_USB
|
||||
const bool ok = canfd.tryToSend(frame);
|
||||
if (ok) {
|
||||
} else {
|
||||
Serial.println("Send canfd failure.");
|
||||
}
|
||||
#else
|
||||
canfd.tryToSend(frame);
|
||||
#endif
|
||||
}
|
||||
|
||||
void receive_canfd_battery(CANFDMessage frame) {
|
||||
CANstillAlive = 12;
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
switch (frame.id) {
|
||||
case 0x7EC:
|
||||
// printFrame(frame);
|
||||
// print_canfd_frame(frame);
|
||||
switch (frame.data[0]) {
|
||||
case 0x10: //"PID Header"
|
||||
// Serial.println ("Send ack");
|
||||
poll_data_pid = frame.data[4];
|
||||
// if (frame.data[4] == poll_data_pid) {
|
||||
canfd.tryToSend(EGMP_7E4_ack); //Send ack to BMS if the same frame is sent as polled
|
||||
send_canfd_frame(EGMP_7E4_ack); //Send ack to BMS if the same frame is sent as polled
|
||||
// }
|
||||
break;
|
||||
case 0x21: //First frame in PID group
|
||||
|
@ -385,10 +377,19 @@ void send_can_battery() {
|
|||
// Check if sending of CAN messages has been delayed too much.
|
||||
if ((currentMillis - previousMillis500ms >= INTERVAL_500_MS_DELAYED) && (currentMillis > BOOTUP_TIME)) {
|
||||
set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis500ms));
|
||||
} else {
|
||||
clear_event(EVENT_CAN_OVERRUN);
|
||||
}
|
||||
previousMillis500ms = currentMillis;
|
||||
// Section added to close contractor
|
||||
if (datalayer.battery.status.bms_status == ACTIVE) {
|
||||
datalayer.system.status.battery_allows_contactor_closing = true;
|
||||
} else { //datalayer.battery.status.bms_status == FAULT or inverter requested opening contactors
|
||||
datalayer.system.status.battery_allows_contactor_closing = false;
|
||||
}
|
||||
// Section end
|
||||
EGMP_7E4.data[3] = KIA_7E4_COUNTER;
|
||||
canfd.tryToSend(EGMP_7E4);
|
||||
send_canfd_frame(EGMP_7E4);
|
||||
|
||||
KIA_7E4_COUNTER++;
|
||||
if (KIA_7E4_COUNTER > 0x0D) { // gets up to 0x010C before repeating
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
extern ACAN2517FD canfd;
|
||||
|
||||
#define BATTERY_SELECTED
|
||||
|
||||
#define MAX_CELL_DEVIATION_MV 150
|
||||
#define MAXCHARGEPOWERALLOWED 10000
|
||||
#define MAXDISCHARGEPOWERALLOWED 10000
|
||||
|
||||
|
|
|
@ -9,41 +9,38 @@
|
|||
/* Do not change code below unless you are sure what you are doing */
|
||||
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
|
||||
static unsigned long previousMillis10 = 0; // will store last time a 10s CAN Message was send
|
||||
static uint8_t CANstillAlive = 12; //counter for checking if CAN is still alive
|
||||
|
||||
#define MAX_CELL_VOLTAGE 4250 //Battery is put into emergency stop if one cell goes over this value
|
||||
#define MIN_CELL_VOLTAGE 2950 //Battery is put into emergency stop if one cell goes below this value
|
||||
#define MAX_CELL_DEVIATION 150 //LED turns yellow on the board if mv delta exceeds this value
|
||||
|
||||
static uint16_t soc_calculated = 0;
|
||||
static uint16_t SOC_BMS = 0;
|
||||
static uint16_t SOC_Display = 0;
|
||||
static uint16_t batterySOH = 1000;
|
||||
static uint8_t waterleakageSensor = 164;
|
||||
static int16_t leadAcidBatteryVoltage = 0;
|
||||
static uint16_t CellVoltMax_mV = 3700;
|
||||
static uint8_t CellVmaxNo = 0;
|
||||
static uint16_t CellVoltMin_mV = 3700;
|
||||
static uint8_t CellVminNo = 0;
|
||||
static uint16_t cell_deviation_mV = 0;
|
||||
static uint16_t allowedDischargePower = 0;
|
||||
static uint16_t allowedChargePower = 0;
|
||||
static uint16_t batteryVoltage = 0;
|
||||
static uint16_t inverterVoltageFrameHigh = 0;
|
||||
static uint16_t inverterVoltage = 0;
|
||||
static uint16_t cellvoltages_mv[98];
|
||||
static int16_t leadAcidBatteryVoltage = 120;
|
||||
static int16_t batteryAmps = 0;
|
||||
static int16_t temperatureMax = 0;
|
||||
static int16_t temperatureMin = 0;
|
||||
static int8_t temperature_water_inlet = 0;
|
||||
static int16_t poll_data_pid = 0;
|
||||
static uint8_t CellVmaxNo = 0;
|
||||
static uint8_t CellVminNo = 0;
|
||||
static uint8_t batteryManagementMode = 0;
|
||||
static uint8_t BMS_ign = 0;
|
||||
static int16_t poll_data_pid = 0;
|
||||
static int8_t heatertemp = 0;
|
||||
static uint8_t batteryRelay = 0;
|
||||
static int8_t powerRelayTemperature = 0;
|
||||
static uint16_t inverterVoltageFrameHigh = 0;
|
||||
static uint16_t inverterVoltage = 0;
|
||||
static uint8_t startedUp = false;
|
||||
static uint8_t waterleakageSensor = 164;
|
||||
static uint8_t counter_200 = 0;
|
||||
static uint16_t cellvoltages_mv[98];
|
||||
static int8_t temperature_water_inlet = 0;
|
||||
static int8_t heatertemp = 0;
|
||||
static int8_t powerRelayTemperature = 0;
|
||||
static bool startedUp = false;
|
||||
|
||||
CAN_frame_t KIA_HYUNDAI_200 = {.FIR = {.B =
|
||||
{
|
||||
|
@ -158,17 +155,9 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
datalayer.battery.status.remaining_capacity_Wh = static_cast<uint32_t>(
|
||||
(static_cast<double>(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh);
|
||||
|
||||
if (datalayer.battery.status.reported_soc == 10000) { // When scaled SOC is 100%, set allowed charge power to 0
|
||||
datalayer.battery.status.max_charge_power_W = 0;
|
||||
} else { // Limit according to CAN value
|
||||
datalayer.battery.status.max_charge_power_W = allowedChargePower * 10;
|
||||
}
|
||||
|
||||
if (datalayer.battery.status.reported_soc < 100) { // When scaled SOC is <1%, set allowed charge power to 0
|
||||
datalayer.battery.status.max_discharge_power_W = 0;
|
||||
} else { // Limit according to CAN value
|
||||
datalayer.battery.status.max_discharge_power_W = allowedDischargePower * 10;
|
||||
}
|
||||
|
||||
//Power in watts, Negative = charging batt
|
||||
datalayer.battery.status.active_power_W =
|
||||
|
@ -182,14 +171,6 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
|
||||
datalayer.battery.status.cell_min_voltage_mV = CellVoltMin_mV;
|
||||
|
||||
/* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/
|
||||
if (!CANstillAlive) {
|
||||
set_event(EVENT_CAN_RX_FAILURE, 0);
|
||||
} else {
|
||||
CANstillAlive--;
|
||||
clear_event(EVENT_CAN_RX_FAILURE);
|
||||
}
|
||||
|
||||
if (waterleakageSensor == 0) {
|
||||
set_event(EVENT_WATER_INGRESS, 0);
|
||||
}
|
||||
|
@ -198,43 +179,13 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
set_event(EVENT_12V_LOW, leadAcidBatteryVoltage);
|
||||
}
|
||||
|
||||
//Map all cell voltages to the global array
|
||||
for (int i = 0; i < 98; ++i) {
|
||||
if (cellvoltages_mv[i] > 1000) {
|
||||
datalayer.battery.status.cell_voltages_mV[i] = cellvoltages_mv[i];
|
||||
}
|
||||
}
|
||||
// Check if we have 98S or 90S battery
|
||||
if (datalayer.battery.status.cell_voltages_mV[97] > 0) {
|
||||
datalayer.battery.info.number_of_cells = 98;
|
||||
datalayer.battery.info.max_design_voltage_dV = 4040;
|
||||
datalayer.battery.info.min_design_voltage_dV = 3100;
|
||||
} else {
|
||||
datalayer.battery.info.number_of_cells = 90;
|
||||
datalayer.battery.info.max_design_voltage_dV = 3870;
|
||||
datalayer.battery.info.min_design_voltage_dV = 2250;
|
||||
}
|
||||
|
||||
// Check if cell voltages are within allowed range
|
||||
cell_deviation_mV = (datalayer.battery.status.cell_max_voltage_mV - datalayer.battery.status.cell_min_voltage_mV);
|
||||
|
||||
if (CellVoltMax_mV >= MAX_CELL_VOLTAGE) {
|
||||
set_event(EVENT_CELL_OVER_VOLTAGE, 0);
|
||||
}
|
||||
if (CellVoltMin_mV <= MIN_CELL_VOLTAGE) {
|
||||
set_event(EVENT_CELL_UNDER_VOLTAGE, 0);
|
||||
}
|
||||
if (cell_deviation_mV > MAX_CELL_DEVIATION) {
|
||||
set_event(EVENT_CELL_DEVIATION_HIGH, 0);
|
||||
} else {
|
||||
clear_event(EVENT_CELL_DEVIATION_HIGH);
|
||||
}
|
||||
|
||||
if (datalayer.battery.status.bms_status ==
|
||||
FAULT) { //Incase we enter a critical fault state, zero out the allowed limits
|
||||
datalayer.battery.status.max_charge_power_W = 0;
|
||||
datalayer.battery.status.max_discharge_power_W = 0;
|
||||
}
|
||||
|
||||
/* Safeties verified. Perform USB serial printout if configured to do so */
|
||||
|
||||
|
@ -298,20 +249,40 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
#endif
|
||||
}
|
||||
|
||||
void update_number_of_cells() {
|
||||
//If we have cell values and number_of_cells not initialized yet
|
||||
if (cellvoltages_mv[0] > 0 && datalayer.battery.info.number_of_cells == 0) {
|
||||
// Check if we have 98S or 90S battery
|
||||
if (datalayer.battery.status.cell_voltages_mV[97] > 0) {
|
||||
datalayer.battery.info.number_of_cells = 98;
|
||||
datalayer.battery.info.max_design_voltage_dV = 4040;
|
||||
datalayer.battery.info.min_design_voltage_dV = 3100;
|
||||
} else {
|
||||
datalayer.battery.info.number_of_cells = 90;
|
||||
datalayer.battery.info.max_design_voltage_dV = 3870;
|
||||
datalayer.battery.info.min_design_voltage_dV = 2250;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void receive_can_battery(CAN_frame_t rx_frame) {
|
||||
switch (rx_frame.MsgID) {
|
||||
case 0x4DE:
|
||||
startedUp = true;
|
||||
break;
|
||||
case 0x542: //BMS SOC
|
||||
CANstillAlive = 12; //We use this message to verify that BMS is still alive
|
||||
startedUp = true;
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
SOC_Display = rx_frame.data.u8[0] * 5; //100% = 200 ( 200 * 5 = 1000 )
|
||||
break;
|
||||
case 0x594:
|
||||
startedUp = true;
|
||||
allowedChargePower = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]);
|
||||
allowedDischargePower = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]);
|
||||
SOC_BMS = rx_frame.data.u8[5] * 5; //100% = 200 ( 200 * 5 = 1000 )
|
||||
break;
|
||||
case 0x595:
|
||||
startedUp = true;
|
||||
batteryVoltage = (rx_frame.data.u8[7] << 8) + rx_frame.data.u8[6];
|
||||
batteryAmps = (rx_frame.data.u8[5] << 8) + rx_frame.data.u8[4];
|
||||
if (counter_200 > 3) {
|
||||
|
@ -320,18 +291,21 @@ void receive_can_battery(CAN_frame_t rx_frame) {
|
|||
} //VCU measured voltage sent back to bms
|
||||
break;
|
||||
case 0x596:
|
||||
startedUp = true;
|
||||
leadAcidBatteryVoltage = rx_frame.data.u8[1]; //12v Battery Volts
|
||||
temperatureMin = rx_frame.data.u8[6]; //Lowest temp in battery
|
||||
temperatureMax = rx_frame.data.u8[7]; //Highest temp in battery
|
||||
break;
|
||||
case 0x598:
|
||||
startedUp = true;
|
||||
break;
|
||||
case 0x5D5:
|
||||
startedUp = true;
|
||||
waterleakageSensor = rx_frame.data.u8[3]; //Water sensor inside pack, value 164 is no water --> 0 is short
|
||||
powerRelayTemperature = rx_frame.data.u8[7];
|
||||
break;
|
||||
case 0x5D8:
|
||||
startedUp = 1;
|
||||
startedUp = true;
|
||||
|
||||
//PID data is polled after last message sent from battery:
|
||||
if (poll_data_pid >= 10) { //polling one of ten PIDs at 100ms, resolution = 1s
|
||||
|
@ -508,6 +482,10 @@ void receive_can_battery(CAN_frame_t rx_frame) {
|
|||
}
|
||||
break;
|
||||
case 0x26: //Sixth datarow in PID group
|
||||
//Map all cell voltages to the global array
|
||||
memcpy(datalayer.battery.status.cell_voltages_mV, cellvoltages_mv, 98 * sizeof(uint16_t));
|
||||
//Update number of cells
|
||||
update_number_of_cells();
|
||||
break;
|
||||
case 0x27: //Seventh datarow in PID group
|
||||
if (poll_data_pid == 1) {
|
||||
|
@ -529,6 +507,11 @@ void receive_can_battery(CAN_frame_t rx_frame) {
|
|||
|
||||
void send_can_battery() {
|
||||
unsigned long currentMillis = millis();
|
||||
|
||||
if (!startedUp) {
|
||||
return; // Don't send any CAN messages towards battery until it has started up
|
||||
}
|
||||
|
||||
//Send 100ms message
|
||||
if (currentMillis - previousMillis100 >= INTERVAL_100_MS) {
|
||||
previousMillis100 = currentMillis;
|
||||
|
@ -542,6 +525,8 @@ void send_can_battery() {
|
|||
// Check if sending of CAN messages has been delayed too much.
|
||||
if ((currentMillis - previousMillis10 >= INTERVAL_10_MS_DELAYED) && (currentMillis > BOOTUP_TIME)) {
|
||||
set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis10));
|
||||
} else {
|
||||
clear_event(EVENT_CAN_OVERRUN);
|
||||
}
|
||||
previousMillis10 = currentMillis;
|
||||
|
||||
|
@ -560,11 +545,7 @@ void send_can_battery() {
|
|||
break;
|
||||
case 3:
|
||||
KIA_HYUNDAI_200.data.u8[5] = 0xD7;
|
||||
if (startedUp) {
|
||||
++counter_200;
|
||||
} else {
|
||||
counter_200 = 0;
|
||||
}
|
||||
break;
|
||||
case 4:
|
||||
KIA_HYUNDAI_200.data.u8[3] = 0x10;
|
||||
|
|
|
@ -5,7 +5,9 @@
|
|||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
|
||||
#define BATTERY_SELECTED
|
||||
#define MAX_CELL_DEVIATION_MV 150
|
||||
|
||||
void setup_battery(void);
|
||||
void update_number_of_cells();
|
||||
|
||||
#endif
|
||||
|
|
284
Software/src/battery/KIA-HYUNDAI-HYBRID-BATTERY.cpp
Normal file
284
Software/src/battery/KIA-HYUNDAI-HYBRID-BATTERY.cpp
Normal file
|
@ -0,0 +1,284 @@
|
|||
#include "../include.h"
|
||||
#ifdef KIA_HYUNDAI_HYBRID_BATTERY
|
||||
#include "../datalayer/datalayer.h"
|
||||
#include "../devboard/utils/events.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
#include "KIA-HYUNDAI-HYBRID-BATTERY.h"
|
||||
|
||||
/* TODO:
|
||||
- The HEV battery seems to turn off after 1 minute of use. When this happens SOC% stops updating.
|
||||
- We need to figure out how to keep the BMS alive. Most likely we need to send a specific CAN message
|
||||
*/
|
||||
|
||||
/* Do not change code below unless you are sure what you are doing */
|
||||
static unsigned long previousMillis1000 = 0; // will store last time a 100ms CAN Message was send
|
||||
|
||||
static uint16_t SOC = 0;
|
||||
static uint16_t SOC_display = 0;
|
||||
static bool interlock_missing = false;
|
||||
static int16_t battery_current = 0;
|
||||
static uint8_t battery_current_high_byte = 0;
|
||||
static uint16_t battery_voltage = 0;
|
||||
static uint32_t available_charge_power = 0;
|
||||
static uint32_t available_discharge_power = 0;
|
||||
static int8_t battery_module_max_temperature = 0;
|
||||
static int8_t battery_module_min_temperature = 0;
|
||||
static uint8_t poll_data_pid = 0;
|
||||
static uint16_t cellvoltages_mv[98];
|
||||
static uint16_t min_cell_voltage_mv = 3700;
|
||||
static uint16_t max_cell_voltage_mv = 3700;
|
||||
|
||||
CAN_frame_t KIA_7E4_id1 = {.FIR = {.B =
|
||||
{
|
||||
.DLC = 8,
|
||||
.FF = CAN_frame_std,
|
||||
}},
|
||||
.MsgID = 0x7E4,
|
||||
.data = {0x02, 0x21, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
CAN_frame_t KIA_7E4_id2 = {.FIR = {.B =
|
||||
{
|
||||
.DLC = 8,
|
||||
.FF = CAN_frame_std,
|
||||
}},
|
||||
.MsgID = 0x7E4,
|
||||
.data = {0x02, 0x21, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
CAN_frame_t KIA_7E4_id3 = {.FIR = {.B =
|
||||
{
|
||||
.DLC = 8,
|
||||
.FF = CAN_frame_std,
|
||||
}},
|
||||
.MsgID = 0x7E4,
|
||||
.data = {0x02, 0x21, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
CAN_frame_t KIA_7E4_id5 = {.FIR = {.B =
|
||||
{
|
||||
.DLC = 8,
|
||||
.FF = CAN_frame_std,
|
||||
}},
|
||||
.MsgID = 0x7E4,
|
||||
.data = {0x02, 0x21, 0x05, 0x04, 0x00, 0x00, 0x00, 0x00}};
|
||||
CAN_frame_t KIA_7E4_ack = {.FIR = {.B =
|
||||
{
|
||||
.DLC = 8,
|
||||
.FF = CAN_frame_std,
|
||||
}},
|
||||
.MsgID = 0x7E4, //Ack frame, correct PID is returned. Flow control message
|
||||
.data = {0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
|
||||
void update_values_battery() { //This function maps all the values fetched via CAN to the correct parameters used for modbus
|
||||
|
||||
datalayer.battery.status.real_soc = SOC * 50;
|
||||
|
||||
datalayer.battery.status.voltage_dV = battery_voltage;
|
||||
|
||||
datalayer.battery.status.current_dA = battery_current;
|
||||
|
||||
datalayer.battery.status.remaining_capacity_Wh = static_cast<uint32_t>(
|
||||
(static_cast<double>(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh);
|
||||
|
||||
datalayer.battery.status.max_discharge_power_W = available_discharge_power * 10;
|
||||
|
||||
datalayer.battery.status.max_charge_power_W = available_charge_power * 10;
|
||||
|
||||
//Power in watts, Negative = charging batt
|
||||
datalayer.battery.status.active_power_W =
|
||||
((datalayer.battery.status.voltage_dV * datalayer.battery.status.current_dA) / 100);
|
||||
|
||||
datalayer.battery.status.temperature_min_dC = (int16_t)(battery_module_min_temperature * 10);
|
||||
|
||||
datalayer.battery.status.temperature_max_dC = (int16_t)(battery_module_max_temperature * 10);
|
||||
|
||||
datalayer.battery.status.cell_max_voltage_mV = max_cell_voltage_mv;
|
||||
|
||||
datalayer.battery.status.cell_min_voltage_mV = min_cell_voltage_mv;
|
||||
|
||||
//Map all cell voltages to the global array
|
||||
memcpy(datalayer.battery.status.cell_voltages_mV, cellvoltages_mv, 98 * sizeof(uint16_t));
|
||||
|
||||
if (interlock_missing) {
|
||||
set_event(EVENT_HVIL_FAILURE, 0);
|
||||
} else {
|
||||
clear_event(EVENT_HVIL_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
void receive_can_battery(CAN_frame_t rx_frame) {
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
switch (rx_frame.MsgID) {
|
||||
case 0x5F1:
|
||||
break;
|
||||
case 0x51E:
|
||||
break;
|
||||
case 0x588:
|
||||
break;
|
||||
case 0x5AE:
|
||||
interlock_missing = (bool)(rx_frame.data.u8[1] & 0x02) >> 1;
|
||||
break;
|
||||
case 0x5AF:
|
||||
break;
|
||||
case 0x5AD:
|
||||
break;
|
||||
case 0x670:
|
||||
break;
|
||||
case 0x7EC: //Data From polled PID group, BigEndian
|
||||
switch (rx_frame.data.u8[0]) {
|
||||
case 0x10: //"PID Header"
|
||||
if (rx_frame.data.u8[3] == poll_data_pid) {
|
||||
ESP32Can.CANWriteFrame(&KIA_7E4_ack); //Send ack to BMS if the same frame is sent as polled
|
||||
}
|
||||
break;
|
||||
case 0x21: //First frame in PID group
|
||||
if (poll_data_pid == 1) { // 21 01
|
||||
SOC = rx_frame.data.u8[1]; // 0xBC = 188 /2 = 94%
|
||||
available_charge_power = ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]);
|
||||
available_discharge_power = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
|
||||
battery_current_high_byte = rx_frame.data.u8[7];
|
||||
} else if (poll_data_pid == 2) { //21 02
|
||||
cellvoltages_mv[0] = (rx_frame.data.u8[2] * 20);
|
||||
cellvoltages_mv[1] = (rx_frame.data.u8[3] * 20);
|
||||
cellvoltages_mv[2] = (rx_frame.data.u8[4] * 20);
|
||||
cellvoltages_mv[3] = (rx_frame.data.u8[5] * 20);
|
||||
cellvoltages_mv[4] = (rx_frame.data.u8[6] * 20);
|
||||
cellvoltages_mv[5] = (rx_frame.data.u8[7] * 20);
|
||||
} else if (poll_data_pid == 3) { //21 03
|
||||
cellvoltages_mv[31] = (rx_frame.data.u8[2] * 20);
|
||||
cellvoltages_mv[32] = (rx_frame.data.u8[3] * 20);
|
||||
cellvoltages_mv[33] = (rx_frame.data.u8[4] * 20);
|
||||
cellvoltages_mv[34] = (rx_frame.data.u8[5] * 20);
|
||||
cellvoltages_mv[35] = (rx_frame.data.u8[6] * 20);
|
||||
cellvoltages_mv[36] = (rx_frame.data.u8[7] * 20);
|
||||
} else if (poll_data_pid == 5) { //21 05
|
||||
}
|
||||
break;
|
||||
case 0x22: //Second datarow in PID group
|
||||
if (poll_data_pid == 1) {
|
||||
battery_current = ((battery_current_high_byte << 8) | rx_frame.data.u8[1]);
|
||||
battery_voltage = ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]);
|
||||
battery_module_max_temperature = rx_frame.data.u8[4];
|
||||
battery_module_min_temperature = rx_frame.data.u8[5];
|
||||
} else if (poll_data_pid == 2) {
|
||||
cellvoltages_mv[6] = (rx_frame.data.u8[1] * 20);
|
||||
cellvoltages_mv[7] = (rx_frame.data.u8[2] * 20);
|
||||
cellvoltages_mv[8] = (rx_frame.data.u8[3] * 20);
|
||||
cellvoltages_mv[9] = (rx_frame.data.u8[4] * 20);
|
||||
cellvoltages_mv[10] = (rx_frame.data.u8[5] * 20);
|
||||
cellvoltages_mv[11] = (rx_frame.data.u8[6] * 20);
|
||||
cellvoltages_mv[12] = (rx_frame.data.u8[7] * 20);
|
||||
|
||||
} else if (poll_data_pid == 3) {
|
||||
cellvoltages_mv[37] = (rx_frame.data.u8[2] * 20);
|
||||
cellvoltages_mv[38] = (rx_frame.data.u8[3] * 20);
|
||||
cellvoltages_mv[39] = (rx_frame.data.u8[4] * 20);
|
||||
cellvoltages_mv[40] = (rx_frame.data.u8[5] * 20);
|
||||
cellvoltages_mv[41] = (rx_frame.data.u8[6] * 20);
|
||||
cellvoltages_mv[42] = (rx_frame.data.u8[7] * 20);
|
||||
} else if (poll_data_pid == 5) {
|
||||
}
|
||||
break;
|
||||
case 0x23: //Third datarow in PID group
|
||||
if (poll_data_pid == 1) {
|
||||
max_cell_voltage_mv = rx_frame.data.u8[6] * 20;
|
||||
} else if (poll_data_pid == 2) {
|
||||
cellvoltages_mv[13] = (rx_frame.data.u8[1] * 20);
|
||||
cellvoltages_mv[14] = (rx_frame.data.u8[2] * 20);
|
||||
cellvoltages_mv[15] = (rx_frame.data.u8[3] * 20);
|
||||
cellvoltages_mv[16] = (rx_frame.data.u8[4] * 20);
|
||||
cellvoltages_mv[17] = (rx_frame.data.u8[5] * 20);
|
||||
cellvoltages_mv[18] = (rx_frame.data.u8[6] * 20);
|
||||
cellvoltages_mv[19] = (rx_frame.data.u8[7] * 20);
|
||||
} else if (poll_data_pid == 3) {
|
||||
cellvoltages_mv[43] = (rx_frame.data.u8[2] * 20);
|
||||
cellvoltages_mv[44] = (rx_frame.data.u8[3] * 20);
|
||||
cellvoltages_mv[45] = (rx_frame.data.u8[4] * 20);
|
||||
cellvoltages_mv[46] = (rx_frame.data.u8[5] * 20);
|
||||
cellvoltages_mv[47] = (rx_frame.data.u8[6] * 20);
|
||||
cellvoltages_mv[48] = (rx_frame.data.u8[7] * 20);
|
||||
} else if (poll_data_pid == 5) {
|
||||
}
|
||||
break;
|
||||
case 0x24: //Fourth datarow in PID group
|
||||
if (poll_data_pid == 1) {
|
||||
min_cell_voltage_mv = rx_frame.data.u8[1] * 20;
|
||||
} else if (poll_data_pid == 2) {
|
||||
cellvoltages_mv[20] = (rx_frame.data.u8[1] * 20);
|
||||
cellvoltages_mv[21] = (rx_frame.data.u8[2] * 20);
|
||||
cellvoltages_mv[22] = (rx_frame.data.u8[3] * 20);
|
||||
cellvoltages_mv[23] = (rx_frame.data.u8[4] * 20);
|
||||
cellvoltages_mv[24] = (rx_frame.data.u8[5] * 20);
|
||||
cellvoltages_mv[25] = (rx_frame.data.u8[6] * 20);
|
||||
cellvoltages_mv[26] = (rx_frame.data.u8[7] * 20);
|
||||
} else if (poll_data_pid == 3) {
|
||||
cellvoltages_mv[49] = (rx_frame.data.u8[2] * 20);
|
||||
cellvoltages_mv[50] = (rx_frame.data.u8[3] * 20);
|
||||
cellvoltages_mv[51] = (rx_frame.data.u8[4] * 20);
|
||||
cellvoltages_mv[52] = (rx_frame.data.u8[5] * 20);
|
||||
cellvoltages_mv[53] = (rx_frame.data.u8[6] * 20);
|
||||
cellvoltages_mv[54] = (rx_frame.data.u8[7] * 20);
|
||||
} else if (poll_data_pid == 5) {
|
||||
SOC_display = rx_frame.data.u8[7]; //0x26 = 38%
|
||||
}
|
||||
break;
|
||||
case 0x25: //Fifth datarow in PID group
|
||||
if (poll_data_pid == 1) {
|
||||
|
||||
} else if (poll_data_pid == 2) {
|
||||
cellvoltages_mv[27] = (rx_frame.data.u8[1] * 20);
|
||||
cellvoltages_mv[28] = (rx_frame.data.u8[2] * 20);
|
||||
cellvoltages_mv[29] = (rx_frame.data.u8[3] * 20);
|
||||
cellvoltages_mv[30] = (rx_frame.data.u8[4] * 20);
|
||||
} else if (poll_data_pid == 3) {
|
||||
cellvoltages_mv[55] = (rx_frame.data.u8[4] * 20);
|
||||
cellvoltages_mv[56] = (rx_frame.data.u8[5] * 20);
|
||||
cellvoltages_mv[57] = (rx_frame.data.u8[6] * 20);
|
||||
cellvoltages_mv[58] = (rx_frame.data.u8[7] * 20);
|
||||
} else if (poll_data_pid == 5) {
|
||||
}
|
||||
break;
|
||||
case 0x26: //Sixth datarow in PID group
|
||||
break;
|
||||
case 0x27: //Seventh datarow in PID group
|
||||
break;
|
||||
case 0x28: //Eighth datarow in PID group
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
void send_can_battery() {
|
||||
unsigned long currentMillis = millis();
|
||||
|
||||
// Send 1000ms CAN Message
|
||||
if (currentMillis - previousMillis1000 >= INTERVAL_1_S) {
|
||||
previousMillis1000 = currentMillis;
|
||||
|
||||
//PID data is polled after last message sent from battery:
|
||||
if (poll_data_pid >= 5) { //polling one of 5 PIDs at 100ms, resolution = 500ms
|
||||
poll_data_pid = 0;
|
||||
}
|
||||
poll_data_pid++;
|
||||
if (poll_data_pid == 1) {
|
||||
ESP32Can.CANWriteFrame(&KIA_7E4_id1);
|
||||
} else if (poll_data_pid == 2) {
|
||||
ESP32Can.CANWriteFrame(&KIA_7E4_id2);
|
||||
} else if (poll_data_pid == 3) {
|
||||
ESP32Can.CANWriteFrame(&KIA_7E4_id3);
|
||||
} else if (poll_data_pid == 4) {
|
||||
|
||||
} else if (poll_data_pid == 5) {
|
||||
ESP32Can.CANWriteFrame(&KIA_7E4_id5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void setup_battery(void) { // Performs one time setup at startup
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("Kia/Hyundai Hybrid battery selected");
|
||||
#endif
|
||||
datalayer.battery.info.number_of_cells = 56; // HEV , TODO: Make dynamic according to HEV/PHEV
|
||||
datalayer.battery.info.max_design_voltage_dV = 2550; //TODO: Values OK?
|
||||
datalayer.battery.info.min_design_voltage_dV = 1700;
|
||||
}
|
||||
|
||||
#endif
|
12
Software/src/battery/KIA-HYUNDAI-HYBRID-BATTERY.h
Normal file
12
Software/src/battery/KIA-HYUNDAI-HYBRID-BATTERY.h
Normal file
|
@ -0,0 +1,12 @@
|
|||
#ifndef KIA_HYUNDAI_HYBRID_BATTERY_H
|
||||
#define KIA_HYUNDAI_HYBRID_BATTERY_H
|
||||
#include <Arduino.h>
|
||||
#include "../include.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
|
||||
#define BATTERY_SELECTED
|
||||
#define MAX_CELL_DEVIATION_MV 100
|
||||
|
||||
void setup_battery(void);
|
||||
|
||||
#endif
|
152
Software/src/battery/MG-5-BATTERY.cpp
Normal file
152
Software/src/battery/MG-5-BATTERY.cpp
Normal file
|
@ -0,0 +1,152 @@
|
|||
#include "../include.h"
|
||||
#ifdef MG_5_BATTERY_H
|
||||
#include "../datalayer/datalayer.h"
|
||||
#include "../devboard/utils/events.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
#include "MG-5-BATTERY.h"
|
||||
|
||||
/* TODO:
|
||||
- Get contactor closing working
|
||||
- Figure out which CAN messages need to be sent towards the battery to keep it alive
|
||||
- Map all values from battery CAN messages
|
||||
- Most important ones
|
||||
*/
|
||||
|
||||
/* Do not change code below unless you are sure what you are doing */
|
||||
static unsigned long previousMillis10 = 0; // will store last time a 10ms CAN Message was send
|
||||
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
|
||||
|
||||
static int BMS_SOC = 0;
|
||||
|
||||
CAN_frame_t MG_5_100 = {.FIR = {.B =
|
||||
{
|
||||
.DLC = 8,
|
||||
.FF = CAN_frame_std,
|
||||
}},
|
||||
.MsgID = 0x100,
|
||||
.data = {0x00, 0x00, 0x00, 0x00, 0x80, 0x10, 0x00, 0x00}};
|
||||
|
||||
void update_values_battery() { //This function maps all the values fetched via CAN to the correct parameters used for modbus
|
||||
|
||||
datalayer.battery.status.real_soc;
|
||||
|
||||
datalayer.battery.status.voltage_dV;
|
||||
|
||||
datalayer.battery.status.current_dA;
|
||||
|
||||
datalayer.battery.info.total_capacity_Wh;
|
||||
|
||||
datalayer.battery.status.remaining_capacity_Wh;
|
||||
|
||||
datalayer.battery.status.max_discharge_power_W;
|
||||
|
||||
datalayer.battery.status.max_charge_power_W;
|
||||
|
||||
datalayer.battery.status.active_power_W;
|
||||
|
||||
datalayer.battery.status.temperature_min_dC;
|
||||
|
||||
datalayer.battery.status.temperature_max_dC;
|
||||
|
||||
#ifdef DEBUG_VIA_USB
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
void receive_can_battery(CAN_frame_t rx_frame) {
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
switch (rx_frame.MsgID) {
|
||||
case 0x171: //Following messages were detected on a MG5 battery BMS
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; // Let system know battery is sending CAN
|
||||
break;
|
||||
case 0x172:
|
||||
break;
|
||||
case 0x173:
|
||||
break;
|
||||
case 0x293:
|
||||
break;
|
||||
case 0x295:
|
||||
break;
|
||||
case 0x297:
|
||||
break;
|
||||
case 0x29B:
|
||||
break;
|
||||
case 0x29C:
|
||||
break;
|
||||
case 0x2A0:
|
||||
break;
|
||||
case 0x2A2:
|
||||
break;
|
||||
case 0x322:
|
||||
break;
|
||||
case 0x334:
|
||||
break;
|
||||
case 0x33F:
|
||||
break;
|
||||
case 0x391:
|
||||
break;
|
||||
case 0x393:
|
||||
break;
|
||||
case 0x3AB:
|
||||
break;
|
||||
case 0x3AC:
|
||||
break;
|
||||
case 0x3B8:
|
||||
break;
|
||||
case 0x3BA:
|
||||
break;
|
||||
case 0x3BC:
|
||||
break;
|
||||
case 0x3BE:
|
||||
break;
|
||||
case 0x3C0:
|
||||
break;
|
||||
case 0x3C2:
|
||||
break;
|
||||
case 0x400:
|
||||
break;
|
||||
case 0x402:
|
||||
break;
|
||||
case 0x418:
|
||||
break;
|
||||
case 0x44C:
|
||||
break;
|
||||
case 0x620:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
void send_can_battery() {
|
||||
unsigned long currentMillis = millis();
|
||||
//Send 10ms message
|
||||
if (currentMillis - previousMillis10 >= INTERVAL_10_MS) {
|
||||
// Check if sending of CAN messages has been delayed too much.
|
||||
if ((currentMillis - previousMillis10 >= INTERVAL_10_MS_DELAYED) && (currentMillis > BOOTUP_TIME)) {
|
||||
set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis10));
|
||||
} else {
|
||||
clear_event(EVENT_CAN_OVERRUN);
|
||||
}
|
||||
previousMillis10 = currentMillis;
|
||||
|
||||
ESP32Can.CANWriteFrame(&MG_5_100);
|
||||
}
|
||||
// Send 100ms CAN Message
|
||||
if (currentMillis - previousMillis100 >= INTERVAL_100_MS) {
|
||||
previousMillis100 = currentMillis;
|
||||
|
||||
//ESP32Can.CANWriteFrame(&MG_5_100);
|
||||
}
|
||||
}
|
||||
|
||||
void setup_battery(void) { // Performs one time setup at startup
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("MG 5 battery selected");
|
||||
#endif
|
||||
|
||||
datalayer.battery.info.max_design_voltage_dV = 4040; // Over this charging is not possible
|
||||
datalayer.battery.info.min_design_voltage_dV = 3100; // Under this discharging is disabled
|
||||
}
|
||||
|
||||
#endif
|
12
Software/src/battery/MG-5-BATTERY.h
Normal file
12
Software/src/battery/MG-5-BATTERY.h
Normal file
|
@ -0,0 +1,12 @@
|
|||
#ifndef MG_5_BATTERY_H
|
||||
#define MG_5_BATTERY_H
|
||||
#include <Arduino.h>
|
||||
#include "../include.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
|
||||
#define BATTERY_SELECTED
|
||||
#define MAX_CELL_DEVIATION_MV 150
|
||||
|
||||
void setup_battery(void);
|
||||
|
||||
#endif
|
|
@ -13,11 +13,10 @@
|
|||
static unsigned long previousMillis10 = 0; // will store last time a 10ms CAN Message was send
|
||||
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
|
||||
static unsigned long previousMillis10s = 0; // will store last time a 1s CAN Message was send
|
||||
static uint16_t CANerror = 0; //counter on how many CAN errors encountered
|
||||
static uint8_t CANstillAlive = 12; //counter for checking if CAN is still alive
|
||||
static uint8_t mprun10r = 0; //counter 0-20 for 0x1F2 message
|
||||
static uint8_t mprun10 = 0; //counter 0-3
|
||||
static uint8_t mprun100 = 0; //counter 0-3
|
||||
static bool can_bus_alive = false;
|
||||
|
||||
CAN_frame_t LEAF_1F2 = {.FIR = {.B =
|
||||
{
|
||||
|
@ -91,7 +90,6 @@ static uint8_t crctable[256] = {
|
|||
static uint8_t LEAF_Battery_Type = ZE0_BATTERY;
|
||||
#define MAX_CELL_VOLTAGE 4250 //Battery is put into emergency stop if one cell goes over this value
|
||||
#define MIN_CELL_VOLTAGE 2700 //Battery is put into emergency stop if one cell goes below this value
|
||||
#define MAX_CELL_DEVIATION 500 //LED turns yellow on the board if mv delta exceeds this value
|
||||
#define WH_PER_GID 77 //One GID is this amount of Watt hours
|
||||
static uint16_t LB_Discharge_Power_Limit = 0; //Limit in kW
|
||||
static uint16_t LB_Charge_Power_Limit = 0; //Limit in kW
|
||||
|
@ -132,12 +130,11 @@ static bool Batt_Heater_Mail_Send_Request = false; //Stores info when a heat re
|
|||
static uint8_t battery_request_idx = 0;
|
||||
static uint8_t group_7bb = 0;
|
||||
static uint8_t group = 1;
|
||||
static uint8_t stop_battery_query = 1;
|
||||
static bool stop_battery_query = true;
|
||||
static uint8_t hold_off_with_polling_10seconds = 10;
|
||||
static uint16_t cell_voltages[97]; //array with all the cellvoltages
|
||||
static uint8_t cellcounter = 0;
|
||||
static uint16_t min_max_voltage[2]; //contains cell min[0] and max[1] values in mV
|
||||
static uint16_t cell_deviation_mV = 0; //contains the deviation between highest and lowest cell in mV
|
||||
static uint16_t HX = 0; //Internal resistance
|
||||
static uint16_t insulation = 0; //Insulation resistance
|
||||
static uint16_t temp_raw_1 = 0;
|
||||
|
@ -202,32 +199,9 @@ void update_values_battery() { /* This function maps all the values fetched via
|
|||
}
|
||||
}
|
||||
|
||||
// Define power able to be discharged from battery
|
||||
if (LB_Discharge_Power_Limit > 60) { //if >60kW can be pulled from battery
|
||||
datalayer.battery.status.max_discharge_power_W = 60000; //cap value so we don't go over uint16 value
|
||||
} else {
|
||||
datalayer.battery.status.max_discharge_power_W = (LB_Discharge_Power_Limit * 1000); //kW to W
|
||||
}
|
||||
if (datalayer.battery.status.reported_soc ==
|
||||
0) { //Scaled SOC% value is 0.00%, we should not discharge battery further
|
||||
datalayer.battery.status.max_discharge_power_W = 0;
|
||||
}
|
||||
|
||||
// Define power able to be put into the battery
|
||||
if (LB_Charge_Power_Limit > 60) { //if >60kW can be put into the battery
|
||||
datalayer.battery.status.max_charge_power_W = 60000; //cap value so we don't go over uint16 value
|
||||
} else {
|
||||
datalayer.battery.status.max_charge_power_W = (LB_Charge_Power_Limit * 1000); //kW to W
|
||||
}
|
||||
if (datalayer.battery.status.reported_soc == 10000) //Scaled SOC% value is 100.00%
|
||||
{
|
||||
datalayer.battery.status.max_charge_power_W = 0; //No need to charge further, set max power to 0
|
||||
}
|
||||
|
||||
//Map all cell voltages to the global array
|
||||
for (int i = 0; i < 96; ++i) {
|
||||
datalayer.battery.status.cell_voltages_mV[i] = cell_voltages[i];
|
||||
}
|
||||
|
||||
/*Extra safety functions below*/
|
||||
if (LB_GIDS < 10) //700Wh left in battery!
|
||||
|
@ -237,17 +211,6 @@ void update_values_battery() { /* This function maps all the values fetched via
|
|||
datalayer.battery.status.max_discharge_power_W = 0;
|
||||
}
|
||||
|
||||
//Check if SOC% is plausible
|
||||
if (datalayer.battery.status.voltage_dV >
|
||||
(datalayer.battery.info.max_design_voltage_dV -
|
||||
100)) { // When pack voltage is close to max, and SOC% is still low, raise FAULT
|
||||
if (LB_SOC < 650) {
|
||||
set_event(EVENT_SOC_PLAUSIBILITY_ERROR, LB_SOC / 10); // Set event with the SOC as data
|
||||
} else {
|
||||
clear_event(EVENT_SOC_PLAUSIBILITY_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
if (LB_Full_CHARGE_flag) { //Battery reports that it is fully charged stop all further charging incase it hasn't already
|
||||
set_event(EVENT_BATTERY_FULL, 0);
|
||||
datalayer.battery.status.max_charge_power_W = 0;
|
||||
|
@ -308,14 +271,6 @@ void update_values_battery() { /* This function maps all the values fetched via
|
|||
clear_event(EVENT_BATTERY_CHG_DISCHG_STOP_REQ);
|
||||
}
|
||||
|
||||
if (LB_StateOfHealth < 25) { //Battery is extremely degraded, not fit for secondlifestorage. Zero it all out.
|
||||
if (LB_StateOfHealth != 0) { //Extra check to see that we actually have a SOH Value available
|
||||
set_event(EVENT_LOW_SOH, LB_StateOfHealth);
|
||||
} else {
|
||||
clear_event(EVENT_LOW_SOH);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef INTERLOCK_REQUIRED
|
||||
if (!LB_Interlock) {
|
||||
set_event(EVENT_HVIL_FAILURE, 0);
|
||||
|
@ -324,19 +279,6 @@ void update_values_battery() { /* This function maps all the values fetched via
|
|||
}
|
||||
#endif
|
||||
|
||||
/* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/
|
||||
if (!CANstillAlive) {
|
||||
set_event(EVENT_CAN_RX_FAILURE, 0);
|
||||
} else {
|
||||
CANstillAlive--;
|
||||
clear_event(EVENT_CAN_RX_FAILURE);
|
||||
}
|
||||
if (CANerror >
|
||||
MAX_CAN_FAILURES) //Also check if we have recieved too many malformed CAN messages. If so, signal via LED
|
||||
{
|
||||
set_event(EVENT_CAN_RX_WARNING, 0);
|
||||
}
|
||||
|
||||
if (LB_HeatExist) {
|
||||
if (LB_Heating_Stop) {
|
||||
set_event(EVENT_BATTERY_WARMED_UP, 0);
|
||||
|
@ -346,12 +288,6 @@ void update_values_battery() { /* This function maps all the values fetched via
|
|||
}
|
||||
}
|
||||
|
||||
if (datalayer.battery.status.bms_status ==
|
||||
FAULT) { //Incase we enter a critical fault state, zero out the allowed limits
|
||||
datalayer.battery.status.max_charge_power_W = 0;
|
||||
datalayer.battery.status.max_discharge_power_W = 0;
|
||||
}
|
||||
|
||||
/*Finally print out values to serial if configured to do so*/
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("Values from battery");
|
||||
|
@ -361,7 +297,6 @@ void update_values_battery() { /* This function maps all the values fetched via
|
|||
print_with_units(", Has heater: ", LB_HeatExist, " ");
|
||||
print_with_units(", Max cell voltage: ", min_max_voltage[1], "mV ");
|
||||
print_with_units(", Min cell voltage: ", min_max_voltage[0], "mV ");
|
||||
print_with_units(", Cell deviation: ", cell_deviation_mV, "mV ");
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -369,7 +304,7 @@ void receive_can_battery(CAN_frame_t rx_frame) {
|
|||
switch (rx_frame.MsgID) {
|
||||
case 0x1DB:
|
||||
if (is_message_corrupt(rx_frame)) {
|
||||
CANerror++;
|
||||
datalayer.battery.status.CAN_error_counter++;
|
||||
break; //Message content malformed, abort reading data from it
|
||||
}
|
||||
LB_Current2 = (rx_frame.data.u8[0] << 3) | (rx_frame.data.u8[1] & 0xe0) >> 5;
|
||||
|
@ -394,7 +329,7 @@ void receive_can_battery(CAN_frame_t rx_frame) {
|
|||
break;
|
||||
case 0x1DC:
|
||||
if (is_message_corrupt(rx_frame)) {
|
||||
CANerror++;
|
||||
datalayer.battery.status.CAN_error_counter++;
|
||||
break; //Message content malformed, abort reading data from it
|
||||
}
|
||||
LB_Discharge_Power_Limit = ((rx_frame.data.u8[0] << 2 | rx_frame.data.u8[1] >> 6) / 4.0);
|
||||
|
@ -403,7 +338,7 @@ void receive_can_battery(CAN_frame_t rx_frame) {
|
|||
break;
|
||||
case 0x55B:
|
||||
if (is_message_corrupt(rx_frame)) {
|
||||
CANerror++;
|
||||
datalayer.battery.status.CAN_error_counter++;
|
||||
break; //Message content malformed, abort reading data from it
|
||||
}
|
||||
LB_TEMP = (rx_frame.data.u8[0] << 2 | rx_frame.data.u8[1] >> 6);
|
||||
|
@ -413,7 +348,8 @@ void receive_can_battery(CAN_frame_t rx_frame) {
|
|||
LB_Capacity_Empty = (bool)((rx_frame.data.u8[6] & 0x80) >> 7);
|
||||
break;
|
||||
case 0x5BC:
|
||||
CANstillAlive = 12; //Indicate that we are still getting CAN messages from the BMS
|
||||
can_bus_alive = true;
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; // Let system know battery is sending CAN
|
||||
|
||||
LB_MAX = ((rx_frame.data.u8[5] & 0x10) >> 4);
|
||||
if (LB_MAX) {
|
||||
|
@ -466,7 +402,7 @@ void receive_can_battery(CAN_frame_t rx_frame) {
|
|||
LEAF_Battery_Type = ZE1_BATTERY;
|
||||
break;
|
||||
case 0x79B:
|
||||
stop_battery_query = 1; //Someone is trying to read data with Leafspy, stop our own polling!
|
||||
stop_battery_query = true; //Someone is trying to read data with Leafspy, stop our own polling!
|
||||
hold_off_with_polling_10seconds = 10; //Polling is paused for 100s
|
||||
break;
|
||||
case 0x7BB:
|
||||
|
@ -512,6 +448,11 @@ void receive_can_battery(CAN_frame_t rx_frame) {
|
|||
}
|
||||
if (rx_frame.data.u8[6] == 0xFF && rx_frame.data.u8[0] == 0x2C) { //Last frame
|
||||
//Last frame does not contain any cell data, calculate the result
|
||||
|
||||
//Map all cell voltages to the global array
|
||||
memcpy(datalayer.battery.status.cell_voltages_mV, cell_voltages, 96 * sizeof(uint16_t));
|
||||
|
||||
//calculate min/max voltages
|
||||
min_max_voltage[0] = 9999;
|
||||
min_max_voltage[1] = 0;
|
||||
for (cellcounter = 0; cellcounter < 96; cellcounter++) {
|
||||
|
@ -521,15 +462,9 @@ void receive_can_battery(CAN_frame_t rx_frame) {
|
|||
min_max_voltage[1] = cell_voltages[cellcounter];
|
||||
}
|
||||
|
||||
cell_deviation_mV = (min_max_voltage[1] - min_max_voltage[0]);
|
||||
|
||||
datalayer.battery.status.cell_max_voltage_mV = min_max_voltage[1];
|
||||
datalayer.battery.status.cell_min_voltage_mV = min_max_voltage[0];
|
||||
|
||||
if (cell_deviation_mV > MAX_CELL_DEVIATION) {
|
||||
set_event(EVENT_CELL_DEVIATION_HIGH, 0);
|
||||
}
|
||||
|
||||
if (min_max_voltage[1] >= MAX_CELL_VOLTAGE) {
|
||||
set_event(EVENT_CELL_OVER_VOLTAGE, 0);
|
||||
}
|
||||
|
@ -615,6 +550,8 @@ void receive_can_battery(CAN_frame_t rx_frame) {
|
|||
}
|
||||
}
|
||||
void send_can_battery() {
|
||||
if (can_bus_alive) {
|
||||
|
||||
unsigned long currentMillis = millis();
|
||||
|
||||
//Send 10ms message
|
||||
|
@ -622,6 +559,8 @@ void send_can_battery() {
|
|||
// Check if sending of CAN messages has been delayed too much.
|
||||
if ((currentMillis - previousMillis10 >= INTERVAL_10_MS_DELAYED) && (currentMillis > BOOTUP_TIME)) {
|
||||
set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis10));
|
||||
} else {
|
||||
clear_event(EVENT_CAN_OVERRUN);
|
||||
}
|
||||
previousMillis10 = currentMillis;
|
||||
|
||||
|
@ -797,7 +736,8 @@ void send_can_battery() {
|
|||
if (hold_off_with_polling_10seconds > 0) {
|
||||
hold_off_with_polling_10seconds--;
|
||||
} else {
|
||||
stop_battery_query = 0;
|
||||
stop_battery_query = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
|
||||
#define BATTERY_SELECTED
|
||||
#define MAX_CELL_DEVIATION_MV 500
|
||||
|
||||
uint16_t Temp_fromRAW_to_F(uint16_t temperature);
|
||||
bool is_message_corrupt(CAN_frame_t rx_frame);
|
||||
|
|
196
Software/src/battery/PYLON-BATTERY.cpp
Normal file
196
Software/src/battery/PYLON-BATTERY.cpp
Normal file
|
@ -0,0 +1,196 @@
|
|||
#include "../include.h"
|
||||
#ifdef PYLON_BATTERY
|
||||
#include "../datalayer/datalayer.h"
|
||||
#include "../devboard/utils/events.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
#include "PYLON-BATTERY.h"
|
||||
|
||||
/* Do not change code below unless you are sure what you are doing */
|
||||
static unsigned long previousMillis1000 = 0; // will store last time a 1s CAN Message was sent
|
||||
|
||||
//Actual content messages
|
||||
CAN_frame_t PYLON_3010 = {.FIR = {.B =
|
||||
{
|
||||
.DLC = 8,
|
||||
.FF = CAN_frame_ext,
|
||||
}},
|
||||
.MsgID = 0x3010,
|
||||
.data = {0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
CAN_frame_t PYLON_8200 = {.FIR = {.B =
|
||||
{
|
||||
.DLC = 8,
|
||||
.FF = CAN_frame_ext,
|
||||
}},
|
||||
.MsgID = 0x8200,
|
||||
.data = {0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
CAN_frame_t PYLON_8210 = {.FIR = {.B =
|
||||
{
|
||||
.DLC = 8,
|
||||
.FF = CAN_frame_ext,
|
||||
}},
|
||||
.MsgID = 0x8210,
|
||||
.data = {0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
CAN_frame_t PYLON_4200 = {.FIR = {.B =
|
||||
{
|
||||
.DLC = 8,
|
||||
.FF = CAN_frame_ext,
|
||||
}},
|
||||
.MsgID = 0x4200,
|
||||
.data = {0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
|
||||
static int16_t celltemperature_max_dC = 0;
|
||||
static int16_t celltemperature_min_dC = 0;
|
||||
static int16_t current_dA = 0;
|
||||
static uint16_t voltage_dV = 0;
|
||||
static uint16_t cellvoltage_max_mV = 0;
|
||||
static uint16_t cellvoltage_min_mV = 0;
|
||||
static uint16_t charge_cutoff_voltage = 0;
|
||||
static uint16_t discharge_cutoff_voltage = 0;
|
||||
static int16_t max_charge_current = 0;
|
||||
static int16_t max_discharge_current = 0;
|
||||
static uint8_t ensemble_info_ack = 0;
|
||||
static uint8_t battery_module_quantity = 0;
|
||||
static uint8_t battery_modules_in_series = 0;
|
||||
static uint8_t cell_quantity_in_module = 0;
|
||||
static uint8_t voltage_level = 0;
|
||||
static uint8_t ah_number = 0;
|
||||
static uint8_t SOC = 0;
|
||||
static uint8_t SOH = 0;
|
||||
static uint8_t charge_forbidden = 0;
|
||||
static uint8_t discharge_forbidden = 0;
|
||||
|
||||
void update_values_battery() {
|
||||
|
||||
datalayer.battery.status.real_soc = (SOC * 100); //increase SOC range from 0-100 -> 100.00
|
||||
|
||||
datalayer.battery.status.soh_pptt = (SOH * 100); //Increase decimals from 100% -> 100.00%
|
||||
|
||||
datalayer.battery.status.voltage_dV = voltage_dV; //value is *10 (3700 = 370.0)
|
||||
|
||||
datalayer.battery.status.current_dA = current_dA; //value is *10 (150 = 15.0) , invert the sign
|
||||
|
||||
datalayer.battery.status.active_power_W = //Power in watts, Negative = charging batt
|
||||
((datalayer.battery.status.voltage_dV * datalayer.battery.status.current_dA) / 100);
|
||||
|
||||
datalayer.battery.status.max_charge_power_W = (max_charge_current * (voltage_dV / 10));
|
||||
|
||||
datalayer.battery.status.max_discharge_power_W = (-max_discharge_current * (voltage_dV / 10));
|
||||
|
||||
datalayer.battery.status.cell_max_voltage_mV = cellvoltage_max_mV;
|
||||
|
||||
datalayer.battery.status.cell_min_voltage_mV = cellvoltage_min_mV;
|
||||
|
||||
datalayer.battery.status.temperature_min_dC = celltemperature_min_dC;
|
||||
|
||||
datalayer.battery.status.temperature_max_dC = celltemperature_max_dC;
|
||||
|
||||
datalayer.battery.info.max_design_voltage_dV = charge_cutoff_voltage;
|
||||
|
||||
datalayer.battery.info.min_design_voltage_dV = discharge_cutoff_voltage;
|
||||
}
|
||||
|
||||
void receive_can_battery(CAN_frame_t rx_frame) {
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
switch (rx_frame.MsgID) {
|
||||
case 0x7310:
|
||||
case 0x7311:
|
||||
ensemble_info_ack = true;
|
||||
// This message contains software/hardware version info. No interest to us
|
||||
break;
|
||||
case 0x7320:
|
||||
case 0x7321:
|
||||
ensemble_info_ack = true;
|
||||
battery_module_quantity = rx_frame.data.u8[0];
|
||||
battery_modules_in_series = rx_frame.data.u8[2];
|
||||
cell_quantity_in_module = rx_frame.data.u8[3];
|
||||
voltage_level = rx_frame.data.u8[4];
|
||||
ah_number = rx_frame.data.u8[6];
|
||||
break;
|
||||
case 0x4210:
|
||||
case 0x4211:
|
||||
voltage_dV = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]);
|
||||
current_dA = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]) - 30000;
|
||||
SOC = rx_frame.data.u8[6];
|
||||
SOH = rx_frame.data.u8[7];
|
||||
break;
|
||||
case 0x4220:
|
||||
case 0x4221:
|
||||
charge_cutoff_voltage = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]);
|
||||
discharge_cutoff_voltage = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]);
|
||||
max_charge_current = (((rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4]) * 0.1) - 3000;
|
||||
max_discharge_current = (((rx_frame.data.u8[7] << 8) | rx_frame.data.u8[6]) * 0.1) - 3000;
|
||||
break;
|
||||
case 0x4230:
|
||||
case 0x4231:
|
||||
cellvoltage_max_mV = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]);
|
||||
cellvoltage_min_mV = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]);
|
||||
break;
|
||||
case 0x4240:
|
||||
case 0x4241:
|
||||
celltemperature_max_dC = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]) - 1000;
|
||||
celltemperature_min_dC = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]) - 1000;
|
||||
break;
|
||||
case 0x4250:
|
||||
case 0x4251:
|
||||
//Byte0 Basic Status
|
||||
//Byte1-2 Cycle Period
|
||||
//Byte3 Error
|
||||
//Byte4-5 Alarm
|
||||
//Byte6-7 Protection
|
||||
break;
|
||||
case 0x4260:
|
||||
case 0x4261:
|
||||
//Byte0-1 Module Max Voltage
|
||||
//Byte2-3 Module Min Voltage
|
||||
//Byte4-5 Module Max. Voltage Number
|
||||
//Byte6-7 Module Min. Voltage Number
|
||||
break;
|
||||
case 0x4270:
|
||||
case 0x4271:
|
||||
//Byte0-1 Module Max. Temperature
|
||||
//Byte2-3 Module Min. Temperature
|
||||
//Byte4-5 Module Max. Temperature Number
|
||||
//Byte6-7 Module Min. Temperature Number
|
||||
break;
|
||||
case 0x4280:
|
||||
case 0x4281:
|
||||
charge_forbidden = rx_frame.data.u8[0];
|
||||
discharge_forbidden = rx_frame.data.u8[1];
|
||||
break;
|
||||
case 0x4290:
|
||||
case 0x4291:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void send_can_battery() {
|
||||
unsigned long currentMillis = millis();
|
||||
// Send 1s CAN Message
|
||||
if (currentMillis - previousMillis1000 >= INTERVAL_1_S) {
|
||||
|
||||
previousMillis1000 = currentMillis;
|
||||
|
||||
ESP32Can.CANWriteFrame(&PYLON_3010); // Heartbeat
|
||||
ESP32Can.CANWriteFrame(&PYLON_4200); // Ensemble OR System equipment info, depends on frame0
|
||||
ESP32Can.CANWriteFrame(&PYLON_8200); // Control device quit sleep status
|
||||
ESP32Can.CANWriteFrame(&PYLON_8210); // Charge command
|
||||
|
||||
if (ensemble_info_ack) {
|
||||
PYLON_4200.data.u8[0] = 0x00; //Request system equipment info
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void setup_battery(void) { // Performs one time setup at startup
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("Pylon battery selected");
|
||||
#endif
|
||||
|
||||
datalayer.battery.info.max_design_voltage_dV = 4040; // 404.0V, charging over this is not possible
|
||||
datalayer.battery.info.min_design_voltage_dV = 3100; // 310.0V, under this, discharging further is disabled
|
||||
}
|
||||
|
||||
#endif
|
12
Software/src/battery/PYLON-BATTERY.h
Normal file
12
Software/src/battery/PYLON-BATTERY.h
Normal file
|
@ -0,0 +1,12 @@
|
|||
#ifndef PYLON_BATTERY_H
|
||||
#define PYLON_BATTERY_H
|
||||
#include <Arduino.h>
|
||||
#include "../include.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
|
||||
#define BATTERY_SELECTED
|
||||
#define MAX_CELL_DEVIATION_MV 9999
|
||||
|
||||
void setup_battery(void);
|
||||
|
||||
#endif
|
|
@ -6,10 +6,23 @@
|
|||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
#include "RENAULT-KANGOO-BATTERY.h"
|
||||
|
||||
/* TODO:
|
||||
There seems to be some values on the Kangoo that differ between the 22/33 kWh version
|
||||
- Find some way to autodetect which Kangoo size we are working with
|
||||
- Fix the mappings of values accordingly
|
||||
- Values still need fixing
|
||||
- SOC% is not valid on all packs
|
||||
-Try to use the 7BB value?
|
||||
- Max charge power is 0W on some packs
|
||||
- SOH% is too high on some packs
|
||||
- Add all cellvoltages from https://github.com/jamiejones85/Kangoo36_canDecode/tree/main
|
||||
|
||||
This page has info on the larger 33kWh pack: https://openinverter.org/wiki/Renault_Kangoo_36
|
||||
*/
|
||||
|
||||
/* Do not change code below unless you are sure what you are doing */
|
||||
static uint32_t LB_Battery_Voltage = 3700;
|
||||
static uint32_t LB_Charge_Power_Limit_Watts = 0;
|
||||
static uint32_t LB_Discharge_Power_Limit_Watts = 0;
|
||||
static int32_t LB_Current = 0;
|
||||
static int16_t LB_MAX_TEMPERATURE = 0;
|
||||
static int16_t LB_MIN_TEMPERATURE = 0;
|
||||
|
@ -20,10 +33,19 @@ static uint16_t LB_Charge_Power_Limit = 0;
|
|||
static uint16_t LB_kWh_Remaining = 0;
|
||||
static uint16_t LB_Cell_Max_Voltage = 3700;
|
||||
static uint16_t LB_Cell_Min_Voltage = 3700;
|
||||
static uint16_t cell_deviation_mV = 0; //contains the deviation between highest and lowest cell in mV
|
||||
static uint8_t CANstillAlive = 12; //counter for checking if CAN is still alive
|
||||
static uint16_t LB_MaxChargeAllowed_W = 0;
|
||||
static uint8_t LB_Discharge_Power_Limit_Byte1 = 0;
|
||||
static uint8_t GVI_Pollcounter = 0;
|
||||
static uint8_t LB_EOCR = 0;
|
||||
static uint8_t LB_HVBUV = 0;
|
||||
static uint8_t LB_HVBIR = 0;
|
||||
static uint8_t LB_CUV = 0;
|
||||
static uint8_t LB_COV = 0;
|
||||
static uint8_t LB_HVBOV = 0;
|
||||
static uint8_t LB_HVBOT = 0;
|
||||
static uint8_t LB_HVBOC = 0;
|
||||
static uint8_t LB_MaxInput_kW = 0;
|
||||
static uint8_t LB_MaxOutput_kW = 0;
|
||||
static bool GVB_79B_Continue = false;
|
||||
|
||||
CAN_frame_t KANGOO_423 = {.FIR = {.B =
|
||||
|
@ -32,7 +54,11 @@ CAN_frame_t KANGOO_423 = {.FIR = {.B =
|
|||
.FF = CAN_frame_std,
|
||||
}},
|
||||
.MsgID = 0x423,
|
||||
.data = {0x33, 0x00, 0xFF, 0xFF, 0x00, 0xE0, 0x00, 0x00}};
|
||||
.data = {0x0B, 0x1D, 0x00, 0x02, 0xB2, 0x20, 0xB2, 0xD9}}; // Charging
|
||||
// Driving: 0x07 0x1D 0x00 0x02 0x5D 0x80 0x5D 0xD8
|
||||
// Charging: 0x0B 0x1D 0x00 0x02 0xB2 0x20 0xB2 0xD9
|
||||
// Fastcharging: 0x07 0x1E 0x00 0x01 0x5D 0x20 0xB2 0xC7
|
||||
// Old hardcoded message: .data = {0x33, 0x00, 0xFF, 0xFF, 0x00, 0xE0, 0x00, 0x00}};
|
||||
CAN_frame_t KANGOO_79B = {.FIR = {.B =
|
||||
{
|
||||
.DLC = 8,
|
||||
|
@ -53,47 +79,32 @@ static unsigned long previousMillis100 = 0; // will store last time a 100ms CA
|
|||
static unsigned long previousMillis1000 = 0; // will store last time a 1000ms CAN Message was sent
|
||||
static unsigned long GVL_pause = 0;
|
||||
|
||||
void update_values_battery() { //This function maps all the values fetched via CAN to the correct parameters used for modbus
|
||||
void update_values_battery() { //This function maps all the values fetched via CAN to the correct parameters
|
||||
|
||||
datalayer.battery.status.real_soc = (LB_SOC * 10); //increase LB_SOC range from 0-100.0 -> 100.00
|
||||
datalayer.battery.status.real_soc = (LB_SOC * 100); //increase LB_SOC range from 0-100 -> 100.00
|
||||
|
||||
datalayer.battery.status.soh_pptt = (LB_SOH * 100); //Increase range from 99% -> 99.00%
|
||||
if (datalayer.battery.status.soh_pptt > 10000) { // Cap value if glitched out
|
||||
datalayer.battery.status.soh_pptt = 10000;
|
||||
}
|
||||
|
||||
datalayer.battery.status.voltage_dV = LB_Battery_Voltage;
|
||||
|
||||
datalayer.battery.status.current_dA = LB_Current;
|
||||
datalayer.battery.status.current_dA = LB_Current * 10;
|
||||
|
||||
datalayer.battery.status.remaining_capacity_Wh = static_cast<uint32_t>(
|
||||
(static_cast<double>(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh);
|
||||
|
||||
LB_Discharge_Power_Limit_Watts = (LB_Discharge_Power_Limit * 500); //Convert value fetched from battery to watts
|
||||
/* Define power able to be discharged from battery */
|
||||
if (LB_Discharge_Power_Limit_Watts > 60000) //if >60kW can be pulled from battery
|
||||
{
|
||||
datalayer.battery.status.max_discharge_power_W = 60000; //cap value so we don't go over the uint16 limit
|
||||
} else {
|
||||
datalayer.battery.status.max_discharge_power_W = LB_Discharge_Power_Limit_Watts;
|
||||
}
|
||||
if (datalayer.battery.status.reported_soc == 0) //Scaled SOC% value is 0.00%, we should not discharge battery further
|
||||
{
|
||||
datalayer.battery.status.max_discharge_power_W = 0;
|
||||
}
|
||||
datalayer.battery.status.max_discharge_power_W =
|
||||
(LB_Discharge_Power_Limit * 500); //Convert value fetched from battery to watts
|
||||
|
||||
LB_Charge_Power_Limit_Watts = (LB_Charge_Power_Limit * 500); //Convert value fetched from battery to watts
|
||||
/* Define power able to be put into the battery */
|
||||
if (LB_Charge_Power_Limit_Watts > 60000) //if >60kW can be put into the battery
|
||||
{
|
||||
datalayer.battery.status.max_charge_power_W = 60000; //cap value so we don't go over the uint16 limit
|
||||
} else {
|
||||
datalayer.battery.status.max_charge_power_W = LB_Charge_Power_Limit_Watts;
|
||||
}
|
||||
if (datalayer.battery.status.reported_soc == 10000) //Scaled SOC% value is 100.00%
|
||||
{
|
||||
datalayer.battery.status.max_charge_power_W = 0; //No need to charge further, set max power to 0
|
||||
}
|
||||
//The above value is 0 on some packs. We instead hardcode this now.
|
||||
datalayer.battery.status.max_charge_power_W = MAX_CHARGE_POWER_W;
|
||||
|
||||
datalayer.battery.status.active_power_W =
|
||||
(datalayer.battery.status.voltage_dV * LB_Current); //TODO: check if scaling is OK
|
||||
((datalayer.battery.status.voltage_dV * datalayer.battery.status.current_dA) / 100);
|
||||
|
||||
datalayer.battery.status.temperature_min_dC = (LB_MIN_TEMPERATURE * 10);
|
||||
|
||||
|
@ -103,26 +114,11 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
|
||||
datalayer.battery.status.cell_max_voltage_mV = LB_Cell_Max_Voltage;
|
||||
|
||||
cell_deviation_mV = (datalayer.battery.status.temperature_max_dC - datalayer.battery.status.temperature_min_dC);
|
||||
|
||||
/* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/
|
||||
if (!CANstillAlive) {
|
||||
set_event(EVENT_CAN_RX_FAILURE, 0);
|
||||
} else {
|
||||
CANstillAlive--;
|
||||
clear_event(EVENT_CAN_RX_FAILURE);
|
||||
}
|
||||
|
||||
if (LB_Cell_Max_Voltage >= ABSOLUTE_CELL_MAX_VOLTAGE) {
|
||||
set_event(EVENT_CELL_OVER_VOLTAGE, 0);
|
||||
set_event(EVENT_CELL_OVER_VOLTAGE, (LB_Cell_Max_Voltage / 20));
|
||||
}
|
||||
if (LB_Cell_Min_Voltage <= ABSOLUTE_CELL_MIN_VOLTAGE) {
|
||||
set_event(EVENT_CELL_UNDER_VOLTAGE, 0);
|
||||
}
|
||||
if (cell_deviation_mV > MAX_CELL_DEVIATION_MV) {
|
||||
set_event(EVENT_CELL_DEVIATION_HIGH, 0);
|
||||
} else {
|
||||
clear_event(EVENT_CELL_DEVIATION_HIGH);
|
||||
set_event(EVENT_CELL_UNDER_VOLTAGE, (LB_Cell_Min_Voltage / 20));
|
||||
}
|
||||
|
||||
#ifdef DEBUG_VIA_USB
|
||||
|
@ -159,31 +155,41 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
#endif
|
||||
}
|
||||
|
||||
void receive_can_battery(CAN_frame_t rx_frame) //GKOE reworked
|
||||
{
|
||||
void receive_can_battery(CAN_frame_t rx_frame) {
|
||||
|
||||
switch (rx_frame.MsgID) {
|
||||
case 0x155: //BMS1
|
||||
CANstillAlive = 12; //Indicate that we are still getting CAN messages from the BMS
|
||||
datalayer.battery.status.CAN_battery_still_alive =
|
||||
CAN_STILL_ALIVE; //Indicate that we are still getting CAN messages from the BMS
|
||||
LB_MaxChargeAllowed_W = (rx_frame.data.u8[0] * 300);
|
||||
LB_Current = word((rx_frame.data.u8[1] & 0xF), rx_frame.data.u8[2]) * 0.25 - 500; //OK!
|
||||
|
||||
LB_SOC = ((rx_frame.data.u8[4] << 8) | (rx_frame.data.u8[5])) * 0.0025; //OK!
|
||||
break;
|
||||
|
||||
case 0x424: //BMS2
|
||||
CANstillAlive = 12; //Indicate that we are still getting CAN messages from the BMS
|
||||
LB_SOH = (rx_frame.data.u8[5]);
|
||||
datalayer.battery.status.CAN_battery_still_alive =
|
||||
CAN_STILL_ALIVE; //Indicate that we are still getting CAN messages from the BMS
|
||||
LB_EOCR = (rx_frame.data.u8[0] & 0x03);
|
||||
LB_HVBUV = (rx_frame.data.u8[0] & 0x0C) >> 2;
|
||||
LB_HVBIR = (rx_frame.data.u8[0] & 0x30) >> 4;
|
||||
LB_CUV = (rx_frame.data.u8[0] & 0xC0) >> 6;
|
||||
LB_COV = (rx_frame.data.u8[1] & 0x03);
|
||||
LB_HVBOV = (rx_frame.data.u8[1] & 0x0C) >> 2;
|
||||
LB_HVBOT = (rx_frame.data.u8[1] & 0x30) >> 4;
|
||||
LB_HVBOC = (rx_frame.data.u8[1] & 0xC0) >> 6;
|
||||
LB_MaxInput_kW = rx_frame.data.u8[2] / 2;
|
||||
LB_MaxOutput_kW = rx_frame.data.u8[3] / 2;
|
||||
LB_SOH = (rx_frame.data.u8[5]); // Only seems valid on Kangoo33?
|
||||
LB_MIN_TEMPERATURE = ((rx_frame.data.u8[4]) - 40); //OK!
|
||||
LB_MAX_TEMPERATURE = ((rx_frame.data.u8[7]) - 40); //OK!
|
||||
break;
|
||||
|
||||
case 0x425:
|
||||
CANstillAlive = 12; //Indicate that we are still getting CAN messages from the BMS
|
||||
datalayer.battery.status.CAN_battery_still_alive =
|
||||
CAN_STILL_ALIVE; //Indicate that we are still getting CAN messages from the BMS
|
||||
LB_kWh_Remaining = word((rx_frame.data.u8[0] & 0x1), rx_frame.data.u8[1]) / 10; //OK!
|
||||
break;
|
||||
|
||||
case 0x445:
|
||||
CANstillAlive = 12; //Indicate that we are still getting CAN messages from the BMS
|
||||
datalayer.battery.status.CAN_battery_still_alive =
|
||||
CAN_STILL_ALIVE; //Indicate that we are still getting CAN messages from the BMS
|
||||
LB_Cell_Max_Voltage = 1000 + word((rx_frame.data.u8[3] & 0x1), rx_frame.data.u8[4]) * 10; //OK!
|
||||
LB_Cell_Min_Voltage = 1000 + (word(rx_frame.data.u8[5], rx_frame.data.u8[6]) >> 7) * 10; //OK!
|
||||
|
||||
|
@ -194,7 +200,8 @@ void receive_can_battery(CAN_frame_t rx_frame) //GKOE reworked
|
|||
}
|
||||
break;
|
||||
case 0x7BB:
|
||||
CANstillAlive = 12; //Indicate that we are still getting CAN messages from the BMS
|
||||
datalayer.battery.status.CAN_battery_still_alive =
|
||||
CAN_STILL_ALIVE; //Indicate that we are still getting CAN messages from the BMS
|
||||
|
||||
if (rx_frame.data.u8[0] == 0x10) { //1st response Bytes 0-7
|
||||
GVB_79B_Continue = true;
|
||||
|
|
|
@ -6,11 +6,10 @@
|
|||
|
||||
#define BATTERY_SELECTED
|
||||
|
||||
#define ABSOLUTE_CELL_MAX_VOLTAGE \
|
||||
4100 // Max Cell Voltage mV! if voltage goes over this, charging is not possible (goes into forced discharge)
|
||||
#define ABSOLUTE_CELL_MIN_VOLTAGE \
|
||||
3000 // Min Cell Voltage mV! if voltage goes under this, discharging further is disabled
|
||||
#define MAX_CELL_DEVIATION_MV 500 //LED turns yellow on the board if mv delta exceeds this value
|
||||
#define ABSOLUTE_CELL_MAX_VOLTAGE 4150 // If cellvoltage goes over this mV, we go into FAULT mode
|
||||
#define ABSOLUTE_CELL_MIN_VOLTAGE 2500 // If cellvoltage goes under this mV, we go into FAULT mode
|
||||
#define MAX_CELL_DEVIATION_MV 500 // If cell mV delta exceeds this, we go into WARNING mode
|
||||
#define MAX_CHARGE_POWER_W 5000 // Battery can be charged with this amount of power
|
||||
|
||||
void setup_battery(void);
|
||||
|
||||
|
|
|
@ -1,166 +0,0 @@
|
|||
#include "../include.h"
|
||||
#ifdef RENAULT_ZOE_BATTERY
|
||||
#include "../datalayer/datalayer.h"
|
||||
#include "../devboard/utils/events.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
#include "RENAULT-ZOE-BATTERY.h"
|
||||
|
||||
/* Do not change code below unless you are sure what you are doing */
|
||||
static uint8_t CANstillAlive = 12; //counter for checking if CAN is still alive
|
||||
static uint8_t errorCode = 0; //stores if we have an error code active from battery control logic
|
||||
static uint16_t LB_SOC = 50;
|
||||
static uint16_t LB_SOH = 99;
|
||||
static int16_t LB_MIN_TEMPERATURE = 0;
|
||||
static int16_t LB_MAX_TEMPERATURE = 0;
|
||||
static uint16_t LB_Discharge_Power_Limit = 0;
|
||||
static uint32_t LB_Discharge_Power_Limit_Watts = 0;
|
||||
static uint16_t LB_Charge_Power_Limit = 0;
|
||||
static uint32_t LB_Charge_Power_Limit_Watts = 0;
|
||||
static int32_t LB_Current = 0;
|
||||
static uint16_t LB_kWh_Remaining = 0;
|
||||
static uint16_t LB_Cell_Max_Voltage = 3700;
|
||||
static uint16_t LB_Cell_Min_Voltage = 3700;
|
||||
static uint16_t cell_deviation_mV = 0; //contains the deviation between highest and lowest cell in mV
|
||||
static uint32_t LB_Battery_Voltage = 3700;
|
||||
static uint8_t LB_Discharge_Power_Limit_Byte1 = 0;
|
||||
|
||||
CAN_frame_t ZOE_423 = {.FIR = {.B =
|
||||
{
|
||||
.DLC = 8,
|
||||
.FF = CAN_frame_std,
|
||||
}},
|
||||
.MsgID = 0x423,
|
||||
.data = {0x33, 0x00, 0xFF, 0xFF, 0x00, 0xE0, 0x00, 0x00}};
|
||||
|
||||
static unsigned long previousMillis10 = 0; // will store last time a 10ms CAN Message was sent
|
||||
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was sent
|
||||
static unsigned long previousMillis1000 = 0; // will store last time a 1000ms CAN Message was sent
|
||||
static unsigned long GVL_pause = 0;
|
||||
|
||||
void update_values_battery() { //This function maps all the values fetched via CAN to the correct parameters used for modbus
|
||||
datalayer.battery.status.soh_pptt = (LB_SOH * 100); //Increase range from 99% -> 99.00%
|
||||
|
||||
datalayer.battery.status.real_soc = (LB_SOC * 10); //increase LB_SOC range from 0-100.0 -> 100.00
|
||||
|
||||
datalayer.battery.status.voltage_dV = LB_Battery_Voltage;
|
||||
|
||||
datalayer.battery.status.current_dA = LB_Current;
|
||||
|
||||
//Calculate the remaining Wh amount from SOC% and max Wh value.
|
||||
datalayer.battery.status.remaining_capacity_Wh = static_cast<uint32_t>(
|
||||
(static_cast<double>(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh);
|
||||
|
||||
datalayer.battery.status.max_discharge_power_W;
|
||||
|
||||
datalayer.battery.status.max_charge_power_W;
|
||||
|
||||
datalayer.battery.status.active_power_W;
|
||||
|
||||
datalayer.battery.status.temperature_min_dC;
|
||||
|
||||
datalayer.battery.status.temperature_max_dC;
|
||||
|
||||
datalayer.battery.status.cell_min_voltage_mV;
|
||||
|
||||
datalayer.battery.status.cell_max_voltage_mV;
|
||||
|
||||
cell_deviation_mV = (datalayer.battery.status.cell_max_voltage_mV - datalayer.battery.status.cell_min_voltage_mV);
|
||||
|
||||
/* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/
|
||||
if (!CANstillAlive) {
|
||||
set_event(EVENT_CAN_RX_FAILURE, 0);
|
||||
} else {
|
||||
CANstillAlive--;
|
||||
clear_event(EVENT_CAN_RX_FAILURE);
|
||||
}
|
||||
|
||||
if (LB_Cell_Max_Voltage >= ABSOLUTE_CELL_MAX_VOLTAGE) {
|
||||
set_event(EVENT_CELL_OVER_VOLTAGE, 0);
|
||||
}
|
||||
if (LB_Cell_Min_Voltage <= ABSOLUTE_CELL_MIN_VOLTAGE) {
|
||||
set_event(EVENT_CELL_UNDER_VOLTAGE, 0);
|
||||
}
|
||||
if (cell_deviation_mV > MAX_CELL_DEVIATION_MV) {
|
||||
set_event(EVENT_CELL_DEVIATION_HIGH, 0);
|
||||
} else {
|
||||
clear_event(EVENT_CELL_DEVIATION_HIGH);
|
||||
}
|
||||
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("Values going to inverter:");
|
||||
Serial.print("SOH%: ");
|
||||
Serial.print(datalayer.battery.status.soh_pptt);
|
||||
Serial.print(", SOC% scaled: ");
|
||||
Serial.print(datalayer.battery.status.reported_soc);
|
||||
Serial.print(", Voltage: ");
|
||||
Serial.print(datalayer.battery.status.voltage_dV);
|
||||
Serial.print(", Max discharge power: ");
|
||||
Serial.print(datalayer.battery.status.max_discharge_power_W);
|
||||
Serial.print(", Max charge power: ");
|
||||
Serial.print(datalayer.battery.status.max_charge_power_W);
|
||||
Serial.print(", Max temp: ");
|
||||
Serial.print(datalayer.battery.status.temperature_max_dC);
|
||||
Serial.print(", Min temp: ");
|
||||
Serial.print(datalayer.battery.status.temperature_min_dC);
|
||||
Serial.print(", BMS Status (3=OK): ");
|
||||
Serial.print(datalayer.battery.status.bms_status);
|
||||
|
||||
Serial.println("Battery values: ");
|
||||
Serial.print("Real SOC: ");
|
||||
Serial.print(LB_SOC);
|
||||
Serial.print(", Current: ");
|
||||
Serial.print(LB_Current);
|
||||
Serial.print(", kWh remain: ");
|
||||
Serial.print(LB_kWh_Remaining);
|
||||
Serial.print(", max mV: ");
|
||||
Serial.print(LB_Cell_Max_Voltage);
|
||||
Serial.print(", min mV: ");
|
||||
Serial.print(LB_Cell_Min_Voltage);
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
void receive_can_battery(CAN_frame_t rx_frame) {
|
||||
|
||||
switch (rx_frame.MsgID) {
|
||||
case 0x42E: //HV SOC & Battery Temp & Charging Power
|
||||
break;
|
||||
case 0x430: //HVBatteryCoolingState & HVBatteryEvapTemp & HVBatteryEvapSetpoint
|
||||
break;
|
||||
case 0x432: //BatVEShutDownAlert & HVBatCondPriorityLevel & HVBatteryLevelAlert & HVBatCondPriorityLevel & HVBatteryConditioningMode
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void send_can_battery() {
|
||||
unsigned long currentMillis = millis();
|
||||
// Send 100ms CAN Message
|
||||
if (currentMillis - previousMillis100 >= INTERVAL_100_MS) {
|
||||
// Check if sending of CAN messages has been delayed too much.
|
||||
if ((currentMillis - previousMillis100 >= INTERVAL_100_MS_DELAYED) && (currentMillis > BOOTUP_TIME)) {
|
||||
set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis100));
|
||||
}
|
||||
previousMillis100 = currentMillis;
|
||||
//ESP32Can.CANWriteFrame(&ZOE_423);
|
||||
}
|
||||
// 1000ms CAN handling
|
||||
if (currentMillis - previousMillis1000 >= INTERVAL_1_S) {
|
||||
previousMillis1000 = currentMillis;
|
||||
//ESP32Can.CANWriteFrame(&ZOE_423);
|
||||
}
|
||||
}
|
||||
|
||||
void setup_battery(void) { // Performs one time setup at startup
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("Renault Zoe battery selected");
|
||||
#endif
|
||||
|
||||
datalayer.battery.info.max_design_voltage_dV =
|
||||
4040; // 404.0V, over this, charging is not possible (goes into forced discharge)
|
||||
datalayer.battery.info.min_design_voltage_dV = 3100; // 310.0V under this, discharging further is disabled
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1,16 +0,0 @@
|
|||
#ifndef RENAULT_ZOE_BATTERY_H
|
||||
#define RENAULT_ZOE_BATTERY_H
|
||||
#include "../include.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
|
||||
#define BATTERY_SELECTED
|
||||
|
||||
#define ABSOLUTE_CELL_MAX_VOLTAGE \
|
||||
4100 // Max Cell Voltage mV! if voltage goes over this, charging is not possible (goes into forced discharge)
|
||||
#define ABSOLUTE_CELL_MIN_VOLTAGE \
|
||||
3000 // Min Cell Voltage mV! if voltage goes under this, discharging further is disabled
|
||||
#define MAX_CELL_DEVIATION_MV 500 //LED turns yellow on the board if mv delta exceeds this value
|
||||
|
||||
void setup_battery(void);
|
||||
|
||||
#endif
|
144
Software/src/battery/RENAULT-ZOE-GEN1-BATTERY.cpp
Normal file
144
Software/src/battery/RENAULT-ZOE-GEN1-BATTERY.cpp
Normal file
|
@ -0,0 +1,144 @@
|
|||
#include "../include.h"
|
||||
#ifdef RENAULT_ZOE_GEN1_BATTERY
|
||||
#include "../datalayer/datalayer.h"
|
||||
#include "../devboard/utils/events.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
#include "RENAULT-ZOE-GEN1-BATTERY.h"
|
||||
|
||||
/* Information in this file is based of the OVMS V3 vehicle_renaultzoe.cpp component
|
||||
https://github.com/openvehicles/Open-Vehicle-Monitoring-System-3/blob/master/vehicle/OVMS.V3/components/vehicle_renaultzoe/src/vehicle_renaultzoe.cpp
|
||||
/*
|
||||
|
||||
/* Do not change code below unless you are sure what you are doing */
|
||||
static uint16_t LB_SOC = 50;
|
||||
static uint16_t LB_SOH = 99;
|
||||
static int16_t LB_Average_Temperature = 0;
|
||||
static uint32_t LB_Charge_Power_W = 0;
|
||||
static int32_t LB_Current = 0;
|
||||
static uint16_t LB_kWh_Remaining = 0;
|
||||
static uint16_t LB_Cell_Max_Voltage = 3700;
|
||||
static uint16_t LB_Cell_Min_Voltage = 3700;
|
||||
static uint16_t LB_Battery_Voltage = 3700;
|
||||
|
||||
CAN_frame_t ZOE_423 = {.FIR = {.B =
|
||||
{
|
||||
.DLC = 8,
|
||||
.FF = CAN_frame_std,
|
||||
}},
|
||||
.MsgID = 0x423,
|
||||
.data = {0x07, 0x1d, 0x00, 0x02, 0x5d, 0x80, 0x5d, 0xc8}};
|
||||
CAN_frame_t ZOE_POLL_79B = {.FIR = {.B =
|
||||
{
|
||||
.DLC = 8,
|
||||
.FF = CAN_frame_std,
|
||||
}},
|
||||
.MsgID = 0x79B, //0x41 = cell bat module 1-62 , 0x42 = cell bat module 63-96
|
||||
.data = {0x02, 0x21, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
|
||||
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was sent
|
||||
static unsigned long previousMillis5000 = 0; // will store last time a 1000ms CAN Message was sent
|
||||
static uint8_t counter_423 = 0;
|
||||
|
||||
void update_values_battery() { //This function maps all the values fetched via CAN to the correct parameters used for modbus
|
||||
datalayer.battery.status.soh_pptt = (LB_SOH * 100); // Increase range from 99% -> 99.00%
|
||||
|
||||
datalayer.battery.status.real_soc = (LB_SOC * 10); // Increase LB_SOC range from 0-100.0 -> 100.00
|
||||
|
||||
datalayer.battery.status.voltage_dV = LB_Battery_Voltage;
|
||||
|
||||
datalayer.battery.status.current_dA = LB_Current; //TODO: Take from CAN
|
||||
|
||||
//Calculate the remaining Wh amount from SOC% and max Wh value.
|
||||
datalayer.battery.status.remaining_capacity_Wh = static_cast<uint32_t>(
|
||||
(static_cast<double>(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh);
|
||||
|
||||
datalayer.battery.status.max_discharge_power_W = 5000; //TODO: Take from CAN
|
||||
|
||||
datalayer.battery.status.max_charge_power_W = LB_Charge_Power_W;
|
||||
|
||||
datalayer.battery.status.active_power_W;
|
||||
|
||||
datalayer.battery.status.temperature_min_dC = LB_Average_Temperature;
|
||||
|
||||
datalayer.battery.status.temperature_max_dC = LB_Average_Temperature;
|
||||
|
||||
datalayer.battery.status.cell_min_voltage_mV;
|
||||
|
||||
datalayer.battery.status.cell_max_voltage_mV;
|
||||
|
||||
if (LB_Cell_Max_Voltage >= ABSOLUTE_CELL_MAX_VOLTAGE) {
|
||||
set_event(EVENT_CELL_OVER_VOLTAGE, 0);
|
||||
}
|
||||
if (LB_Cell_Min_Voltage <= ABSOLUTE_CELL_MIN_VOLTAGE) {
|
||||
set_event(EVENT_CELL_UNDER_VOLTAGE, 0);
|
||||
}
|
||||
|
||||
#ifdef DEBUG_VIA_USB
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
void receive_can_battery(CAN_frame_t rx_frame) {
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
switch (rx_frame.MsgID) {
|
||||
case 0x427:
|
||||
LB_Charge_Power_W = rx_frame.data.u8[5] * 300;
|
||||
LB_kWh_Remaining = (((((rx_frame.data.u8[6] << 8) | (rx_frame.data.u8[7])) >> 6) & 0x3ff) * 0.1);
|
||||
break;
|
||||
case 0x42E: //HV SOC & Battery Temp & Charging Power
|
||||
LB_Battery_Voltage = (((((rx_frame.data.u8[3] << 8) | (rx_frame.data.u8[4])) >> 5) & 0x3ff) * 0.5); //0.5V/bit
|
||||
LB_Average_Temperature = (((((rx_frame.data.u8[5] << 8) | (rx_frame.data.u8[6])) >> 5) & 0x7F) - 40);
|
||||
break;
|
||||
case 0x654: //SOC
|
||||
LB_SOC = rx_frame.data.u8[3];
|
||||
break;
|
||||
case 0x658: //SOH
|
||||
LB_SOH = (rx_frame.data.u8[4] & 0x7F);
|
||||
break;
|
||||
case 0x7BB: //Reply from active polling
|
||||
// TODO: Handle the cellvoltages
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void send_can_battery() {
|
||||
unsigned long currentMillis = millis();
|
||||
// Send 100ms CAN Message
|
||||
if (currentMillis - previousMillis100 >= INTERVAL_100_MS) {
|
||||
// Check if sending of CAN messages has been delayed too much.
|
||||
if ((currentMillis - previousMillis100 >= INTERVAL_100_MS_DELAYED) && (currentMillis > BOOTUP_TIME)) {
|
||||
set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis100));
|
||||
}
|
||||
previousMillis100 = currentMillis;
|
||||
ESP32Can.CANWriteFrame(&ZOE_423);
|
||||
|
||||
if ((counter_423 / 5) % 2 == 0) { // Alternate every 5 messages between these two
|
||||
ZOE_423.data.u8[4] = 0xB2;
|
||||
ZOE_423.data.u8[6] = 0xB2;
|
||||
} else {
|
||||
ZOE_423.data.u8[4] = 0x5D;
|
||||
ZOE_423.data.u8[6] = 0x5D;
|
||||
}
|
||||
counter_423 = (counter_423 + 1) % 10;
|
||||
}
|
||||
// 5000ms CAN handling
|
||||
if (currentMillis - previousMillis5000 >= INTERVAL_5_S) {
|
||||
previousMillis5000 = currentMillis;
|
||||
ESP32Can.CANWriteFrame(&ZOE_POLL_79B);
|
||||
}
|
||||
}
|
||||
|
||||
void setup_battery(void) { // Performs one time setup at startup
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("Renault Zoe 22/40kWh battery selected");
|
||||
#endif
|
||||
|
||||
datalayer.battery.info.max_design_voltage_dV = 4040;
|
||||
datalayer.battery.info.min_design_voltage_dV = 2700;
|
||||
}
|
||||
|
||||
#endif
|
14
Software/src/battery/RENAULT-ZOE-GEN1-BATTERY.h
Normal file
14
Software/src/battery/RENAULT-ZOE-GEN1-BATTERY.h
Normal file
|
@ -0,0 +1,14 @@
|
|||
#ifndef RENAULT_ZOE_GEN1_BATTERY_H
|
||||
#define RENAULT_ZOE_GEN1_BATTERY_H
|
||||
#include "../include.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
|
||||
#define BATTERY_SELECTED
|
||||
|
||||
#define ABSOLUTE_CELL_MAX_VOLTAGE 4100
|
||||
#define ABSOLUTE_CELL_MIN_VOLTAGE 3000
|
||||
#define MAX_CELL_DEVIATION_MV 500
|
||||
|
||||
void setup_battery(void);
|
||||
|
||||
#endif
|
113
Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.cpp
Normal file
113
Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.cpp
Normal file
|
@ -0,0 +1,113 @@
|
|||
#include "../include.h"
|
||||
#ifdef RENAULT_ZOE_GEN2_BATTERY
|
||||
#include "../datalayer/datalayer.h"
|
||||
#include "../devboard/utils/events.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
#include "RENAULT-ZOE-GEN2-BATTERY.h"
|
||||
|
||||
/* Information in this file is based of the OVMS V3 vehicle_renaultzoe.cpp component
|
||||
https://github.com/openvehicles/Open-Vehicle-Monitoring-System-3/blob/master/vehicle/OVMS.V3/components/vehicle_renaultzoe_ph2_obd/src/vehicle_renaultzoe_ph2_obd.cpp
|
||||
/*
|
||||
|
||||
/* Do not change code below unless you are sure what you are doing */
|
||||
static uint16_t LB_SOC = 50;
|
||||
static uint16_t LB_SOH = 99;
|
||||
static int16_t LB_Average_Temperature = 0;
|
||||
static uint32_t LB_Charge_Power_W = 0;
|
||||
static int32_t LB_Current = 0;
|
||||
static uint16_t LB_kWh_Remaining = 0;
|
||||
static uint16_t LB_Cell_Max_Voltage = 3700;
|
||||
static uint16_t LB_Cell_Min_Voltage = 3700;
|
||||
static uint16_t LB_Battery_Voltage = 3700;
|
||||
|
||||
CAN_frame_t ZOE_373 = {.FIR = {.B =
|
||||
{
|
||||
.DLC = 8,
|
||||
.FF = CAN_frame_std,
|
||||
}},
|
||||
.MsgID = 0x373,
|
||||
.data = {0xC1, 0x80, 0x5D, 0x5D, 0x00, 0x00, 0xff, 0xcb}};
|
||||
CAN_frame_t ZOE_POLL_18DADBF1 = {.FIR = {.B =
|
||||
{
|
||||
.DLC = 8,
|
||||
.FF = CAN_frame_ext,
|
||||
}},
|
||||
.MsgID = 0x18DADBF1,
|
||||
.data = {0x03, 0x22, 0x90, 0x00, 0xff, 0xff, 0xff, 0xff}};
|
||||
|
||||
static unsigned long previousMillis200 = 0; // will store last time a 200ms CAN Message was sent
|
||||
|
||||
void update_values_battery() { //This function maps all the values fetched via CAN to the correct parameters used for modbus
|
||||
datalayer.battery.status.soh_pptt = (LB_SOH * 100); //Increase range from 99% -> 99.00%
|
||||
|
||||
datalayer.battery.status.real_soc = (LB_SOC * 10); //increase LB_SOC range from 0-100.0 -> 100.00
|
||||
|
||||
datalayer.battery.status.voltage_dV = LB_Battery_Voltage;
|
||||
|
||||
datalayer.battery.status.current_dA = LB_Current;
|
||||
|
||||
//Calculate the remaining Wh amount from SOC% and max Wh value.
|
||||
datalayer.battery.status.remaining_capacity_Wh = static_cast<uint32_t>(
|
||||
(static_cast<double>(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh);
|
||||
|
||||
datalayer.battery.status.max_discharge_power_W;
|
||||
|
||||
datalayer.battery.status.max_charge_power_W;
|
||||
|
||||
datalayer.battery.status.active_power_W;
|
||||
|
||||
datalayer.battery.status.temperature_min_dC;
|
||||
|
||||
datalayer.battery.status.temperature_max_dC;
|
||||
|
||||
datalayer.battery.status.cell_min_voltage_mV;
|
||||
|
||||
datalayer.battery.status.cell_max_voltage_mV;
|
||||
|
||||
if (LB_Cell_Max_Voltage >= ABSOLUTE_CELL_MAX_VOLTAGE) {
|
||||
set_event(EVENT_CELL_OVER_VOLTAGE, 0);
|
||||
}
|
||||
if (LB_Cell_Min_Voltage <= ABSOLUTE_CELL_MIN_VOLTAGE) {
|
||||
set_event(EVENT_CELL_UNDER_VOLTAGE, 0);
|
||||
}
|
||||
|
||||
#ifdef DEBUG_VIA_USB
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
void receive_can_battery(CAN_frame_t rx_frame) {
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
switch (rx_frame.MsgID) {
|
||||
case 0x18daf1db: // LBC Reply from active polling
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void send_can_battery() {
|
||||
unsigned long currentMillis = millis();
|
||||
// Send 200ms CAN Message
|
||||
if (currentMillis - previousMillis200 >= INTERVAL_200_MS) {
|
||||
// Check if sending of CAN messages has been delayed too much.
|
||||
if ((currentMillis - previousMillis200 >= INTERVAL_200_MS_DELAYED) && (currentMillis > BOOTUP_TIME)) {
|
||||
set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis200));
|
||||
}
|
||||
previousMillis200 = currentMillis;
|
||||
ESP32Can.CANWriteFrame(&ZOE_373);
|
||||
ESP32Can.CANWriteFrame(&ZOE_POLL_18DADBF1);
|
||||
}
|
||||
}
|
||||
|
||||
void setup_battery(void) { // Performs one time setup at startup
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("Renault Zoe 50kWh battery selected");
|
||||
#endif
|
||||
|
||||
datalayer.battery.info.max_design_voltage_dV = 4040;
|
||||
datalayer.battery.info.min_design_voltage_dV = 3100;
|
||||
}
|
||||
|
||||
#endif
|
14
Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.h
Normal file
14
Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.h
Normal file
|
@ -0,0 +1,14 @@
|
|||
#ifndef RENAULT_ZOE_GEN2_BATTERY_H
|
||||
#define RENAULT_ZOE_GEN2_BATTERY_H
|
||||
#include "../include.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
|
||||
#define BATTERY_SELECTED
|
||||
|
||||
#define ABSOLUTE_CELL_MAX_VOLTAGE 4100
|
||||
#define ABSOLUTE_CELL_MIN_VOLTAGE 3000
|
||||
#define MAX_CELL_DEVIATION_MV 500
|
||||
|
||||
void setup_battery(void);
|
||||
|
||||
#endif
|
|
@ -18,7 +18,6 @@ TODO: Map all values from battery CAN messages
|
|||
/* Do not change code below unless you are sure what you are doing */
|
||||
static unsigned long previousMillis10 = 0; // will store last time a 10ms CAN Message was send
|
||||
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
|
||||
static uint8_t CANstillAlive = 12; //counter for checking if CAN is still alive
|
||||
|
||||
static int SOC_1 = 0;
|
||||
static int SOC_2 = 0;
|
||||
|
@ -77,21 +76,13 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
|
||||
datalayer.battery.status.temperature_max_dC;
|
||||
|
||||
/* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/
|
||||
if (!CANstillAlive) {
|
||||
set_event(EVENT_CAN_RX_FAILURE, 0);
|
||||
} else {
|
||||
CANstillAlive--;
|
||||
clear_event(EVENT_CAN_RX_FAILURE);
|
||||
}
|
||||
|
||||
#ifdef DEBUG_VIA_USB
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
void receive_can_battery(CAN_frame_t rx_frame) {
|
||||
CANstillAlive = 12;
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
switch (rx_frame.MsgID) {
|
||||
case 0x200:
|
||||
break;
|
||||
|
@ -132,6 +123,8 @@ void send_can_battery() {
|
|||
// Check if sending of CAN messages has been delayed too much.
|
||||
if ((currentMillis - previousMillis10 >= INTERVAL_10_MS_DELAYED) && (currentMillis > BOOTUP_TIME)) {
|
||||
set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis10));
|
||||
} else {
|
||||
clear_event(EVENT_CAN_OVERRUN);
|
||||
}
|
||||
previousMillis10 = currentMillis;
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
|
||||
#define BATTERY_SELECTED
|
||||
#define MAX_CELL_DEVIATION_MV 250
|
||||
|
||||
uint8_t CalculateCRC8(CAN_frame_t rx_frame);
|
||||
void setup_battery(void);
|
||||
|
|
|
@ -40,7 +40,7 @@ void __getData() {
|
|||
(uint32_t)(dataLinkReceive.getReceivedData(6) * 10); //add back missing decimal
|
||||
datalayer.battery.status.max_charge_power_W =
|
||||
(uint32_t)(dataLinkReceive.getReceivedData(7) * 10); //add back missing decimal
|
||||
uint16_t _datalayer.battery.status.bms_status = (uint16_t)dataLinkReceive.getReceivedData(8);
|
||||
uint16_t _system_bms_status = (uint16_t)dataLinkReceive.getReceivedData(8);
|
||||
datalayer.battery.status.active_power_W =
|
||||
(uint32_t)(dataLinkReceive.getReceivedData(9) * 10); //add back missing decimal
|
||||
datalayer.battery.status.temperature_min_dC = (int16_t)dataLinkReceive.getReceivedData(10);
|
||||
|
@ -51,7 +51,7 @@ void __getData() {
|
|||
datalayer.system.status.battery_allows_contactor_closing = (bool)dataLinkReceive.getReceivedData(15);
|
||||
|
||||
batteryFault = false;
|
||||
if (_datalayer.battery.status.bms_status == FAULT) {
|
||||
if (_system_bms_status == FAULT) {
|
||||
batteryFault = true;
|
||||
set_event(EVENT_SERIAL_TRANSMITTER_FAILURE, 0);
|
||||
}
|
||||
|
|
|
@ -4,6 +4,9 @@
|
|||
#define SERIAL_LINK_RECEIVER_FROM_BATTERY_H
|
||||
|
||||
#define BATTERY_SELECTED
|
||||
#ifndef MAX_CELL_DEVIATION_MV
|
||||
#define MAX_CELL_DEVIATION_MV 9999
|
||||
#endif
|
||||
|
||||
#include "../include.h"
|
||||
#include "../lib/mackelec-SerialDataLink/SerialDataLink.h"
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
/* Credits: Most of the code comes from Per Carlen's bms_comms_tesla_model3.py (https://gitlab.com/pelle8/batt2gen24/) */
|
||||
|
||||
static unsigned long previousMillis30 = 0; // will store last time a 30ms CAN Message was send
|
||||
static uint8_t stillAliveCAN = 6; //counter for checking if CAN is still alive
|
||||
|
||||
CAN_frame_t TESLA_221_1 = {
|
||||
.FIR = {.B =
|
||||
|
@ -33,7 +32,6 @@ static uint32_t total_discharge = 0;
|
|||
static uint32_t total_charge = 0;
|
||||
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 = 0; // C*
|
||||
static int16_t min_temp = 0; // C*
|
||||
|
@ -164,19 +162,8 @@ static const char* hvilStatusState[] = {"NOT OK",
|
|||
|
||||
void update_values_battery() { //This function maps all the values fetched via CAN to the correct parameters used for modbus
|
||||
//After values are mapped, we perform some safety checks, and do some serial printouts
|
||||
//Calculate the SOH% to send to inverter
|
||||
if (bat_beginning_of_life != 0) { //div/0 safeguard
|
||||
datalayer.battery.status.soh_pptt =
|
||||
static_cast<uint16_t>((static_cast<double>(nominal_full_pack_energy) / bat_beginning_of_life) * 10000.0);
|
||||
}
|
||||
//If the calculation went wrong, set SOH to 100%
|
||||
if (datalayer.battery.status.soh_pptt > 10000) {
|
||||
datalayer.battery.status.soh_pptt = 10000;
|
||||
}
|
||||
//If the value is unavailable, set SOH to 99%
|
||||
if (nominal_full_pack_energy < REASONABLE_ENERGYAMOUNT) {
|
||||
datalayer.battery.status.soh_pptt = 9900;
|
||||
}
|
||||
|
||||
datalayer.battery.status.soh_pptt = 9900; //Tesla batteries do not send a SOH% value on bus. Hardcode to 99%
|
||||
|
||||
datalayer.battery.status.real_soc = (soc_vi * 10); //increase SOC range from 0-100.0 -> 100.00
|
||||
|
||||
|
@ -190,17 +177,13 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
|
||||
// Define the allowed discharge power
|
||||
datalayer.battery.status.max_discharge_power_W = (max_discharge_current * volts);
|
||||
// Cap the allowed discharge power if battery is empty, or discharge power is higher than the maximum discharge power allowed
|
||||
if (datalayer.battery.status.reported_soc == 0) {
|
||||
datalayer.battery.status.max_discharge_power_W = 0;
|
||||
} else if (datalayer.battery.status.max_discharge_power_W > MAXDISCHARGEPOWERALLOWED) {
|
||||
// Cap the allowed discharge power if higher than the maximum discharge power allowed
|
||||
if (datalayer.battery.status.max_discharge_power_W > MAXDISCHARGEPOWERALLOWED) {
|
||||
datalayer.battery.status.max_discharge_power_W = MAXDISCHARGEPOWERALLOWED;
|
||||
}
|
||||
|
||||
//The allowed charge power behaves strangely. We instead estimate this value
|
||||
if (datalayer.battery.status.reported_soc == 10000) { // When scaled SOC is 100.00%, set allowed charge power to 0
|
||||
datalayer.battery.status.max_charge_power_W = 0;
|
||||
} else if (soc_vi > 990) {
|
||||
if (soc_vi > 990) {
|
||||
datalayer.battery.status.max_charge_power_W = FLOAT_MAX_POWER_W;
|
||||
} else if (soc_vi > RAMPDOWN_SOC) { // When real SOC is between RAMPDOWN_SOC-99%, ramp the value between Max<->0
|
||||
datalayer.battery.status.max_charge_power_W =
|
||||
|
@ -219,8 +202,7 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
datalayer.battery.status.max_charge_power_W = MAXCHARGEPOWERALLOWED;
|
||||
}
|
||||
|
||||
power = ((volts / 10) * amps);
|
||||
datalayer.battery.status.active_power_W = power;
|
||||
datalayer.battery.status.active_power_W = ((volts / 10) * amps);
|
||||
|
||||
datalayer.battery.status.temperature_min_dC = min_temp;
|
||||
|
||||
|
@ -232,14 +214,6 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
|
||||
/* Value mapping is completed. Start to check all safeties */
|
||||
|
||||
/* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/
|
||||
if (!stillAliveCAN) {
|
||||
set_event(EVENT_CAN_RX_FAILURE, 0);
|
||||
} else {
|
||||
stillAliveCAN--;
|
||||
clear_event(EVENT_CAN_RX_FAILURE);
|
||||
}
|
||||
|
||||
if (hvil_status == 3) { //INTERNAL_OPEN_FAULT - Someone disconnected a high voltage cable while battery was in use
|
||||
set_event(EVENT_INTERNAL_OPEN_FAULT, 0);
|
||||
} else {
|
||||
|
@ -312,12 +286,6 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
}
|
||||
}
|
||||
|
||||
if (datalayer.battery.status.bms_status ==
|
||||
FAULT) { //Incase we enter a critical fault state, zero out the allowed limits
|
||||
datalayer.battery.status.max_charge_power_W = 0;
|
||||
datalayer.battery.status.max_discharge_power_W = 0;
|
||||
}
|
||||
|
||||
/* Safeties verified. Perform USB serial printout if configured to do so */
|
||||
|
||||
#ifdef DEBUG_VIA_USB
|
||||
|
@ -513,7 +481,7 @@ void receive_can_battery(CAN_frame_t rx_frame) {
|
|||
output_current = (((rx_frame.data.u8[4] & 0x0F) << 8) | rx_frame.data.u8[3]) / 100;
|
||||
break;
|
||||
case 0x292:
|
||||
stillAliveCAN = 12; //We are getting CAN messages from the BMS, set the CAN detect counter
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; //We are getting CAN messages from the BMS
|
||||
bat_beginning_of_life = (((rx_frame.data.u8[6] & 0x03) << 8) | rx_frame.data.u8[5]);
|
||||
soc_min = (((rx_frame.data.u8[1] & 0x03) << 8) | rx_frame.data.u8[0]);
|
||||
soc_vi = (((rx_frame.data.u8[2] & 0x0F) << 6) | ((rx_frame.data.u8[1] & 0xFC) >> 2));
|
||||
|
@ -589,6 +557,8 @@ the first, for a few cycles, then stop all messages which causes the contactor
|
|||
// Check if sending of CAN messages has been delayed too much.
|
||||
if ((currentMillis - previousMillis30 >= INTERVAL_30_MS_DELAYED) && (currentMillis > BOOTUP_TIME)) {
|
||||
set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis30));
|
||||
} else {
|
||||
clear_event(EVENT_CAN_OVERRUN);
|
||||
}
|
||||
previousMillis30 = currentMillis;
|
||||
|
||||
|
@ -636,8 +606,8 @@ void printFaultCodesIfActive() {
|
|||
}
|
||||
if (datalayer.system.status.inverter_allows_contactor_closing == false) {
|
||||
Serial.println(
|
||||
"ERROR: Solar inverter does not allow for contactor closing. Check "
|
||||
"datalayer.system.status.inverter_allows_contactor_closing parameter");
|
||||
"ERROR: Solar inverter does not allow for contactor closing. Check communication connection to the inverter OR "
|
||||
"disable the inverter protocol to proceed with contactor closing");
|
||||
}
|
||||
// Check each symbol and print debug information if its value is 1
|
||||
printDebugIfActive(WatchdogReset, "ERROR: The processor has experienced a reset due to watchdog reset");
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
//#define LFP_CHEMISTRY // Enable this line to startup in LFP mode
|
||||
|
||||
#define RAMPDOWN_SOC 900 // 90.0 SOC% to start ramping down from max charge power towards 0 at 100.00%
|
||||
#define MAX_CELL_DEVIATION_MV 9999 // Handled inside the Tesla.cpp file, just for compilation
|
||||
#define FLOAT_MAX_POWER_W 200 // W, what power to allow for top balancing battery
|
||||
#define FLOAT_START_MV 20 // mV, how many mV under overvoltage to start float charging
|
||||
#define MAXCHARGEPOWERALLOWED 15000 // 15000W we use a define since the value supplied by Tesla is always 0
|
||||
|
|
|
@ -16,6 +16,7 @@ void print_units(char* header, int value, char* units) {
|
|||
}
|
||||
|
||||
void update_values_battery() { /* This function puts fake values onto the parameters sent towards the inverter */
|
||||
|
||||
datalayer.battery.status.real_soc = 5000; // 50.00%
|
||||
|
||||
datalayer.battery.status.soh_pptt = 9900; // 99.00%
|
||||
|
@ -46,6 +47,9 @@ void update_values_battery() { /* This function puts fake values onto the parame
|
|||
datalayer.battery.status.cell_voltages_mV[i] = 3500 + i;
|
||||
}
|
||||
|
||||
//Fake that we get CAN messages
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
|
||||
/*Finally print out values to serial if configured to do so*/
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("FAKE Values going to inverter");
|
||||
|
@ -62,7 +66,9 @@ void update_values_battery() { /* This function puts fake values onto the parame
|
|||
#endif
|
||||
}
|
||||
|
||||
void receive_can_battery(CAN_frame_t rx_frame) { // All CAN messages recieved will be logged via serial
|
||||
void receive_can_battery(CAN_frame_t rx_frame) {
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
// All CAN messages recieved will be logged via serial
|
||||
Serial.print(millis()); // Example printout, time, ID, length, data: 7553 1DB 8 FF C0 B9 EA 0 0 2 5D
|
||||
Serial.print(" ");
|
||||
Serial.print(rx_frame.MsgID, HEX);
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#include "../include.h"
|
||||
|
||||
#define BATTERY_SELECTED
|
||||
#define MAX_CELL_DEVIATION_MV 9999
|
||||
|
||||
void setup_battery(void);
|
||||
|
||||
|
|
|
@ -9,11 +9,9 @@
|
|||
/* Do not change code below unless you are sure what you are doing */
|
||||
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
|
||||
static unsigned long previousMillis60s = 0; // will store last time a 60s CAN Message was send
|
||||
static uint8_t CANstillAlive = 12; //counter for checking if CAN is still alive
|
||||
|
||||
#define MAX_CELL_VOLTAGE 4210 //Battery is put into emergency stop if one cell goes over this value
|
||||
#define MIN_CELL_VOLTAGE 2700 //Battery is put into emergency stop if one cell goes below this value
|
||||
#define MAX_CELL_DEVIATION 500 //LED turns yellow on the board if mv delta exceeds this value
|
||||
|
||||
static float BATT_U = 0; //0x3A
|
||||
static float MAX_U = 0; //0x3A
|
||||
|
@ -30,12 +28,10 @@ static uint16_t CELL_U_MAX = 0; //0x37D
|
|||
static uint16_t CELL_U_MIN = 0; //0x37D
|
||||
static uint8_t CELL_ID_U_MAX = 0; //0x37D
|
||||
static uint16_t HvBattPwrLimDchaSoft = 0; //0x369
|
||||
|
||||
static uint8_t batteryModuleNumber = 0x10; // First battery module
|
||||
static uint8_t battery_request_idx = 0;
|
||||
static uint8_t rxConsecutiveFrames = 0;
|
||||
static uint16_t min_max_voltage[2]; //contains cell min[0] and max[1] values in mV
|
||||
static uint16_t cell_deviation_mV = 0; //contains the deviation between highest and lowest cell in mV
|
||||
static uint8_t cellcounter = 0;
|
||||
static uint32_t remaining_capacity = 0;
|
||||
static uint16_t cell_voltages[108]; //array with all the cellvoltages
|
||||
|
@ -114,14 +110,6 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
datalayer.battery.status.cell_voltages_mV[i] = cell_voltages[i];
|
||||
}
|
||||
|
||||
/* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/
|
||||
if (!CANstillAlive) {
|
||||
set_event(EVENT_CAN_RX_FAILURE, 0);
|
||||
} else {
|
||||
CANstillAlive--;
|
||||
clear_event(EVENT_CAN_RX_FAILURE);
|
||||
}
|
||||
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.print("BMS reported SOC%: ");
|
||||
Serial.println(SOC_BMS);
|
||||
|
@ -159,8 +147,6 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
Serial.println(min_max_voltage[1]);
|
||||
Serial.print("Lowest cell voltage: ");
|
||||
Serial.println(min_max_voltage[0]);
|
||||
Serial.print("Cell deviation voltage: ");
|
||||
Serial.println(cell_deviation_mV);
|
||||
Serial.print("Cell voltage,");
|
||||
while (cnt < 108) {
|
||||
Serial.print(cell_voltages[cnt++]);
|
||||
|
@ -171,7 +157,7 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
}
|
||||
|
||||
void receive_can_battery(CAN_frame_t rx_frame) {
|
||||
CANstillAlive = 12;
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
switch (rx_frame.MsgID) {
|
||||
case 0x3A:
|
||||
if ((rx_frame.data.u8[6] & 0x80) == 0x80)
|
||||
|
@ -314,12 +300,6 @@ void receive_can_battery(CAN_frame_t rx_frame) {
|
|||
min_max_voltage[1] = cell_voltages[cellcounter];
|
||||
}
|
||||
|
||||
cell_deviation_mV = (min_max_voltage[1] - min_max_voltage[0]);
|
||||
|
||||
if (cell_deviation_mV > MAX_CELL_DEVIATION) {
|
||||
set_event(EVENT_CELL_DEVIATION_HIGH, 0);
|
||||
}
|
||||
|
||||
if (min_max_voltage[1] >= MAX_CELL_VOLTAGE) {
|
||||
set_event(EVENT_CELL_OVER_VOLTAGE, 0);
|
||||
}
|
||||
|
@ -351,6 +331,8 @@ void send_can_battery() {
|
|||
// Check if sending of CAN messages has been delayed too much.
|
||||
if ((currentMillis - previousMillis100 >= INTERVAL_100_MS_DELAYED) && (currentMillis > BOOTUP_TIME)) {
|
||||
set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis100));
|
||||
} else {
|
||||
clear_event(EVENT_CAN_OVERRUN);
|
||||
}
|
||||
previousMillis100 = currentMillis;
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
|
||||
#define BATTERY_SELECTED
|
||||
#define MAX_CELL_DEVIATION_MV 250
|
||||
|
||||
void setup_battery(void);
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#ifndef CHARGERS_H
|
||||
#define CHARGERS_H
|
||||
|
||||
#include "../../USER_SETTINGS.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h" // This include is annoying, consider defining a frame type in types.h
|
||||
|
||||
#ifdef CHEVYVOLT_CHARGER
|
||||
#include "CHEVY-VOLT-CHARGER.h"
|
||||
|
@ -11,4 +11,7 @@
|
|||
#include "NISSAN-LEAF-CHARGER.h"
|
||||
#endif
|
||||
|
||||
void receive_can_charger(CAN_frame_t rx_frame);
|
||||
void send_can_charger();
|
||||
|
||||
#endif
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#include "../include.h"
|
||||
#ifdef CHEVYVOLT_CHARGER
|
||||
#include "../datalayer/datalayer.h"
|
||||
#include "../devboard/utils/events.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
#include "CHEVY-VOLT-CHARGER.h"
|
||||
|
@ -63,7 +64,7 @@ static CAN_frame_t charger_set_targets = {.FIR = {.B =
|
|||
.data = {0x40, 0x00, 0x00, 0x00}};
|
||||
|
||||
/* We are mostly sending out not receiving */
|
||||
void receive_can_chevyvolt_charger(CAN_frame_t rx_frame) {
|
||||
void receive_can_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;
|
||||
|
@ -115,7 +116,7 @@ void receive_can_chevyvolt_charger(CAN_frame_t rx_frame) {
|
|||
}
|
||||
}
|
||||
|
||||
void send_can_chevyvolt_charger() {
|
||||
void send_can_charger() {
|
||||
unsigned long currentMillis = millis();
|
||||
uint16_t Vol_temp = 0;
|
||||
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
#include "../include.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
|
||||
#define CHARGER_SELECTED
|
||||
|
||||
/* Charger hardware limits
|
||||
*
|
||||
* Relative to runtime settings, expectations are:
|
||||
|
@ -14,8 +16,4 @@
|
|||
#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
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#include "../include.h"
|
||||
#ifdef NISSAN_LEAF_CHARGER
|
||||
#ifdef NISSANLEAF_CHARGER
|
||||
#include "../datalayer/datalayer.h"
|
||||
#include "../devboard/utils/events.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
#include "NISSAN-LEAF-CHARGER.h"
|
||||
|
@ -144,7 +145,7 @@ static uint8_t calculate_checksum_nibble(CAN_frame_t* frame) {
|
|||
return sum;
|
||||
}
|
||||
|
||||
void receive_can_nissanleaf_charger(CAN_frame_t rx_frame) {
|
||||
void receive_can_charger(CAN_frame_t rx_frame) {
|
||||
|
||||
switch (rx_frame.MsgID) {
|
||||
case 0x679: // This message fires once when charging cable is plugged in
|
||||
|
@ -181,7 +182,7 @@ void receive_can_nissanleaf_charger(CAN_frame_t rx_frame) {
|
|||
}
|
||||
}
|
||||
|
||||
void send_can_nissanleaf_charger() {
|
||||
void send_can_charger() {
|
||||
unsigned long currentMillis = millis();
|
||||
|
||||
/* Send keepalive with mode every 10ms */
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
#include "../include.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
|
||||
void send_can_nissanleaf_charger();
|
||||
void receive_can_nissanleaf_charger(CAN_frame_t rx_frame);
|
||||
#define CHARGER_SELECTED
|
||||
|
||||
#endif
|
||||
|
|
|
@ -30,6 +30,8 @@ typedef struct {
|
|||
typedef struct {
|
||||
/** int32_t */
|
||||
/** Instantaneous battery power in Watts */
|
||||
/* Positive value = Battery Charging */
|
||||
/* Negative value = Battery Discharging */
|
||||
int32_t active_power_W;
|
||||
|
||||
/** uint32_t */
|
||||
|
@ -68,6 +70,14 @@ typedef struct {
|
|||
* battery.settings.soc_scaling_active
|
||||
*/
|
||||
uint16_t reported_soc;
|
||||
/** A counter that increases incase a CAN CRC read error occurs */
|
||||
uint16_t CAN_error_counter;
|
||||
/** uint8_t */
|
||||
/** A counter set each time a new message comes from battery.
|
||||
* This value then gets decremented each 5 seconds. Incase we reach 0
|
||||
* we report the battery as missing entirely on the CAN bus.
|
||||
*/
|
||||
uint8_t CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
|
||||
/** Other */
|
||||
/** The current BMS status */
|
||||
|
@ -93,6 +103,13 @@ typedef struct {
|
|||
DATALAYER_BATTERY_SETTINGS_TYPE settings;
|
||||
} DATALAYER_BATTERY_TYPE;
|
||||
|
||||
typedef struct {
|
||||
/** measured voltage in deciVolts. 4200 = 420.0 V */
|
||||
uint16_t measured_voltage_dV = 0;
|
||||
/** measured amperage in deciAmperes. 300 = 30.0 A */
|
||||
uint16_t measured_amperage_dA = 0;
|
||||
} DATALAYER_SHUNT_TYPE;
|
||||
|
||||
typedef struct {
|
||||
// TODO
|
||||
} DATALAYER_SYSTEM_INFO_TYPE;
|
||||
|
@ -103,13 +120,15 @@ typedef struct {
|
|||
int64_t core_task_max_us = 0;
|
||||
/** Core task measurement variable, reset each 10 seconds */
|
||||
int64_t core_task_10s_max_us = 0;
|
||||
/** MQTT task measurement variable, reset each 10 seconds */
|
||||
/** MQTT sub-task measurement variable, reset each 10 seconds */
|
||||
int64_t mqtt_task_10s_max_us = 0;
|
||||
/** Wifi sub-task measurement variable, reset each 10 seconds */
|
||||
int64_t wifi_task_10s_max_us = 0;
|
||||
/** loop() task measurement variable, reset each 10 seconds */
|
||||
int64_t loop_task_10s_max_us = 0;
|
||||
|
||||
/** OTA/Wifi handling function measurement variable */
|
||||
int64_t time_wifi_us = 0;
|
||||
/** OTA handling function measurement variable */
|
||||
int64_t time_ota_us = 0;
|
||||
/** CAN RX or serial link function measurement variable */
|
||||
int64_t time_comm_us = 0;
|
||||
/** 10 ms function measurement variable */
|
||||
|
@ -120,9 +139,9 @@ typedef struct {
|
|||
int64_t time_cantx_us = 0;
|
||||
|
||||
/** Function measurement snapshot variable.
|
||||
* This will show the performance of OTA/Wifi handling when the total time reached a new worst case
|
||||
* This will show the performance of OTA handling when the total time reached a new worst case
|
||||
*/
|
||||
int64_t time_snap_wifi_us = 0;
|
||||
int64_t time_snap_ota_us = 0;
|
||||
/** Function measurement snapshot variable.
|
||||
* This will show the performance of CAN RX or serial link when the total time reached a new worst case
|
||||
*/
|
||||
|
@ -161,6 +180,7 @@ class DataLayer {
|
|||
public:
|
||||
DATALAYER_BATTERY_TYPE battery;
|
||||
DATALAYER_BATTERY_TYPE battery2;
|
||||
DATALAYER_SHUNT_TYPE shunt;
|
||||
DATALAYER_SYSTEM_TYPE system;
|
||||
};
|
||||
|
||||
|
|
|
@ -3,11 +3,10 @@
|
|||
|
||||
#include "../../../USER_SETTINGS.h"
|
||||
|
||||
/* Select HW - DONT TOUCH */
|
||||
#define HW_LILYGO
|
||||
|
||||
#if defined(HW_LILYGO)
|
||||
#include "hw_lilygo.h"
|
||||
#elif defined(HW_STARK)
|
||||
#include "hw_stark.h"
|
||||
#elif defined(HW_SJB_V1)
|
||||
#include "hw_sjb_v1.h"
|
||||
#endif
|
||||
|
|
|
@ -40,6 +40,13 @@
|
|||
#define MCP2517_CS 18 // CS input of MCP2517
|
||||
#define MCP2517_INT 35 // INT output of MCP2517
|
||||
|
||||
// CHAdeMO support pin dependencies
|
||||
#define CHADEMO_PIN_2 12
|
||||
#define CHADEMO_PIN_10 5
|
||||
#define CHADEMO_PIN_7 34
|
||||
#define CHADEMO_PIN_4 35
|
||||
#define CHADEMO_LOCK 18
|
||||
|
||||
// Contactor handling
|
||||
#define POSITIVE_CONTACTOR_PIN 32
|
||||
#define NEGATIVE_CONTACTOR_PIN 33
|
||||
|
@ -62,4 +69,10 @@
|
|||
#error Multiple HW defined! Please select a single HW
|
||||
#endif
|
||||
|
||||
#ifdef CHADEMO_BATTERY
|
||||
#ifdef DUAL_CAN
|
||||
#error CHADEMO and DUAL_CAN cannot coexist due to overlapping GPIO pin usage
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
|
66
Software/src/devboard/hal/hw_stark.h
Normal file
66
Software/src/devboard/hal/hw_stark.h
Normal file
|
@ -0,0 +1,66 @@
|
|||
#ifndef __HW_STARK06_H__
|
||||
#define __HW_STARK06_H__
|
||||
|
||||
// Board boot-up time
|
||||
#define BOOTUP_TIME 1000 // Time in ms it takes before system is considered fully started up
|
||||
|
||||
// Core assignment
|
||||
#define CORE_FUNCTION_CORE 1
|
||||
#define MODBUS_CORE 0
|
||||
#define WIFI_CORE 0
|
||||
|
||||
// RS485
|
||||
// #define PIN_5V_EN 16 // No function, GPIO 16 used instead as MCP_SCK
|
||||
// #define RS485_EN_PIN 17 // RE, No function, GPIO 17 is instead available as extra GPIO via pin header
|
||||
#define RS485_TX_PIN 22
|
||||
#define RS485_RX_PIN 21
|
||||
// #define RS485_SE_PIN 19 // No function, GPIO 19 is instead available as extra GPIO via pin header
|
||||
|
||||
// CAN settings. CAN_2 is not defined as it can be either MCP2515 or MCP2517, defined by the user settings
|
||||
#define CAN_1_TYPE ESP32CAN
|
||||
|
||||
// CAN1 PIN mappings, do not change these unless you are adding on extra hardware to the PCB
|
||||
#define CAN_TX_PIN GPIO_NUM_27
|
||||
#define CAN_RX_PIN GPIO_NUM_26
|
||||
// #define CAN_SE_PIN 23 // (No function, GPIO 23 used instead as MCP_SCK)
|
||||
|
||||
// CAN2 defines below
|
||||
|
||||
// DUAL_CAN defines
|
||||
//#define MCP2515_SCK 12 // SCK input of MCP2515
|
||||
//#define MCP2515_MOSI 5 // SDI input of MCP2515
|
||||
//#define MCP2515_MISO 34 // SDO output of MCP2515 | Pin 34 is input only, without pullup/down resistors
|
||||
//#define MCP2515_CS 18 // CS input of MCP2515
|
||||
//#define MCP2515_INT 35 // INT output of MCP2515 | | Pin 35 is input only, without pullup/down resistors
|
||||
|
||||
// CAN_FD defines
|
||||
#define MCP2517_SCK 16 // SCK input of MCP2517 (Changed from 12 to 16 since 12 can be used for JTAG TDI)
|
||||
#define MCP2517_SDI 5 // SDI input of MCP2517
|
||||
#define MCP2517_SDO 34 // SDO output of MCP2517
|
||||
#define MCP2517_CS 18 // CS input of MCP2517
|
||||
#define MCP2517_INT 35 // INT output of MCP2517
|
||||
|
||||
// Contactor handling
|
||||
#define POSITIVE_CONTACTOR_PIN 32
|
||||
#define NEGATIVE_CONTACTOR_PIN 33
|
||||
#define PRECHARGE_PIN 25
|
||||
#define BMS_POWER 23 // Also connected to MCP_SCK
|
||||
|
||||
// SD card
|
||||
//#define SD_MISO_PIN 2
|
||||
//#define SD_MOSI_PIN 15
|
||||
//#define SD_SCLK_PIN 14
|
||||
//#define SD_CS_PIN 13
|
||||
|
||||
// LED
|
||||
#define LED_PIN 4
|
||||
#define LED_MAX_BRIGHTNESS 40
|
||||
|
||||
/* ----- Error checks below, don't change (can't be moved to separate file) ----- */
|
||||
#ifndef HW_CONFIGURED
|
||||
#define HW_CONFIGURED
|
||||
#else
|
||||
#error Multiple HW defined! Please select a single HW
|
||||
#endif
|
||||
|
||||
#endif
|
|
@ -9,9 +9,6 @@
|
|||
#include "../../lib/knolleary-pubsubclient/PubSubClient.h"
|
||||
#include "../utils/timer.h"
|
||||
|
||||
const char* mqtt_subscriptions[] = MQTT_SUBSCRIPTIONS;
|
||||
const size_t mqtt_nof_subscriptions = sizeof(mqtt_subscriptions) / sizeof(mqtt_subscriptions[0]);
|
||||
|
||||
WiFiClient espClient;
|
||||
PubSubClient client(espClient);
|
||||
char mqtt_msg[MQTT_MSG_BUFFER_SIZE];
|
||||
|
@ -75,7 +72,8 @@ static void publish_cell_voltages(void) {
|
|||
doc.clear(); // clear after sending autoconfig
|
||||
} else {
|
||||
// If cell voltages haven't been populated...
|
||||
if (datalayer.battery.info.number_of_cells == 0u) {
|
||||
if (datalayer.battery.info.number_of_cells == 0u ||
|
||||
datalayer.battery.status.cell_voltages_mV[datalayer.battery.info.number_of_cells - 1] == 0u) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -158,10 +156,13 @@ static void publish_common_info(void) {
|
|||
doc["temperature_max"] = ((float)((int16_t)datalayer.battery.status.temperature_max_dC)) / 10.0;
|
||||
doc["stat_batt_power"] = ((float)((int32_t)datalayer.battery.status.active_power_W));
|
||||
doc["battery_current"] = ((float)((int16_t)datalayer.battery.status.current_dA)) / 10.0;
|
||||
doc["battery_voltage"] = ((float)datalayer.battery.status.voltage_dV) / 10.0;
|
||||
// publish only if cell voltages have been populated...
|
||||
if (datalayer.battery.info.number_of_cells != 0u &&
|
||||
datalayer.battery.status.cell_voltages_mV[datalayer.battery.info.number_of_cells - 1] != 0u) {
|
||||
doc["cell_max_voltage"] = ((float)datalayer.battery.status.cell_max_voltage_mV) / 1000.0;
|
||||
doc["cell_min_voltage"] = ((float)datalayer.battery.status.cell_min_voltage_mV) / 1000.0;
|
||||
doc["battery_voltage"] = ((float)datalayer.battery.status.voltage_dV) / 10.0;
|
||||
|
||||
}
|
||||
serializeJson(doc, mqtt_msg);
|
||||
if (!mqtt_publish(state_topic.c_str(), mqtt_msg, false)) {
|
||||
#ifdef DEBUG_VIA_USB
|
||||
|
@ -172,20 +173,7 @@ static void publish_common_info(void) {
|
|||
}
|
||||
}
|
||||
|
||||
/* This is called whenever a subscribed topic changes (hopefully) */
|
||||
static void callback(char* topic, byte* payload, unsigned int length) {
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.print("Message arrived [");
|
||||
Serial.print(topic);
|
||||
Serial.print("] ");
|
||||
for (unsigned int i = 0; i < length; i++) {
|
||||
Serial.print((char)payload[i]);
|
||||
}
|
||||
Serial.println();
|
||||
#endif
|
||||
}
|
||||
|
||||
/* If we lose the connection, get it back and re-sub */
|
||||
/* If we lose the connection, get it back */
|
||||
static void reconnect() {
|
||||
// attempt one reconnection
|
||||
#ifdef DEBUG_VIA_USB
|
||||
|
@ -199,14 +187,6 @@ static void reconnect() {
|
|||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("connected");
|
||||
#endif
|
||||
|
||||
for (int i = 0; i < mqtt_nof_subscriptions; i++) {
|
||||
client.subscribe(mqtt_subscriptions[i]);
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.print("Subscribed to: ");
|
||||
Serial.println(mqtt_subscriptions[i]);
|
||||
#endif
|
||||
}
|
||||
} else {
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.print("failed, rc=");
|
||||
|
@ -219,7 +199,6 @@ static void reconnect() {
|
|||
|
||||
void init_mqtt(void) {
|
||||
client.setServer(MQTT_SERVER, MQTT_PORT);
|
||||
client.setCallback(callback);
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("MQTT initialized");
|
||||
#endif
|
||||
|
|
136
Software/src/devboard/safety/safety.cpp
Normal file
136
Software/src/devboard/safety/safety.cpp
Normal file
|
@ -0,0 +1,136 @@
|
|||
#include "../../datalayer/datalayer.h"
|
||||
#include "../utils/events.h"
|
||||
|
||||
static uint16_t cell_deviation_mV = 0;
|
||||
static uint8_t charge_limit_failures = 0;
|
||||
static uint8_t discharge_limit_failures = 0;
|
||||
static bool battery_full_event_fired = false;
|
||||
static bool battery_empty_event_fired = false;
|
||||
|
||||
void update_machineryprotection() {
|
||||
// Start checking that the battery is within reason. Incase we see any funny business, raise an event!
|
||||
|
||||
// Battery is overheated!
|
||||
if (datalayer.battery.status.temperature_max_dC > 500) {
|
||||
set_event(EVENT_BATTERY_OVERHEAT, datalayer.battery.status.temperature_max_dC);
|
||||
} else {
|
||||
clear_event(EVENT_BATTERY_OVERHEAT);
|
||||
}
|
||||
|
||||
// Battery is frozen!
|
||||
if (datalayer.battery.status.temperature_min_dC < -250) {
|
||||
set_event(EVENT_BATTERY_FROZEN, datalayer.battery.status.temperature_min_dC);
|
||||
} else {
|
||||
clear_event(EVENT_BATTERY_FROZEN);
|
||||
}
|
||||
|
||||
// Battery voltage is over designed max voltage!
|
||||
if (datalayer.battery.status.voltage_dV > datalayer.battery.info.max_design_voltage_dV) {
|
||||
set_event(EVENT_BATTERY_OVERVOLTAGE, datalayer.battery.status.voltage_dV);
|
||||
} else {
|
||||
clear_event(EVENT_BATTERY_OVERVOLTAGE);
|
||||
}
|
||||
|
||||
// Battery voltage is under designed min voltage!
|
||||
if (datalayer.battery.status.voltage_dV < datalayer.battery.info.min_design_voltage_dV) {
|
||||
set_event(EVENT_BATTERY_UNDERVOLTAGE, datalayer.battery.status.voltage_dV);
|
||||
} else {
|
||||
clear_event(EVENT_BATTERY_UNDERVOLTAGE);
|
||||
}
|
||||
|
||||
// Battery is fully charged. Dont allow any more power into it
|
||||
// Normally the BMS will send 0W allowed, but this acts as an additional layer of safety
|
||||
if (datalayer.battery.status.reported_soc == 10000) //Scaled SOC% value is 100.00%
|
||||
{
|
||||
if (!battery_full_event_fired) {
|
||||
set_event(EVENT_BATTERY_FULL, 0);
|
||||
battery_full_event_fired = true;
|
||||
}
|
||||
datalayer.battery.status.max_charge_power_W = 0;
|
||||
} else {
|
||||
clear_event(EVENT_BATTERY_FULL);
|
||||
battery_full_event_fired = false;
|
||||
}
|
||||
|
||||
// Battery is empty. Do not allow further discharge.
|
||||
// Normally the BMS will send 0W allowed, but this acts as an additional layer of safety
|
||||
if (datalayer.battery.status.reported_soc == 0) { //Scaled SOC% value is 0.00%
|
||||
if (!battery_empty_event_fired) {
|
||||
set_event(EVENT_BATTERY_EMPTY, 0);
|
||||
battery_empty_event_fired = true;
|
||||
}
|
||||
datalayer.battery.status.max_discharge_power_W = 0;
|
||||
} else {
|
||||
clear_event(EVENT_BATTERY_EMPTY);
|
||||
battery_empty_event_fired = false;
|
||||
}
|
||||
|
||||
// Battery is extremely degraded, not fit for secondlifestorage!
|
||||
if (datalayer.battery.status.soh_pptt < 2500) {
|
||||
set_event(EVENT_LOW_SOH, datalayer.battery.status.soh_pptt);
|
||||
} else {
|
||||
clear_event(EVENT_LOW_SOH);
|
||||
}
|
||||
|
||||
// Check if SOC% is plausible
|
||||
if (datalayer.battery.status.voltage_dV >
|
||||
(datalayer.battery.info.max_design_voltage_dV -
|
||||
100)) { // When pack voltage is close to max, and SOC% is still low, raise event
|
||||
if (datalayer.battery.status.real_soc < 6500) { // 65.00%
|
||||
set_event(EVENT_SOC_PLAUSIBILITY_ERROR, datalayer.battery.status.real_soc);
|
||||
} else {
|
||||
clear_event(EVENT_SOC_PLAUSIBILITY_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
// Check diff between highest and lowest cell
|
||||
cell_deviation_mV = (datalayer.battery.status.cell_max_voltage_mV - datalayer.battery.status.cell_min_voltage_mV);
|
||||
if (cell_deviation_mV > MAX_CELL_DEVIATION_MV) {
|
||||
set_event(EVENT_CELL_DEVIATION_HIGH, (cell_deviation_mV / 20));
|
||||
} else {
|
||||
clear_event(EVENT_CELL_DEVIATION_HIGH);
|
||||
}
|
||||
|
||||
// Inverter is charging with more power than battery wants!
|
||||
if (datalayer.battery.status.active_power_W > 0) { // Charging
|
||||
if (datalayer.battery.status.active_power_W > (datalayer.battery.status.max_charge_power_W + 2000)) {
|
||||
if (charge_limit_failures > MAX_CHARGE_DISCHARGE_LIMIT_FAILURES) {
|
||||
set_event(EVENT_CHARGE_LIMIT_EXCEEDED, 0); // Alert when 2kW over requested max
|
||||
} else {
|
||||
charge_limit_failures++;
|
||||
}
|
||||
} else {
|
||||
clear_event(EVENT_CHARGE_LIMIT_EXCEEDED);
|
||||
charge_limit_failures = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Inverter is pulling too much power from battery!
|
||||
if (datalayer.battery.status.active_power_W < 0) { // Discharging
|
||||
if (-datalayer.battery.status.active_power_W > (datalayer.battery.status.max_discharge_power_W + 2000)) {
|
||||
if (discharge_limit_failures > MAX_CHARGE_DISCHARGE_LIMIT_FAILURES) {
|
||||
set_event(EVENT_DISCHARGE_LIMIT_EXCEEDED, 0); // Alert when 2kW over requested max
|
||||
} else {
|
||||
discharge_limit_failures++;
|
||||
}
|
||||
} else {
|
||||
clear_event(EVENT_DISCHARGE_LIMIT_EXCEEDED);
|
||||
discharge_limit_failures = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error
|
||||
if (!datalayer.battery.status.CAN_battery_still_alive) {
|
||||
set_event(EVENT_CAN_RX_FAILURE, 0);
|
||||
} else {
|
||||
datalayer.battery.status.CAN_battery_still_alive--;
|
||||
clear_event(EVENT_CAN_RX_FAILURE);
|
||||
}
|
||||
|
||||
// Too many malformed CAN messages recieved!
|
||||
if (datalayer.battery.status.CAN_error_counter > MAX_CAN_FAILURES) {
|
||||
set_event(EVENT_CAN_RX_WARNING, 0);
|
||||
} else {
|
||||
clear_event(EVENT_CAN_RX_WARNING);
|
||||
}
|
||||
}
|
11
Software/src/devboard/safety/safety.h
Normal file
11
Software/src/devboard/safety/safety.h
Normal file
|
@ -0,0 +1,11 @@
|
|||
#ifndef SAFETY_H
|
||||
#define SAFETY_H
|
||||
#include <Arduino.h>
|
||||
|
||||
#define MAX_CAN_FAILURES 50
|
||||
|
||||
#define MAX_CHARGE_DISCHARGE_LIMIT_FAILURES 1
|
||||
|
||||
void update_machineryprotection();
|
||||
|
||||
#endif
|
|
@ -5,8 +5,14 @@
|
|||
#endif
|
||||
|
||||
#include "../../../USER_SETTINGS.h"
|
||||
#include "../../lib/YiannisBourkelis-Uptime-Library/src/uptime.h"
|
||||
#include "timer.h"
|
||||
|
||||
// Time conversion macros
|
||||
#define DAYS_TO_SECS 86400 // 24 * 60 * 60
|
||||
#define HOURS_TO_SECS 3600 // 60 * 60
|
||||
#define MINUTES_TO_SECS 60
|
||||
|
||||
#define EE_NOF_EVENT_ENTRIES 30
|
||||
#define EE_EVENT_ENTRY_SIZE sizeof(EVENT_LOG_ENTRY_TYPE)
|
||||
#define EE_WRITE_PERIOD_MINUTES 10
|
||||
|
@ -44,7 +50,7 @@ typedef struct {
|
|||
|
||||
typedef struct {
|
||||
EVENTS_STRUCT_TYPE entries[EVENT_NOF_EVENTS];
|
||||
uint32_t time_seconds;
|
||||
unsigned long time_seconds;
|
||||
MyTimer second_timer;
|
||||
MyTimer ee_timer;
|
||||
MyTimer update_timer;
|
||||
|
@ -134,20 +140,30 @@ void init_events(void) {
|
|||
events.entries[EVENT_CAN_RX_WARNING].level = EVENT_LEVEL_WARNING;
|
||||
events.entries[EVENT_CAN_TX_FAILURE].level = EVENT_LEVEL_ERROR;
|
||||
events.entries[EVENT_WATER_INGRESS].level = EVENT_LEVEL_ERROR;
|
||||
events.entries[EVENT_CHARGE_LIMIT_EXCEEDED].level = EVENT_LEVEL_INFO;
|
||||
events.entries[EVENT_DISCHARGE_LIMIT_EXCEEDED].level = EVENT_LEVEL_INFO;
|
||||
events.entries[EVENT_12V_LOW].level = EVENT_LEVEL_WARNING;
|
||||
events.entries[EVENT_SOC_PLAUSIBILITY_ERROR].level = EVENT_LEVEL_ERROR;
|
||||
events.entries[EVENT_KWH_PLAUSIBILITY_ERROR].level = EVENT_LEVEL_INFO;
|
||||
events.entries[EVENT_BATTERY_EMPTY].level = EVENT_LEVEL_INFO;
|
||||
events.entries[EVENT_BATTERY_FULL].level = EVENT_LEVEL_INFO;
|
||||
events.entries[EVENT_BATTERY_FROZEN].level = EVENT_LEVEL_INFO;
|
||||
events.entries[EVENT_BATTERY_CAUTION].level = EVENT_LEVEL_INFO;
|
||||
events.entries[EVENT_BATTERY_CHG_STOP_REQ].level = EVENT_LEVEL_ERROR;
|
||||
events.entries[EVENT_BATTERY_DISCHG_STOP_REQ].level = EVENT_LEVEL_ERROR;
|
||||
events.entries[EVENT_BATTERY_CHG_DISCHG_STOP_REQ].level = EVENT_LEVEL_ERROR;
|
||||
events.entries[EVENT_BATTERY_OVERHEAT].level = EVENT_LEVEL_ERROR;
|
||||
events.entries[EVENT_BATTERY_OVERVOLTAGE].level = EVENT_LEVEL_WARNING;
|
||||
events.entries[EVENT_BATTERY_UNDERVOLTAGE].level = EVENT_LEVEL_WARNING;
|
||||
events.entries[EVENT_BATTERY_ISOLATION].level = EVENT_LEVEL_WARNING;
|
||||
events.entries[EVENT_VOLTAGE_DIFFERENCE].level = EVENT_LEVEL_INFO;
|
||||
events.entries[EVENT_LOW_SOH].level = EVENT_LEVEL_ERROR;
|
||||
events.entries[EVENT_HVIL_FAILURE].level = EVENT_LEVEL_ERROR;
|
||||
events.entries[EVENT_PRECHARGE_FAILURE].level = EVENT_LEVEL_INFO;
|
||||
events.entries[EVENT_INTERNAL_OPEN_FAULT].level = EVENT_LEVEL_ERROR;
|
||||
events.entries[EVENT_INVERTER_OPEN_CONTACTOR].level = EVENT_LEVEL_INFO;
|
||||
events.entries[EVENT_MODBUS_INVERTER_MISSING].level = EVENT_LEVEL_INFO;
|
||||
events.entries[EVENT_ERROR_OPEN_CONTACTOR].level = EVENT_LEVEL_INFO;
|
||||
events.entries[EVENT_CELL_UNDER_VOLTAGE].level = EVENT_LEVEL_ERROR;
|
||||
events.entries[EVENT_CELL_OVER_VOLTAGE].level = EVENT_LEVEL_ERROR;
|
||||
events.entries[EVENT_CELL_DEVIATION_HIGH].level = EVENT_LEVEL_WARNING;
|
||||
|
@ -163,10 +179,26 @@ void init_events(void) {
|
|||
events.entries[EVENT_SERIAL_TX_FAILURE].level = EVENT_LEVEL_ERROR;
|
||||
events.entries[EVENT_SERIAL_TRANSMITTER_FAILURE].level = EVENT_LEVEL_ERROR;
|
||||
events.entries[EVENT_EEPROM_WRITE].level = EVENT_LEVEL_INFO;
|
||||
events.entries[EVENT_RESET_UNKNOWN].level = EVENT_LEVEL_INFO;
|
||||
events.entries[EVENT_RESET_POWERON].level = EVENT_LEVEL_INFO;
|
||||
events.entries[EVENT_RESET_EXT].level = EVENT_LEVEL_INFO;
|
||||
events.entries[EVENT_RESET_SW].level = EVENT_LEVEL_INFO;
|
||||
events.entries[EVENT_RESET_PANIC].level = EVENT_LEVEL_WARNING;
|
||||
events.entries[EVENT_RESET_INT_WDT].level = EVENT_LEVEL_WARNING;
|
||||
events.entries[EVENT_RESET_TASK_WDT].level = EVENT_LEVEL_WARNING;
|
||||
events.entries[EVENT_RESET_WDT].level = EVENT_LEVEL_WARNING;
|
||||
events.entries[EVENT_RESET_DEEPSLEEP].level = EVENT_LEVEL_INFO;
|
||||
events.entries[EVENT_RESET_BROWNOUT].level = EVENT_LEVEL_INFO;
|
||||
events.entries[EVENT_RESET_SDIO].level = EVENT_LEVEL_INFO;
|
||||
events.entries[EVENT_RESET_USB].level = EVENT_LEVEL_INFO;
|
||||
events.entries[EVENT_RESET_JTAG].level = EVENT_LEVEL_INFO;
|
||||
events.entries[EVENT_RESET_EFUSE].level = EVENT_LEVEL_INFO;
|
||||
events.entries[EVENT_RESET_PWR_GLITCH].level = EVENT_LEVEL_INFO;
|
||||
events.entries[EVENT_RESET_CPU_LOCKUP].level = EVENT_LEVEL_WARNING;
|
||||
|
||||
events.entries[EVENT_EEPROM_WRITE].log = false; // Don't log the logger...
|
||||
|
||||
events.second_timer.set_interval(1000);
|
||||
events.second_timer.set_interval(600);
|
||||
// Write to EEPROM every X minutes (if an event has been set)
|
||||
events.ee_timer.set_interval(EE_WRITE_PERIOD_MINUTES * 60 * 1000);
|
||||
events.update_timer.set_interval(2000);
|
||||
|
@ -204,6 +236,10 @@ const char* get_event_message_string(EVENTS_ENUM_TYPE event) {
|
|||
return "ERROR: High amount of corrupted CAN messages detected. Check CAN wire shielding!";
|
||||
case EVENT_CAN_TX_FAILURE:
|
||||
return "ERROR: CAN messages failed to transmit, or no one on the bus to ACK the message!";
|
||||
case EVENT_CHARGE_LIMIT_EXCEEDED:
|
||||
return "Info: Inverter is charging faster than battery is allowing.";
|
||||
case EVENT_DISCHARGE_LIMIT_EXCEEDED:
|
||||
return "Info: Inverter is discharging faster than battery is allowing.";
|
||||
case EVENT_WATER_INGRESS:
|
||||
return "Water leakage inside battery detected. Operation halted. Inspect battery!";
|
||||
case EVENT_12V_LOW:
|
||||
|
@ -216,6 +252,8 @@ const char* get_event_message_string(EVENTS_ENUM_TYPE event) {
|
|||
return "Info: Battery is completely discharged";
|
||||
case EVENT_BATTERY_FULL:
|
||||
return "Info: Battery is fully charged";
|
||||
case EVENT_BATTERY_FROZEN:
|
||||
return "Info: Battery is too cold to operate optimally. Consider warming it up!";
|
||||
case EVENT_BATTERY_CAUTION:
|
||||
return "Info: Battery has raised a general caution flag. Might want to inspect it closely.";
|
||||
case EVENT_BATTERY_CHG_STOP_REQ:
|
||||
|
@ -228,18 +266,33 @@ const char* get_event_message_string(EVENTS_ENUM_TYPE event) {
|
|||
return "Info: COLD BATTERY! Battery requesting heating pads to activate!";
|
||||
case EVENT_BATTERY_WARMED_UP:
|
||||
return "Info: Battery requesting heating pads to stop. The battery is now warm enough.";
|
||||
case EVENT_BATTERY_OVERHEAT:
|
||||
return "ERROR: Battery overheated. Shutting down to prevent thermal runaway!";
|
||||
case EVENT_BATTERY_OVERVOLTAGE:
|
||||
return "Warning: Battery exceeding maximum design voltage. Discharge battery to prevent damage!";
|
||||
case EVENT_BATTERY_UNDERVOLTAGE:
|
||||
return "Warning: Battery under minimum design voltage. Charge battery to prevent damage!";
|
||||
case EVENT_BATTERY_ISOLATION:
|
||||
return "Warning: Battery reports isolation error. High voltage might be leaking to ground. Check battery!";
|
||||
case EVENT_VOLTAGE_DIFFERENCE:
|
||||
return "Info: Too large voltage diff between the batteries. Second battery cannot join the DC-link";
|
||||
case EVENT_LOW_SOH:
|
||||
return "ERROR: State of health critically low. Battery internal resistance too high to continue. Recycle "
|
||||
"battery.";
|
||||
case EVENT_HVIL_FAILURE:
|
||||
return "ERROR: Battery interlock loop broken. Check that high voltage connectors are seated. Battery will be "
|
||||
"disabled!";
|
||||
return "ERROR: Battery interlock loop broken. Check that high voltage / low voltage connectors are seated. "
|
||||
"Battery will be disabled!";
|
||||
case EVENT_PRECHARGE_FAILURE:
|
||||
return "Info: Battery failed to precharge. Check that capacitor is seated on high voltage output.";
|
||||
case EVENT_INTERNAL_OPEN_FAULT:
|
||||
return "ERROR: High voltage cable removed while battery running. Opening contactors!";
|
||||
case EVENT_INVERTER_OPEN_CONTACTOR:
|
||||
return "Info: Inverter side opened contactors. Normal operation.";
|
||||
case EVENT_ERROR_OPEN_CONTACTOR:
|
||||
return "Info: Too much time spent in error state. Opening contactors, not safe to continue charging. "
|
||||
"Check other error code for reason!";
|
||||
case EVENT_MODBUS_INVERTER_MISSING:
|
||||
return "Info: Modbus inverter has not sent any data. Inspect communication wiring!";
|
||||
case EVENT_CELL_UNDER_VOLTAGE:
|
||||
return "ERROR: CELL UNDERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!";
|
||||
case EVENT_CELL_OVER_VOLTAGE:
|
||||
|
@ -270,6 +323,39 @@ const char* get_event_message_string(EVENTS_ENUM_TYPE event) {
|
|||
return "OTA update timed out!";
|
||||
case EVENT_EEPROM_WRITE:
|
||||
return "Info: The EEPROM was written";
|
||||
case EVENT_RESET_UNKNOWN:
|
||||
return "Info: The board was reset unexpectedly, and reason can't be determined";
|
||||
case EVENT_RESET_POWERON:
|
||||
return "Info: The board was reset from a power-on event. Normal operation";
|
||||
case EVENT_RESET_EXT:
|
||||
return "Info: The board was reset from an external pin";
|
||||
case EVENT_RESET_SW:
|
||||
return "Info: The board was reset via software, webserver or OTA. Normal operation";
|
||||
case EVENT_RESET_PANIC:
|
||||
return "Warning: The board was reset due to an exception or panic. Inform developers!";
|
||||
case EVENT_RESET_INT_WDT:
|
||||
return "Warning: The board was reset due to an interrupt watchdog timeout. Inform developers!";
|
||||
case EVENT_RESET_TASK_WDT:
|
||||
return "Warning: The board was reset due to a task watchdog timeout. Inform developers!";
|
||||
case EVENT_RESET_WDT:
|
||||
return "Warning: The board was reset due to other watchdog timeout. Inform developers!";
|
||||
case EVENT_RESET_DEEPSLEEP:
|
||||
return "Info: The board was reset after exiting deep sleep mode";
|
||||
case EVENT_RESET_BROWNOUT:
|
||||
return "Info: The board was reset due to a momentary low voltage condition. This is expected during certain "
|
||||
"operations like flashing via USB";
|
||||
case EVENT_RESET_SDIO:
|
||||
return "Info: The board was reset over SDIO";
|
||||
case EVENT_RESET_USB:
|
||||
return "Info: The board was reset by the USB peripheral";
|
||||
case EVENT_RESET_JTAG:
|
||||
return "Info: The board was reset by JTAG";
|
||||
case EVENT_RESET_EFUSE:
|
||||
return "Info: The board was reset due to an efuse error";
|
||||
case EVENT_RESET_PWR_GLITCH:
|
||||
return "Info: The board was reset due to a detected power glitch";
|
||||
case EVENT_RESET_CPU_LOCKUP:
|
||||
return "Warning: The board was reset due to CPU lockup. Inform developers!";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
|
@ -355,11 +441,20 @@ static void update_event_level(void) {
|
|||
}
|
||||
|
||||
static void update_event_time(void) {
|
||||
// This should run roughly 2 times per second
|
||||
if (events.second_timer.elapsed() == true) {
|
||||
events.time_seconds++;
|
||||
uptime::calculateUptime(); // millis() overflows every 50 days, so update occasionally to adjust
|
||||
events.time_seconds = uptime::getDays() * DAYS_TO_SECS;
|
||||
events.time_seconds += uptime::getHours() * HOURS_TO_SECS;
|
||||
events.time_seconds += uptime::getMinutes() * MINUTES_TO_SECS;
|
||||
events.time_seconds += uptime::getSeconds();
|
||||
}
|
||||
}
|
||||
|
||||
unsigned long get_current_event_time_secs(void) {
|
||||
return events.time_seconds;
|
||||
}
|
||||
|
||||
static void log_event(EVENTS_ENUM_TYPE event, uint8_t data) {
|
||||
// Update head with wrap to 0
|
||||
if (++events.event_log_head_index == EE_NOF_EVENT_ENTRIES) {
|
||||
|
|
|
@ -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 0x0003 // 0x0000 to 0xFFFF
|
||||
#define EE_MAGIC_HEADER_VALUE 0x0009 // 0x0000 to 0xFFFF
|
||||
|
||||
#define GENERATE_ENUM(ENUM) ENUM,
|
||||
#define GENERATE_STRING(STRING) #STRING,
|
||||
|
@ -33,23 +33,33 @@
|
|||
XX(EVENT_CANFD_RX_FAILURE) \
|
||||
XX(EVENT_CAN_RX_WARNING) \
|
||||
XX(EVENT_CAN_TX_FAILURE) \
|
||||
XX(EVENT_CHARGE_LIMIT_EXCEEDED) \
|
||||
XX(EVENT_DISCHARGE_LIMIT_EXCEEDED) \
|
||||
XX(EVENT_WATER_INGRESS) \
|
||||
XX(EVENT_12V_LOW) \
|
||||
XX(EVENT_SOC_PLAUSIBILITY_ERROR) \
|
||||
XX(EVENT_KWH_PLAUSIBILITY_ERROR) \
|
||||
XX(EVENT_BATTERY_EMPTY) \
|
||||
XX(EVENT_BATTERY_FULL) \
|
||||
XX(EVENT_BATTERY_FROZEN) \
|
||||
XX(EVENT_BATTERY_CAUTION) \
|
||||
XX(EVENT_BATTERY_CHG_STOP_REQ) \
|
||||
XX(EVENT_BATTERY_DISCHG_STOP_REQ) \
|
||||
XX(EVENT_BATTERY_CHG_DISCHG_STOP_REQ) \
|
||||
XX(EVENT_BATTERY_OVERHEAT) \
|
||||
XX(EVENT_BATTERY_OVERVOLTAGE) \
|
||||
XX(EVENT_BATTERY_UNDERVOLTAGE) \
|
||||
XX(EVENT_BATTERY_ISOLATION) \
|
||||
XX(EVENT_BATTERY_REQUESTS_HEAT) \
|
||||
XX(EVENT_BATTERY_WARMED_UP) \
|
||||
XX(EVENT_VOLTAGE_DIFFERENCE) \
|
||||
XX(EVENT_LOW_SOH) \
|
||||
XX(EVENT_HVIL_FAILURE) \
|
||||
XX(EVENT_PRECHARGE_FAILURE) \
|
||||
XX(EVENT_INTERNAL_OPEN_FAULT) \
|
||||
XX(EVENT_INVERTER_OPEN_CONTACTOR) \
|
||||
XX(EVENT_MODBUS_INVERTER_MISSING) \
|
||||
XX(EVENT_ERROR_OPEN_CONTACTOR) \
|
||||
XX(EVENT_CELL_UNDER_VOLTAGE) \
|
||||
XX(EVENT_CELL_OVER_VOLTAGE) \
|
||||
XX(EVENT_CELL_DEVIATION_HIGH) \
|
||||
|
@ -65,6 +75,22 @@
|
|||
XX(EVENT_SERIAL_TX_FAILURE) \
|
||||
XX(EVENT_SERIAL_TRANSMITTER_FAILURE) \
|
||||
XX(EVENT_EEPROM_WRITE) \
|
||||
XX(EVENT_RESET_UNKNOWN) \
|
||||
XX(EVENT_RESET_POWERON) \
|
||||
XX(EVENT_RESET_EXT) \
|
||||
XX(EVENT_RESET_SW) \
|
||||
XX(EVENT_RESET_PANIC) \
|
||||
XX(EVENT_RESET_INT_WDT) \
|
||||
XX(EVENT_RESET_TASK_WDT) \
|
||||
XX(EVENT_RESET_WDT) \
|
||||
XX(EVENT_RESET_DEEPSLEEP) \
|
||||
XX(EVENT_RESET_BROWNOUT) \
|
||||
XX(EVENT_RESET_SDIO) \
|
||||
XX(EVENT_RESET_USB) \
|
||||
XX(EVENT_RESET_JTAG) \
|
||||
XX(EVENT_RESET_EFUSE) \
|
||||
XX(EVENT_RESET_PWR_GLITCH) \
|
||||
XX(EVENT_RESET_CPU_LOCKUP) \
|
||||
XX(EVENT_NOF_EVENTS)
|
||||
|
||||
typedef enum { EVENTS_ENUM_TYPE(GENERATE_ENUM) } EVENTS_ENUM_TYPE;
|
||||
|
@ -99,6 +125,7 @@ const char* get_event_enum_string(EVENTS_ENUM_TYPE event);
|
|||
const char* get_event_message_string(EVENTS_ENUM_TYPE event);
|
||||
const char* get_event_level_string(EVENTS_ENUM_TYPE event);
|
||||
const char* get_event_type(EVENTS_ENUM_TYPE event);
|
||||
unsigned long get_current_event_time_secs(void);
|
||||
|
||||
EVENTS_LEVEL_TYPE get_event_level(void);
|
||||
|
||||
|
|
|
@ -30,7 +30,6 @@ led_color led_get_color() {
|
|||
}
|
||||
|
||||
void LED::exe(void) {
|
||||
static bool test_all_colors = true;
|
||||
// Don't run too often
|
||||
if (!timer.elapsed()) {
|
||||
return;
|
||||
|
@ -70,7 +69,7 @@ void LED::exe(void) {
|
|||
break;
|
||||
case EVENT_LEVEL_ERROR:
|
||||
color = led_color::RED;
|
||||
pixels.setPixelColor(0, COLOR_RED(brightness)); // Red LED full brightness
|
||||
pixels.setPixelColor(0, COLOR_RED(LED_MAX_BRIGHTNESS)); // Red LED full brightness
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
@ -93,7 +92,6 @@ void LED::classic_run(void) {
|
|||
|
||||
void LED::flow_run(void) {
|
||||
// Determine how bright the LED should be
|
||||
bool power_positive;
|
||||
int16_t power_W = datalayer.battery.status.active_power_W;
|
||||
if (power_W < -50) {
|
||||
// Discharging
|
||||
|
@ -178,7 +176,6 @@ void LED::rainbow_run(void) {
|
|||
}
|
||||
|
||||
// Assemble the color
|
||||
uint32_t color = (static_cast<uint32_t>(r) << 16) | (static_cast<uint32_t>(g) << 8) | b;
|
||||
pixels.setPixelColor(0, pixels.Color(r, g, b)); // RGB
|
||||
}
|
||||
|
||||
|
|
|
@ -13,14 +13,19 @@ class LED {
|
|||
led_color color = led_color::GREEN;
|
||||
|
||||
LED()
|
||||
: mode(led_mode::CLASSIC),
|
||||
: pixels(1, LED_PIN, NEO_GRB + NEO_KHZ800),
|
||||
max_brightness(LED_MAX_BRIGHTNESS),
|
||||
pixels(1, LED_PIN, NEO_GRB + NEO_KHZ800),
|
||||
brightness(LED_MAX_BRIGHTNESS),
|
||||
mode(led_mode::CLASSIC),
|
||||
state(LED_NORMAL),
|
||||
timer(LED_EXECUTION_FREQUENCY) {}
|
||||
|
||||
LED(led_mode mode)
|
||||
: mode(mode),
|
||||
: pixels(1, LED_PIN, NEO_GRB + NEO_KHZ800),
|
||||
max_brightness(LED_MAX_BRIGHTNESS),
|
||||
pixels(1, LED_PIN, NEO_GRB + NEO_KHZ800),
|
||||
brightness(LED_MAX_BRIGHTNESS),
|
||||
mode(mode),
|
||||
state(LED_NORMAL),
|
||||
timer(LED_EXECUTION_FREQUENCY) {}
|
||||
|
||||
void exe(void);
|
||||
|
@ -30,7 +35,7 @@ class LED {
|
|||
Adafruit_NeoPixel pixels;
|
||||
uint8_t max_brightness;
|
||||
uint8_t brightness;
|
||||
led_mode mode = led_mode::CLASSIC;
|
||||
led_mode mode;
|
||||
led_state state = LED_NORMAL;
|
||||
MyTimer timer;
|
||||
|
||||
|
@ -46,4 +51,4 @@ void led_init(void);
|
|||
void led_exe(void);
|
||||
led_color led_get_color(void);
|
||||
|
||||
#endif
|
||||
#endif // LED_H_
|
||||
|
|
|
@ -25,9 +25,12 @@ enum led_color { GREEN, YELLOW, RED, BLUE, RGB };
|
|||
#define INTERVAL_10_MS_DELAYED 15
|
||||
#define INTERVAL_20_MS_DELAYED 30
|
||||
#define INTERVAL_30_MS_DELAYED 40
|
||||
#define INTERVAL_50_MS_DELAYED 65
|
||||
#define INTERVAL_100_MS_DELAYED 120
|
||||
#define INTERVAL_200_MS_DELAYED 240
|
||||
#define INTERVAL_500_MS_DELAYED 550
|
||||
|
||||
#define MAX_CAN_FAILURES 500 // Amount of malformed CAN messages to allow before raising a warning
|
||||
#define CAN_STILL_ALIVE \
|
||||
12 // Set by battery each time we get a CAN message. Decrements every 5seconds. Incase we reach 0 (after 60 seconds of inactivity)
|
||||
|
||||
#endif
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
#include "../../datalayer/datalayer.h"
|
||||
|
||||
String cellmonitor_processor(const String& var) {
|
||||
if (var == "ABC") {
|
||||
if (var == "X") {
|
||||
String content = "";
|
||||
// Page format
|
||||
content += "<style>";
|
||||
|
@ -37,7 +37,7 @@ String cellmonitor_processor(const String& var) {
|
|||
content += "<script>";
|
||||
// Populate cell data
|
||||
content += "const data = [";
|
||||
for (uint8_t i = 0u; i < MAX_AMOUNT_CELLS; i++) {
|
||||
for (uint8_t i = 0u; i < datalayer.battery.info.number_of_cells; i++) {
|
||||
if (datalayer.battery.status.cell_voltages_mV[i] == 0) {
|
||||
continue;
|
||||
}
|
||||
|
|
|
@ -9,16 +9,19 @@ const char EVENTS_HTML_END[] = R"=====(
|
|||
</div></div>
|
||||
<button onclick='home()'>Back to main page</button>
|
||||
<style>.event:nth-child(even){background-color:#455a64}.event:nth-child(odd){background-color:#394b52}</style>
|
||||
<script>function showEvent(){document.querySelector(".event-log");var i=(new Date).getTime()/1e3;document.querySelectorAll(".event").forEach(function(e){var n=e.querySelector(".sec-ago"),t=e.querySelector(".timestamp");if(n&&t){var o=parseInt(n.innerText,10),a=parseFloat(t.innerText),r=new Date(1e3*(i-a+o)).toLocaleString();n.innerText=r}})}function home(){window.location.href="/"}window.onload=function(){showEvent()}</script>
|
||||
<script>function showEvent(){document.querySelectorAll(".event").forEach(function(e){var n=e.querySelector(".sec-ago");n&&(n.innerText=new Date(new Date().getTime()-1e3*parseInt(n.innerText,10)).toLocaleString())})}function home(){window.location.href="/"}window.onload=function(){showEvent()}</script>
|
||||
)=====";
|
||||
|
||||
String events_processor(const String& var) {
|
||||
if (var == "ABC") {
|
||||
if (var == "X") {
|
||||
String content = "";
|
||||
content.reserve(5000);
|
||||
// Page format
|
||||
content.concat(FPSTR(EVENTS_HTML_START));
|
||||
const EVENTS_STRUCT_TYPE* event_pointer;
|
||||
|
||||
unsigned long timestamp_now = get_current_event_time_secs();
|
||||
|
||||
for (int i = 0; i < EVENT_NOF_EVENTS; i++) {
|
||||
event_pointer = get_event_pointer((EVENTS_ENUM_TYPE)i);
|
||||
EVENTS_ENUM_TYPE event_handle = static_cast<EVENTS_ENUM_TYPE>(i);
|
||||
|
@ -32,11 +35,10 @@ String events_processor(const String& var) {
|
|||
content.concat("<div class='event'>");
|
||||
content.concat("<div>" + String(get_event_enum_string(event_handle)) + "</div>");
|
||||
content.concat("<div>" + String(get_event_level_string(event_handle)) + "</div>");
|
||||
content.concat("<div class='sec-ago'>" + String(event_pointer->timestamp) + "</div>");
|
||||
content.concat("<div class='sec-ago'>" + String(timestamp_now - event_pointer->timestamp) + "</div>");
|
||||
content.concat("<div>" + String(event_pointer->occurences) + "</div>");
|
||||
content.concat("<div>" + String(event_pointer->data) + "</div>");
|
||||
content.concat("<div>" + String(get_event_message_string(event_handle)) + "</div>");
|
||||
content.concat("<div class='timestamp' style='display:none;'>" + String(millis() / 1000) + "</div>");
|
||||
content.concat("</div>"); // End of event row
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
const char index_html[] = R"rawliteral(
|
||||
<!doctypehtml><title>Battery Emulator</title><meta content="width=device-width"name=viewport><style>html{font-family:Arial;display:inline-block;text-align:center}h2{font-size:3rem}body{max-width:800px;margin:0 auto}</style><h2>Battery Emulator</h2>%ABC%
|
||||
<!doctypehtml><title>Battery Emulator</title><meta content="width=device-width"name=viewport><style>html{font-family:Arial;display:inline-block;text-align:center}h2{font-size:3rem}body{max-width:800px;margin:0 auto}</style>%X%
|
||||
)rawliteral";
|
||||
|
||||
/* The above code is minified (https://kangax.github.io/html-minifier/) to increase performance. Here is the full HTML function:
|
||||
|
@ -14,8 +14,7 @@ const char index_html[] = R"rawliteral(
|
|||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h2>Battery Emulator</h2>
|
||||
%ABC%
|
||||
%X%
|
||||
</body>
|
||||
</html>
|
||||
*/
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
#include "../../datalayer/datalayer.h"
|
||||
|
||||
String settings_processor(const String& var) {
|
||||
if (var == "ABC") {
|
||||
if (var == "X") {
|
||||
String content = "";
|
||||
//Page format
|
||||
content += "<style>";
|
||||
|
@ -13,6 +13,18 @@ String settings_processor(const String& var) {
|
|||
// Start a new block with a specific background color
|
||||
content += "<div style='background-color: #303E47; padding: 10px; margin-bottom: 10px;border-radius: 50px'>";
|
||||
|
||||
content += "<h4 style='color: white;'>SSID: <span id='SSID'>" + String(ssid.c_str()) +
|
||||
" </span> <button onclick='editSSID()'>Edit</button></h4>";
|
||||
content +=
|
||||
"<h4 style='color: white;'>Password: ######## <span id='Password'></span> <button "
|
||||
"onclick='editPassword()'>Edit</button></h4>";
|
||||
|
||||
// Close the block
|
||||
content += "</div>";
|
||||
|
||||
// Start a new block with a specific background color
|
||||
content += "<div style='background-color: #2D3F2F; padding: 10px; margin-bottom: 10px;border-radius: 50px'>";
|
||||
|
||||
// Show current settings with edit buttons and input fields
|
||||
content += "<h4 style='color: white;'>Battery capacity: <span id='BATTERY_WH_MAX'>" +
|
||||
String(datalayer.battery.info.total_capacity_Wh) +
|
||||
|
@ -78,208 +90,99 @@ String settings_processor(const String& var) {
|
|||
content += "</div>";
|
||||
#endif
|
||||
|
||||
content += "<script>";
|
||||
content += "function editComplete() {";
|
||||
content += " if (this.status == 200) {";
|
||||
content += " window.location.reload();";
|
||||
content += " }";
|
||||
content += "}";
|
||||
content += "function editError() {";
|
||||
content += " alert('Invalid input');";
|
||||
content += "}";
|
||||
content += "function editWh() {";
|
||||
content += "var value = prompt('How much energy the battery can store. Enter new Wh value (1-120000):');";
|
||||
content += "if (value !== null) {";
|
||||
content += " if (value >= 1 && value <= 120000) {";
|
||||
content += " var xhr = new XMLHttpRequest();";
|
||||
content += " xhr.onload = editComplete;";
|
||||
content += " xhr.onerror = editError;";
|
||||
content += " xhr.open('GET', '/updateBatterySize?value=' + value, true);";
|
||||
content += " xhr.send();";
|
||||
content += " } else {";
|
||||
content += " alert('Invalid value. Please enter a value between 1 and 120000.');";
|
||||
content += " }";
|
||||
content += "}";
|
||||
content += "}";
|
||||
content += "function editUseScaledSOC() {";
|
||||
content += "var value = prompt('Should SOC% be scaled? (0 = No, 1 = Yes):');";
|
||||
content += "if (value !== null) {";
|
||||
content += " if (value == 0 || value == 1) {";
|
||||
content += " var xhr = new XMLHttpRequest();";
|
||||
content += " xhr.onload = editComplete;";
|
||||
content += " xhr.onerror = editError;";
|
||||
content += " xhr.open('GET', '/updateUseScaledSOC?value=' + value, true);";
|
||||
content += " xhr.send();";
|
||||
content += " } else {";
|
||||
content += " alert('Invalid value. Please enter a value between 0 and 1.');";
|
||||
content += " }";
|
||||
content += "}";
|
||||
content += "}";
|
||||
content += "function editSocMax() {";
|
||||
content += "<script>"; // Note, this section is minified to improve performance
|
||||
content += "function editComplete(){if(this.status==200){window.location.reload();}}";
|
||||
content += "function editError(){alert('Invalid input');}";
|
||||
content +=
|
||||
"var value = prompt('Inverter will see fully charged (100pct)SOC when this value is reached. Enter new maximum "
|
||||
"SOC value that battery will charge to (50.0-100.0):');";
|
||||
content += "if (value !== null) {";
|
||||
content += " if (value >= 50 && value <= 100) {";
|
||||
content += " var xhr = new XMLHttpRequest();";
|
||||
content += " xhr.onload = editComplete;";
|
||||
content += " xhr.onerror = editError;";
|
||||
content += " xhr.open('GET', '/updateSocMax?value=' + value, true);";
|
||||
content += " xhr.send();";
|
||||
content += " } else {";
|
||||
content += " alert('Invalid value. Please enter a value between 50.0 and 100.0');";
|
||||
content += " }";
|
||||
content += "}";
|
||||
content += "}";
|
||||
content += "function editSocMin() {";
|
||||
"function editSSID(){var value=prompt('Enter new SSID:');if(value!==null){var xhr=new "
|
||||
"XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/"
|
||||
"updateSSID?value='+encodeURIComponent(value),true);xhr.send();}}";
|
||||
content +=
|
||||
"var value = prompt('Inverter will see completely discharged (0pct)SOC when this value is reached. Enter new "
|
||||
"minimum SOC value that battery will discharge to (0-50.0):');";
|
||||
content += "if (value !== null) {";
|
||||
content += " if (value >= 0 && value <= 50) {";
|
||||
content += " var xhr = new XMLHttpRequest();";
|
||||
content += " xhr.onload = editComplete;";
|
||||
content += " xhr.onerror = editError;";
|
||||
content += " xhr.open('GET', '/updateSocMin?value=' + value, true);";
|
||||
content += " xhr.send();";
|
||||
content += " } else {";
|
||||
content += " alert('Invalid value. Please enter a value between 0 and 50.0');";
|
||||
content += " }";
|
||||
content += "}";
|
||||
content += "}";
|
||||
content += "function editMaxChargeA() {";
|
||||
"function editPassword(){var value=prompt('Enter new password:');if(value!==null){var xhr=new "
|
||||
"XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/"
|
||||
"updatePassword?value='+encodeURIComponent(value),true);xhr.send();}}";
|
||||
content +=
|
||||
"var value = prompt('BYD CAN specific setting, some inverters needs to be artificially limited. Enter new "
|
||||
"maximum charge current in A (0-1000.0):');";
|
||||
content += "if (value !== null) {";
|
||||
content += " if (value >= 0 && value <= 1000) {";
|
||||
content += " var xhr = new XMLHttpRequest();";
|
||||
content += " xhr.onload = editComplete;";
|
||||
content += " xhr.onerror = editError;";
|
||||
content += " xhr.open('GET', '/updateMaxChargeA?value=' + value, true);";
|
||||
content += " xhr.send();";
|
||||
content += " } else {";
|
||||
content += " alert('Invalid value. Please enter a value between 0 and 1000.0');";
|
||||
content += " }";
|
||||
content += "}";
|
||||
content += "}";
|
||||
content += "function editMaxDischargeA() {";
|
||||
"function editWh(){var value=prompt('How much energy the battery can store. Enter new Wh value "
|
||||
"(1-120000):');if(value!==null){if(value>=1&&value<=120000){var xhr=new "
|
||||
"XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/"
|
||||
"updateBatterySize?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter a value between 1 "
|
||||
"and 120000.');}}}";
|
||||
content +=
|
||||
"var value = prompt('BYD CAN specific setting, some inverters needs to be artificially limited. Enter new "
|
||||
"maximum discharge current in A (0-1000.0):');";
|
||||
content += "if (value !== null) {";
|
||||
content += " if (value >= 0 && value <= 1000) {";
|
||||
content += " var xhr = new XMLHttpRequest();";
|
||||
content += " xhr.onload = editComplete;";
|
||||
content += " xhr.onerror = editError;";
|
||||
content += " xhr.open('GET', '/updateMaxDischargeA?value=' + value, true);";
|
||||
content += " xhr.send();";
|
||||
content += " } else {";
|
||||
content += " alert('Invalid value. Please enter a value between 0 and 1000.0');";
|
||||
content += " }";
|
||||
content += "}";
|
||||
content += "}";
|
||||
"function editUseScaledSOC(){var value=prompt('Should SOC% be scaled? (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 "
|
||||
"and 1.');}}}";
|
||||
content +=
|
||||
"function editSocMax(){var value=prompt('Inverter will see fully charged (100pct)SOC when this value is "
|
||||
"reached. Enter new maximum SOC value that battery will charge to "
|
||||
"(50.0-100.0):');if(value!==null){if(value>=50&&value<=100){var xhr=new "
|
||||
"XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/"
|
||||
"updateSocMax?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter a value between 50.0 and "
|
||||
"100.0');}}}";
|
||||
content +=
|
||||
"function editSocMin(){var value=prompt('Inverter will see completely discharged (0pct)SOC when this value is "
|
||||
"reached. Enter new minimum SOC value that battery will discharge to "
|
||||
"(0-50.0):');if(value!==null){if(value>=0&&value<=50){var xhr=new "
|
||||
"XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/"
|
||||
"updateSocMin?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter a value between 0 and "
|
||||
"50.0');}}}";
|
||||
content +=
|
||||
"function editMaxChargeA(){var value=prompt('Some inverters needs to be artificially limited. Enter new "
|
||||
"maximum charge current in A (0-1000.0):');if(value!==null){if(value>=0&&value<=1000){var xhr=new "
|
||||
"XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/"
|
||||
"updateMaxChargeA?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter a value between 0 "
|
||||
"and 1000.0');}}}";
|
||||
content +=
|
||||
"function editMaxDischargeA(){var value=prompt('Some inverters needs to be artificially limited. Enter new "
|
||||
"maximum discharge current in A (0-1000.0):');if(value!==null){if(value>=0&&value<=1000){var xhr=new "
|
||||
"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 += "</script>";
|
||||
|
||||
#ifdef TEST_FAKE_BATTERY
|
||||
content += "function editFakeBatteryVoltage() {";
|
||||
content += " var value = prompt('Enter new fake battery voltage');";
|
||||
content += "if (value !== null) {";
|
||||
content += " if (value >= 0 && value <= 5000) {";
|
||||
content += " var xhr = new XMLHttpRequest();";
|
||||
content += " xhr.onload = editComplete;";
|
||||
content += " xhr.onerror = editError;";
|
||||
content += " xhr.open('GET', '/updateFakeBatteryVoltage?value=' + value, true);";
|
||||
content += " xhr.send();";
|
||||
content += " } else {";
|
||||
content += " alert('Invalid value. Please enter a value between 0 and 1000');";
|
||||
content += " }";
|
||||
content += "}";
|
||||
content += "}";
|
||||
content +=
|
||||
"function editFakeBatteryVoltage(){var value=prompt('Enter new fake battery "
|
||||
"voltage');if(value!==null){if(value>=0&&value<=5000){var xhr=new "
|
||||
"XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/"
|
||||
"updateFakeBatteryVoltage?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter a value "
|
||||
"between 0 and 1000');}}}";
|
||||
#endif
|
||||
|
||||
#if defined CHEVYVOLT_CHARGER || defined NISSANLEAF_CHARGER
|
||||
content += "function editChargerHVDCEnabled() {";
|
||||
content += " var value = prompt('Enable or disable HV DC output. Enter 1 for enabled, 0 for disabled');";
|
||||
content += " if (value !== null) {";
|
||||
content += " if (value == 0 || value == 1) {";
|
||||
content += " var xhr = new XMLHttpRequest();";
|
||||
content += " xhr.onload = editComplete;";
|
||||
content += " xhr.onerror = editError;";
|
||||
content += " xhr.open('GET', '/updateChargerHvEnabled?value=' + value, true);";
|
||||
content += " xhr.send();";
|
||||
content += " }";
|
||||
content += " } else {";
|
||||
content += " alert('Invalid value. Please enter 1 or 0');";
|
||||
content += " }";
|
||||
content += "}";
|
||||
|
||||
content += "function editChargerAux12vEnabled() {";
|
||||
content +=
|
||||
"var value = prompt('Enable or disable low voltage 12v auxiliary DC output. Enter 1 for enabled, 0 for "
|
||||
"disabled');";
|
||||
content += "if (value !== null) {";
|
||||
content += " if (value == 0 || value == 1) {";
|
||||
content += " var xhr = new XMLHttpRequest();";
|
||||
content += " xhr.onload = editComplete;";
|
||||
content += " xhr.onerror = editError;";
|
||||
content += " xhr.open('GET', '/updateChargerAux12vEnabled?value=' + value, true);";
|
||||
content += " xhr.send();";
|
||||
content += " } else {";
|
||||
content += " alert('Invalid value. Please enter 1 or 0');";
|
||||
content += " }";
|
||||
content += "}";
|
||||
content += "}";
|
||||
|
||||
content += "function editChargerSetpointVDC() {";
|
||||
"function editChargerHVDCEnabled(){var value=prompt('Enable or disable HV DC output. Enter 1 for enabled, 0 "
|
||||
"for disabled');if(value!==null){if(value==0||value==1){var xhr=new "
|
||||
"XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/"
|
||||
"updateChargerHvEnabled?value='+value,true);xhr.send();}}else{alert('Invalid value. Please enter 1 or 0');}}";
|
||||
content +=
|
||||
"var value = prompt('Set charging voltage. Input will be validated against inverter and/or charger "
|
||||
"configuration parameters, but use sensible values like 200 to 420.');";
|
||||
content += "if (value !== null) {";
|
||||
content += " if (value >= 0 && value <= 1000) {";
|
||||
content += " var xhr = new XMLHttpRequest();";
|
||||
content += " xhr.onload = editComplete;";
|
||||
content += " xhr.onerror = editError;";
|
||||
content += " xhr.open('GET', '/updateChargeSetpointV?value=' + value, true);";
|
||||
content += " xhr.send();";
|
||||
content += " } else {";
|
||||
content += " alert('Invalid value. Please enter a value between 0 and 1000');";
|
||||
content += " }";
|
||||
content += "}";
|
||||
content += "}";
|
||||
|
||||
content += "function editChargerSetpointIDC() {";
|
||||
"function editChargerAux12vEnabled(){var value=prompt('Enable or disable low voltage 12v auxiliary DC output. "
|
||||
"Enter 1 for enabled, 0 for disabled');if(value!==null){if(value==0||value==1){var xhr=new "
|
||||
"XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/"
|
||||
"updateChargerAux12vEnabled?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter 1 or "
|
||||
"0');}}}";
|
||||
content +=
|
||||
"var value = prompt('Set charging amperage. Input will be validated against inverter and/or charger "
|
||||
"configuration parameters, but use sensible values like 6 to 48.');";
|
||||
content += "if (value !== null) {";
|
||||
content += " if (value >= 0 && value <= 1000) {";
|
||||
content += " var xhr = new XMLHttpRequest();";
|
||||
content += " xhr.onload = editComplete;";
|
||||
content += " xhr.onerror = editError;";
|
||||
content += " xhr.open('GET', '/updateChargeSetpointA?value=' + value, true);";
|
||||
content += " xhr.send();";
|
||||
content += " } else {";
|
||||
content += " alert('Invalid value. Please enter a value between 0 and 100');";
|
||||
content += " }";
|
||||
content += "}";
|
||||
content += "}";
|
||||
|
||||
content += "function editChargerSetpointEndI() {";
|
||||
"function editChargerSetpointVDC(){var value=prompt('Set charging voltage. Input will be validated against "
|
||||
"inverter and/or charger configuration parameters, but use sensible values like 200 to "
|
||||
"420.');if(value!==null){if(value>=0&&value<=1000){var xhr=new "
|
||||
"XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/"
|
||||
"updateChargeSetpointV?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter a value between "
|
||||
"0 and 1000');}}}";
|
||||
content +=
|
||||
"var value = prompt('Set amperage that terminates charge as being sufficiently complete. Input will be "
|
||||
"validated against inverter and/or charger configuration parameters, but use sensible values like 1-5.');";
|
||||
content += "if (value !== null) {";
|
||||
content += " if (value >= 0 && value <= 1000) {";
|
||||
content += " var xhr = new XMLHttpRequest();";
|
||||
content += " xhr.onload = editComplete;";
|
||||
content += " xhr.onerror = editError;";
|
||||
content += " xhr.open('GET', '/updateChargeEndA?value=' + value, true);";
|
||||
content += " xhr.send();";
|
||||
content += " } else {";
|
||||
content += " alert('Invalid value. Please enter a value between 0 and 100');";
|
||||
content += " }";
|
||||
content += "}";
|
||||
content += "}";
|
||||
"function editChargerSetpointIDC(){var value=prompt('Set charging amperage. Input will be validated against "
|
||||
"inverter and/or charger configuration parameters, but use sensible values like 6 to "
|
||||
"48.');if(value!==null){if(value>=0&&value<=1000){var xhr=new "
|
||||
"XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/"
|
||||
"updateChargeSetpointA?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter a value between "
|
||||
"0 and 100');}}}";
|
||||
content +=
|
||||
"function editChargerSetpointEndI(){var value=prompt('Set amperage that terminates charge as being "
|
||||
"sufficiently complete. Input will be validated against inverter and/or charger configuration parameters, but "
|
||||
"use sensible values like 1-5.');if(value!==null){if(value>=0&&value<=1000){var xhr=new "
|
||||
"XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/"
|
||||
"updateChargeEndA?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter a value between 0 "
|
||||
"and 100');}}}";
|
||||
#endif
|
||||
content += "</script>";
|
||||
|
||||
|
|
|
@ -2,6 +2,10 @@
|
|||
#define SETTINGS_H
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <string>
|
||||
|
||||
extern std::string ssid;
|
||||
extern std::string password;
|
||||
|
||||
#include "../../../USER_SETTINGS.h" // Needed for WiFi ssid and password
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ bool ota_active = false;
|
|||
unsigned const long WIFI_MONITOR_INTERVAL_TIME = 15000;
|
||||
unsigned const long INIT_WIFI_CONNECT_TIMEOUT = 8000; // Timeout for initial WiFi connect in milliseconds
|
||||
unsigned const long DEFAULT_WIFI_RECONNECT_INTERVAL = 1000; // Default WiFi reconnect interval in ms
|
||||
unsigned const long MAX_WIFI_RETRY_INTERVAL = 30000; // Maximum wifi retry interval in ms
|
||||
unsigned const long MAX_WIFI_RETRY_INTERVAL = 90000; // Maximum wifi retry interval in ms
|
||||
unsigned long last_wifi_monitor_time = millis(); //init millis so wifi monitor doesn't run immediately
|
||||
unsigned long wifi_reconnect_interval = DEFAULT_WIFI_RECONNECT_INTERVAL;
|
||||
unsigned long last_wifi_attempt_time = millis(); //init millis so wifi monitor doesn't run immediately
|
||||
|
@ -43,7 +43,7 @@ void init_webserver() {
|
|||
} else {
|
||||
WiFi.mode(WIFI_STA); // Only Router connection
|
||||
}
|
||||
init_WiFi_STA(ssid, password, wifi_channel);
|
||||
init_WiFi_STA(ssid.c_str(), password.c_str(), wifi_channel);
|
||||
|
||||
String content = index_html;
|
||||
|
||||
|
@ -64,6 +64,37 @@ void init_webserver() {
|
|||
server.on("/events", HTTP_GET,
|
||||
[](AsyncWebServerRequest* request) { request->send_P(200, "text/html", index_html, events_processor); });
|
||||
|
||||
// Route for editing SSID
|
||||
server.on("/updateSSID", HTTP_GET, [](AsyncWebServerRequest* request) {
|
||||
if (request->hasParam("value")) {
|
||||
String value = request->getParam("value")->value();
|
||||
if (value.length() <= 63) { // Check if SSID is within the allowable length
|
||||
ssid = value.c_str();
|
||||
storeSettings();
|
||||
request->send(200, "text/plain", "Updated successfully");
|
||||
} else {
|
||||
request->send(400, "text/plain", "SSID must be 63 characters or less");
|
||||
}
|
||||
} else {
|
||||
request->send(400, "text/plain", "Bad Request");
|
||||
}
|
||||
});
|
||||
// Route for editing Password
|
||||
server.on("/updatePassword", HTTP_GET, [](AsyncWebServerRequest* request) {
|
||||
if (request->hasParam("value")) {
|
||||
String value = request->getParam("value")->value();
|
||||
if (value.length() > 8) { // Check if password is within the allowable length
|
||||
password = value.c_str();
|
||||
storeSettings();
|
||||
request->send(200, "text/plain", "Updated successfully");
|
||||
} else {
|
||||
request->send(400, "text/plain", "Password must be atleast 8 characters");
|
||||
}
|
||||
} else {
|
||||
request->send(400, "text/plain", "Bad Request");
|
||||
}
|
||||
});
|
||||
|
||||
// Route for editing Wh
|
||||
server.on("/updateBatterySize", HTTP_GET, [](AsyncWebServerRequest* request) {
|
||||
if (request->hasParam("value")) {
|
||||
|
@ -302,7 +333,7 @@ void wifi_monitor() {
|
|||
Serial.println(getConnectResultString(status));
|
||||
#endif
|
||||
if (wifi_state == INIT) { //we haven't been connected yet, try the init logic
|
||||
init_WiFi_STA(ssid, password, wifi_channel);
|
||||
init_WiFi_STA(ssid.c_str(), password.c_str(), wifi_channel);
|
||||
} else { //we were connected before, try the reconnect logic
|
||||
if (currentMillis - last_wifi_attempt_time > wifi_reconnect_interval) {
|
||||
last_wifi_attempt_time = currentMillis;
|
||||
|
@ -319,7 +350,7 @@ void wifi_monitor() {
|
|||
wifi_reconnect_interval = DEFAULT_WIFI_RECONNECT_INTERVAL;
|
||||
// Print local IP address and start web server
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.print("Connected to WiFi network: " + String(ssid));
|
||||
Serial.print("Connected to WiFi network: " + String(ssid.c_str()));
|
||||
Serial.print(" IP address: " + WiFi.localIP().toString());
|
||||
Serial.print(" Signal Strength: " + String(WiFi.RSSI()) + " dBm");
|
||||
Serial.println(" Channel: " + String(WiFi.channel()));
|
||||
|
@ -346,6 +377,9 @@ void init_WiFi_STA(const char* ssid, const char* password, const uint8_t wifi_ch
|
|||
WiFi.begin(ssid, password, wifi_channel);
|
||||
WiFi.setAutoReconnect(true); // Enable auto reconnect
|
||||
wl_status_t result = static_cast<wl_status_t>(WiFi.waitForConnectResult(INIT_WIFI_CONNECT_TIMEOUT));
|
||||
if (result) {
|
||||
//TODO: Add event or serial print?
|
||||
}
|
||||
}
|
||||
|
||||
// Function to initialize ElegantOTA
|
||||
|
@ -358,8 +392,9 @@ void init_ElegantOTA() {
|
|||
}
|
||||
|
||||
String processor(const String& var) {
|
||||
if (var == "ABC") {
|
||||
if (var == "X") {
|
||||
String content = "";
|
||||
content += "<h2>" + String(ssidAP) + "</h2>"; // ssidAP name is used as header name
|
||||
//Page format
|
||||
content += "<style>";
|
||||
content += "body { background-color: black; color: white; }";
|
||||
|
@ -370,11 +405,24 @@ String processor(const String& var) {
|
|||
|
||||
// Show version number
|
||||
content += "<h4>Software: " + String(version_number) + "</h4>";
|
||||
// Show hardware used:
|
||||
#ifdef HW_LILYGO
|
||||
content += "<h4>Hardware: LilyGo T-CAN485</h4>";
|
||||
#endif
|
||||
#ifdef HW_STARK
|
||||
content += "<h4>Hardware: Stark CMR Module</h4>";
|
||||
#endif
|
||||
content += "<h4>Uptime: " + uptime_formatter::getUptime() + "</h4>";
|
||||
#ifdef FUNCTION_TIME_MEASUREMENT
|
||||
// Load information
|
||||
content += "<h4>Core task max load: " + String(datalayer.system.status.core_task_max_us) + " us</h4>";
|
||||
content += "<h4>Core task max load last 10 s: " + String(datalayer.system.status.core_task_10s_max_us) + " us</h4>";
|
||||
content += "<h4>MQTT task max load last 10 s: " + String(datalayer.system.status.mqtt_task_10s_max_us) + " us</h4>";
|
||||
content +=
|
||||
"<h4>MQTT function (MQTT task) max load last 10 s: " + String(datalayer.system.status.mqtt_task_10s_max_us) +
|
||||
" us</h4>";
|
||||
content +=
|
||||
"<h4>WIFI function (MQTT task) max load last 10 s: " + String(datalayer.system.status.wifi_task_10s_max_us) +
|
||||
" us</h4>";
|
||||
content +=
|
||||
"<h4>loop() task max load last 10 s: " + String(datalayer.system.status.loop_task_10s_max_us) + " us</h4>";
|
||||
content += "<h4>Max load @ worst case execution of core task:</h4>";
|
||||
|
@ -382,17 +430,16 @@ String processor(const String& var) {
|
|||
content += "<h4>5s function timing: " + String(datalayer.system.status.time_snap_5s_us) + " us</h4>";
|
||||
content += "<h4>CAN/serial RX function timing: " + String(datalayer.system.status.time_snap_comm_us) + " us</h4>";
|
||||
content += "<h4>CAN TX function timing: " + String(datalayer.system.status.time_snap_cantx_us) + " us</h4>";
|
||||
content += "<h4>Wifi and OTA function timing: " + String(datalayer.system.status.time_snap_wifi_us) + " us</h4>";
|
||||
content += "<h4>OTA function timing: " + String(datalayer.system.status.time_snap_ota_us) + " us</h4>";
|
||||
#endif
|
||||
|
||||
wl_status_t status = WiFi.status();
|
||||
// Display ssid of network connected to and, if connected to the WiFi, its own IP
|
||||
content += "<h4>SSID: " + String(ssid) + "</h4>";
|
||||
content += "<h4>SSID: " + String(ssid.c_str()) + "</h4>";
|
||||
if (status == WL_CONNECTED) {
|
||||
content += "<h4>IP: " + WiFi.localIP().toString() + "</h4>";
|
||||
// Get and display the signal strength (RSSI)
|
||||
content += "<h4>Signal Strength: " + String(WiFi.RSSI()) + " dBm</h4>";
|
||||
content += "<h4>Channel: " + String(WiFi.channel()) + "</h4>";
|
||||
// Get and display the signal strength (RSSI) and channel
|
||||
content += "<h4>Signal strength: " + String(WiFi.RSSI()) + " dBm, at channel " + String(WiFi.channel()) + "</h4>";
|
||||
} else {
|
||||
content += "<h4>Wifi state: " + getConnectResultString(status) + "</h4>";
|
||||
}
|
||||
|
@ -434,26 +481,41 @@ String processor(const String& var) {
|
|||
#ifdef BMW_I3_BATTERY
|
||||
content += "BMW i3";
|
||||
#endif
|
||||
#ifdef BYD_ATTO_3_BATTERY
|
||||
content += "BYD Atto 3";
|
||||
#endif
|
||||
#ifdef CHADEMO_BATTERY
|
||||
content += "Chademo V2X mode";
|
||||
#endif
|
||||
#ifdef IMIEV_CZERO_ION_BATTERY
|
||||
content += "I-Miev / C-Zero / Ion Triplet";
|
||||
#endif
|
||||
#ifdef JAGUAR_IPACE_BATTERY
|
||||
content += "Jaguar I-PACE";
|
||||
#endif
|
||||
#ifdef KIA_HYUNDAI_64_BATTERY
|
||||
content += "Kia/Hyundai 64kWh";
|
||||
#endif
|
||||
#ifdef KIA_E_GMP_BATTERY
|
||||
content += "Kia/Hyundai EGMP platform";
|
||||
#endif
|
||||
#ifdef KIA_HYUNDAI_HYBRID_BATTERY
|
||||
content += "Kia/Hyundai Hybrid";
|
||||
#endif
|
||||
#ifdef MG_5_BATTERY
|
||||
content += "MG 5";
|
||||
#endif
|
||||
#ifdef NISSAN_LEAF_BATTERY
|
||||
content += "Nissan LEAF";
|
||||
#endif
|
||||
#ifdef RENAULT_KANGOO_BATTERY
|
||||
content += "Renault Kangoo";
|
||||
#endif
|
||||
#ifdef RENAULT_ZOE_BATTERY
|
||||
content += "Renault Zoe";
|
||||
#ifdef RENAULT_ZOE_GEN1_BATTERY
|
||||
content += "Renault Zoe Gen1 22/40";
|
||||
#endif
|
||||
#ifdef RENAULT_ZOE_GEN2_BATTERY
|
||||
content += "Renault Zoe Gen2 50";
|
||||
#endif
|
||||
#ifdef SERIAL_LINK_RECEIVER
|
||||
content += "Serial link to another LilyGo board";
|
||||
|
@ -491,7 +553,7 @@ String processor(const String& var) {
|
|||
content += "<div style='display: flex; width: 100%;'>";
|
||||
content += "<div style='flex: 1; background-color: ";
|
||||
#else
|
||||
// Start a new block with a specific background color. Color changes depending on BMS status
|
||||
// Start a new block with a specific background color. Color changes depending on system status
|
||||
content += "<div style='background-color: ";
|
||||
#endif
|
||||
|
||||
|
@ -547,11 +609,11 @@ String processor(const String& var) {
|
|||
content += "<h4>Temperature max: " + String(tempMaxFloat, 1) + " C</h4>";
|
||||
content += "<h4>Temperature min: " + String(tempMinFloat, 1) + " C</h4>";
|
||||
if (datalayer.battery.status.bms_status == ACTIVE) {
|
||||
content += "<h4>BMS Status: OK </h4>";
|
||||
content += "<h4>System status: OK </h4>";
|
||||
} else if (datalayer.battery.status.bms_status == UPDATING) {
|
||||
content += "<h4>BMS Status: UPDATING </h4>";
|
||||
content += "<h4>System status: UPDATING </h4>";
|
||||
} else {
|
||||
content += "<h4>BMS Status: FAULT </h4>";
|
||||
content += "<h4>System status: FAULT </h4>";
|
||||
}
|
||||
if (datalayer.battery.status.current_dA == 0) {
|
||||
content += "<h4>Battery idle</h4>";
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include <Preferences.h>
|
||||
#include <WiFi.h>
|
||||
#include "../../include.h"
|
||||
#include "../../lib/YiannisBourkelis-Uptime-Library/src/uptime_formatter.h"
|
||||
#include "../../lib/ayushsharma82-ElegantOTA/src/ElegantOTA.h"
|
||||
#ifdef MQTT
|
||||
#include "../../lib/knolleary-pubsubclient/PubSubClient.h"
|
||||
|
@ -17,8 +18,9 @@
|
|||
|
||||
extern const char* version_number; // The current software version, shown on webserver
|
||||
|
||||
extern const char* ssid;
|
||||
extern const char* password;
|
||||
#include <string>
|
||||
extern std::string ssid;
|
||||
extern std::string password;
|
||||
extern const uint8_t wifi_channel;
|
||||
extern const char* ssidAP;
|
||||
extern const char* passwordAP;
|
||||
|
|
|
@ -8,10 +8,12 @@
|
|||
#include "system_settings.h"
|
||||
|
||||
#include "devboard/hal/hal.h"
|
||||
#include "devboard/safety/safety.h"
|
||||
#include "devboard/utils/time_meas.h"
|
||||
#include "devboard/utils/types.h"
|
||||
|
||||
#include "battery/BATTERIES.h"
|
||||
#include "charger/CHARGERS.h"
|
||||
#include "inverter/INVERTERS.h"
|
||||
|
||||
/* - ERROR CHECKS BELOW, DON'T TOUCH - */
|
||||
|
@ -25,7 +27,7 @@
|
|||
#error CAN-FD AND DUAL-CAN CANNOT BE USED SIMULTANEOUSLY
|
||||
#endif
|
||||
|
||||
#if defined(BYD_MODBUS) || defined(LUNA2000_MODBUS)
|
||||
#ifdef MODBUS_INVERTER_SELECTED
|
||||
#if defined(SERIAL_LINK_RECEIVER) || defined(SERIAL_LINK_TRANSMITTER)
|
||||
// Check that Dual LilyGo via RS485 option isn't enabled, this collides with Modbus!
|
||||
#error MODBUS CANNOT BE USED IN DOUBLE LILYGO SETUPS! CHECK USER SETTINGS!
|
||||
|
|
|
@ -114,24 +114,29 @@ static uint16_t inverter_SOC = 0;
|
|||
static long inverter_timestamp = 0;
|
||||
static bool initialDataSent = 0;
|
||||
|
||||
void update_values_can_byd() { //This function maps all the values fetched from battery CAN to the correct CAN messages
|
||||
void update_values_can_inverter() { //This function maps all the values fetched from battery CAN to the correct CAN messages
|
||||
//Calculate values
|
||||
|
||||
if (datalayer.battery.status.voltage_dV > 10) { // Only update value when we have voltage available to avoid div0
|
||||
charge_current =
|
||||
((datalayer.battery.status.max_charge_power_W * 10) /
|
||||
datalayer.battery.info.max_design_voltage_dV); //Charge power in W , max volt in V+1decimal (P=UI, solve for I)
|
||||
datalayer.battery.status.voltage_dV); //Charge power in W , max volt in V+1decimal (P=UI, solve for I)
|
||||
//The above calculation results in (30 000*10)/3700=81A
|
||||
charge_current = (charge_current * 10); //Value needs a decimal before getting sent to inverter (81.0A)
|
||||
|
||||
discharge_current =
|
||||
((datalayer.battery.status.max_discharge_power_W * 10) /
|
||||
datalayer.battery.status.voltage_dV); //Charge power in W , max volt in V+1decimal (P=UI, solve for I)
|
||||
//The above calculation results in (30 000*10)/3700=81A
|
||||
discharge_current = (discharge_current * 10); //Value needs a decimal before getting sent to inverter (81.0A)
|
||||
}
|
||||
|
||||
if (charge_current > datalayer.battery.info.max_charge_amp_dA) {
|
||||
charge_current =
|
||||
datalayer.battery.info
|
||||
.max_charge_amp_dA; //Cap the value to the max allowed Amp. Some inverters cannot handle large values.
|
||||
}
|
||||
|
||||
discharge_current =
|
||||
((datalayer.battery.status.max_discharge_power_W * 10) /
|
||||
datalayer.battery.info.max_design_voltage_dV); //Charge power in W , max volt in V+1decimal (P=UI, solve for I)
|
||||
//The above calculation results in (30 000*10)/3700=81A
|
||||
discharge_current = (discharge_current * 10); //Value needs a decimal before getting sent to inverter (81.0A)
|
||||
if (discharge_current > datalayer.battery.info.max_discharge_amp_dA) {
|
||||
discharge_current =
|
||||
datalayer.battery.info
|
||||
|
@ -199,7 +204,7 @@ void update_values_can_byd() { //This function maps all the values fetched from
|
|||
#endif
|
||||
}
|
||||
|
||||
void receive_can_byd(CAN_frame_t rx_frame) {
|
||||
void receive_can_inverter(CAN_frame_t rx_frame) {
|
||||
switch (rx_frame.MsgID) {
|
||||
case 0x151: //Message originating from BYD HVS compatible inverter. Reply with CAN identifier!
|
||||
if (rx_frame.data.u8[0] & 0x01) { //Battery requests identification
|
||||
|
@ -229,7 +234,7 @@ void receive_can_byd(CAN_frame_t rx_frame) {
|
|||
}
|
||||
}
|
||||
|
||||
void send_can_byd() {
|
||||
void send_can_inverter() {
|
||||
unsigned long currentMillis = millis();
|
||||
// Send initial CAN data once on bootup
|
||||
if (!initialDataSent) {
|
||||
|
|
|
@ -1,14 +1,10 @@
|
|||
#ifndef BYD_CAN_H
|
||||
#define BYD_CAN_H
|
||||
#include <Arduino.h>
|
||||
#include "../include.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
|
||||
#define INVERTER_SELECTED
|
||||
#define CAN_INVERTER_SELECTED
|
||||
|
||||
void update_values_can_byd();
|
||||
void send_can_byd();
|
||||
void receive_can_byd(CAN_frame_t rx_frame);
|
||||
void send_intial_data();
|
||||
|
||||
#endif
|
||||
|
|
|
@ -1,11 +1,25 @@
|
|||
#include "../include.h"
|
||||
#ifdef BYD_MODBUS
|
||||
#include "../datalayer/datalayer.h"
|
||||
#include "../devboard/utils/events.h"
|
||||
#include "BYD-MODBUS.h"
|
||||
|
||||
void update_modbus_registers_byd() {
|
||||
//Updata for ModbusRTU Server for BYD
|
||||
// For modbus register definitions, see https://gitlab.com/pelle8/inverter_resources/-/blob/main/byd_registers_modbus_rtu.md
|
||||
|
||||
#define HISTORY_LENGTH 3 // Amount of samples(minutes) that needs to match for register to be considered stale
|
||||
static unsigned long previousMillis60s = 0; // will store last time a 60s event occured
|
||||
static uint32_t user_configured_max_discharge_W = 0;
|
||||
static uint32_t user_configured_max_charge_W = 0;
|
||||
static uint32_t max_discharge_W = 0;
|
||||
static uint32_t max_charge_W = 0;
|
||||
static uint16_t register_401_history[HISTORY_LENGTH] = {0};
|
||||
static uint8_t history_index = 0;
|
||||
static uint8_t bms_char_dis_status = STANDBY;
|
||||
static bool all_401_values_equal = false;
|
||||
|
||||
void update_modbus_registers_inverter() {
|
||||
verify_temperature_modbus();
|
||||
verify_inverter_modbus();
|
||||
handle_update_data_modbusp201_byd();
|
||||
handle_update_data_modbusp301_byd();
|
||||
}
|
||||
|
@ -28,46 +42,20 @@ void handle_static_data_modbus_byd() {
|
|||
memcpy(&mbPV[i], data_array_pointers[arr_idx], data_size);
|
||||
i += data_size / sizeof(uint16_t);
|
||||
}
|
||||
static uint16_t init_p201[13] = {0, 0, 0, MAX_POWER, MAX_POWER, 0, 0, 53248, 10, 53248, 10, 0, 0};
|
||||
memcpy(&mbPV[200], init_p201, sizeof(init_p201));
|
||||
static uint16_t init_p301[24] = {0, 0, 128, 0, 0, 0, 0, 0, 0, 2000, 0, 2000,
|
||||
75, 95, 0, 0, 16, 22741, 0, 0, 13, 52064, 230, 9900};
|
||||
memcpy(&mbPV[300], init_p301, sizeof(init_p301));
|
||||
}
|
||||
|
||||
void handle_update_data_modbusp201_byd() {
|
||||
// Store the data into the array
|
||||
static uint16_t system_data[13];
|
||||
system_data[0] = 0; // Id.: p201 Value.: 0 Scaled value.: 0 Comment.: Always 0
|
||||
system_data[1] = 0; // Id.: p202 Value.: 0 Scaled value.: 0 Comment.: Always 0
|
||||
if (datalayer.battery.info.total_capacity_Wh > 60000) {
|
||||
system_data[2] = 60000;
|
||||
} else {
|
||||
system_data[2] =
|
||||
(datalayer.battery.info
|
||||
.total_capacity_Wh); // Id.: p203 Value.: 32000 Scaled value.: 32kWh Comment.: Capacity rated, maximum value is 60000 (60kWh)
|
||||
}
|
||||
system_data[3] = MAX_POWER; // Id.: p204 Value.: 32000 Scaled value.: 32kWh Comment.: Nominal capacity
|
||||
system_data[4] =
|
||||
MAX_POWER; // Id.: p205 Value.: 14500 Scaled value.: 30,42kW Comment.: Max Charge/Discharge Power=10.24kW (lowest value of 204 and 205 will be enforced by Gen24)
|
||||
system_data[5] =
|
||||
(datalayer.battery.info
|
||||
.max_design_voltage_dV); // Id.: p206 Value.: 3667 Scaled value.: 362,7VDC Comment.: Max Voltage, if higher charging is not possible (goes into forced discharge)
|
||||
system_data[6] =
|
||||
(datalayer.battery.info
|
||||
.min_design_voltage_dV); // Id.: p207 Value.: 2776 Scaled value.: 277,6VDC Comment.: Min Voltage, if lower Gen24 disables battery
|
||||
system_data[7] =
|
||||
53248; // Id.: p208 Value.: 53248 Scaled value.: 53248 Comment.: Always 53248 for this BYD, Peak Charge power?
|
||||
system_data[8] = 10; // Id.: p209 Value.: 10 Scaled value.: 10 Comment.: Always 10
|
||||
system_data[9] =
|
||||
53248; // Id.: p210 Value.: 53248 Scaled value.: 53248 Comment.: Always 53248 for this BYD, Peak Discharge power?
|
||||
system_data[10] = 10; // Id.: p211 Value.: 10 Scaled value.: 10 Comment.: Always 10
|
||||
system_data[11] = 0; // Id.: p212 Value.: 0 Scaled value.: 0 Comment.: Always 0
|
||||
system_data[12] = 0; // Id.: p213 Value.: 0 Scaled value.: 0 Comment.: Always 0
|
||||
static uint16_t i = 200;
|
||||
memcpy(&mbPV[i], system_data, sizeof(system_data));
|
||||
mbPV[202] = std::min(datalayer.battery.info.total_capacity_Wh, static_cast<uint32_t>(60000u)); //Cap to 60kWh
|
||||
mbPV[205] = (datalayer.battery.info.max_design_voltage_dV); // Max Voltage, if higher Gen24 forces discharge
|
||||
mbPV[206] = (datalayer.battery.info.min_design_voltage_dV); // Min Voltage, if lower Gen24 disables battery
|
||||
}
|
||||
|
||||
void handle_update_data_modbusp301_byd() {
|
||||
// Store the data into the array
|
||||
static uint16_t battery_data[24];
|
||||
|
||||
static uint8_t bms_char_dis_status = STANDBY;
|
||||
if (datalayer.battery.status.current_dA == 0) {
|
||||
bms_char_dis_status = STANDBY;
|
||||
} else if (datalayer.battery.status.current_dA < 0) { //Negative value = Discharging
|
||||
|
@ -75,76 +63,34 @@ void handle_update_data_modbusp301_byd() {
|
|||
} else { //Positive value = Charging
|
||||
bms_char_dis_status = CHARGING;
|
||||
}
|
||||
// Convert max discharge Amp value to max Watt
|
||||
user_configured_max_discharge_W =
|
||||
((datalayer.battery.info.max_discharge_amp_dA * datalayer.battery.info.max_design_voltage_dV) / 100);
|
||||
// Use the smaller value, battery reported value OR user configured value
|
||||
max_discharge_W = std::min(datalayer.battery.status.max_discharge_power_W, user_configured_max_discharge_W);
|
||||
|
||||
// Convert max charge Amp value to max Watt
|
||||
user_configured_max_charge_W =
|
||||
((datalayer.battery.info.max_charge_amp_dA * datalayer.battery.info.max_design_voltage_dV) / 100);
|
||||
// Use the smaller value, battery reported value OR user configured value
|
||||
max_charge_W = std::min(datalayer.battery.status.max_charge_power_W, user_configured_max_charge_W);
|
||||
|
||||
if (datalayer.battery.status.bms_status == ACTIVE) {
|
||||
battery_data[8] =
|
||||
datalayer.battery.status
|
||||
.voltage_dV; // Id.: p309 Value.: 3161 Scaled value.: 316,1VDC Comment.: Batt Voltage outer (0 if status !=3, maybe a contactor closes when active): 173.4V
|
||||
mbPV[308] = datalayer.battery.status.voltage_dV;
|
||||
} else {
|
||||
battery_data[8] = 0;
|
||||
mbPV[308] = 0;
|
||||
}
|
||||
battery_data[0] =
|
||||
datalayer.battery.status
|
||||
.bms_status; // Id.: p301 Value.: 3 Scaled value.: 3 Comment.: status(*): ACTIVE - [0..5]<>[STANDBY,INACTIVE,DARKSTART,ACTIVE,FAULT,UPDATING]
|
||||
battery_data[1] = 0; // Id.: p302 Value.: 0 Scaled value.: 0 Comment.: always 0
|
||||
battery_data[2] = 128 + bms_char_dis_status; // Id.: p303 Value.: 130 Scaled value.: 130 Comment.: mode(*): normal
|
||||
battery_data[3] =
|
||||
datalayer.battery.status
|
||||
.reported_soc; // Id.: p304 Value.: 1700 Scaled value.: 50% Comment.: SOC: (50% would equal 5000)
|
||||
if (datalayer.battery.info.total_capacity_Wh > 60000) {
|
||||
battery_data[4] = 60000;
|
||||
} else {
|
||||
battery_data[4] =
|
||||
datalayer.battery.info.total_capacity_Wh; // Id.: p305 Value.: 32000 Scaled value.: 32kWh Comment.: tot cap:
|
||||
}
|
||||
if (datalayer.battery.status.remaining_capacity_Wh > 60000) {
|
||||
battery_data[5] = 60000;
|
||||
} else {
|
||||
// Id.: p306 Value.: 13260 Scaled value.: 13,26kWh Comment.: remaining cap: 7.68kWh
|
||||
battery_data[5] = datalayer.battery.status.remaining_capacity_Wh;
|
||||
}
|
||||
if (datalayer.battery.status.max_discharge_power_W > 30000) {
|
||||
battery_data[6] = 30000;
|
||||
} else {
|
||||
battery_data[6] =
|
||||
datalayer.battery.status
|
||||
.max_discharge_power_W; // Id.: p307 Value.: 25604 Scaled value.: 25,604kW Comment.: max/target discharge power: 0W (0W > restricts to no discharge)
|
||||
}
|
||||
|
||||
if (datalayer.battery.status.max_charge_power_W > 30000) {
|
||||
battery_data[7] = 30000;
|
||||
} else {
|
||||
battery_data[7] =
|
||||
datalayer.battery.status
|
||||
.max_charge_power_W; // Id.: p308 Value.: 25604 Scaled value.: 25,604kW Comment.: max/target charge power: 4.3kW (during charge), both 307&308 can be set (>0) at the same time
|
||||
}
|
||||
//Battery_data[8] set previously in function // Id.: p309 Value.: 3161 Scaled value.: 316,1VDC Comment.: Batt Voltage outer (0 if status !=3, maybe a contactor closes when active): 173.4V
|
||||
battery_data[9] =
|
||||
2000; // Id.: p310 Value.: 64121 Scaled value.: 6412,1W Comment.: Current Power to API: if>32768... -(65535-61760)=3775W
|
||||
battery_data[10] =
|
||||
datalayer.battery.status
|
||||
.voltage_dV; // Id.: p311 Value.: 3161 Scaled value.: 316,1VDC Comment.: Batt Voltage inner: 173.2V
|
||||
battery_data[11] = 2000; // Id.: p312 Value.: 64121 Scaled value.: 6412,1W Comment.: p310
|
||||
battery_data[12] =
|
||||
datalayer.battery.status
|
||||
.temperature_min_dC; // Id.: p313 Value.: 75 Scaled value.: 7,5 Comment.: temp min: 7 degrees (if below 0....65535-t)
|
||||
battery_data[13] =
|
||||
datalayer.battery.status
|
||||
.temperature_max_dC; // Id.: p314 Value.: 95 Scaled value.: 9,5 Comment.: temp max: 9 degrees (if below 0....65535-t)
|
||||
battery_data[14] = 0; // Id.: p315 Value.: 0 Scaled value.: 0 Comment.: always 0
|
||||
battery_data[15] = 0; // Id.: p316 Value.: 0 Scaled value.: 0 Comment.: always 0
|
||||
battery_data[16] = 16; // Id.: p317 Value.: 0 Scaled value.: 0 Comment.: counter charge hi
|
||||
battery_data[17] =
|
||||
22741; // Id.: p318 Value.: 0 Scaled value.: 0 Comment.: counter charge lo....65536*101+9912 = 6629048 Wh?
|
||||
battery_data[18] = 0; // Id.: p319 Value.: 0 Scaled value.: 0 Comment.: always 0
|
||||
battery_data[19] = 0; // Id.: p320 Value.: 0 Scaled value.: 0 Comment.: always 0
|
||||
battery_data[20] = 13; // Id.: p321 Value.: 0 Scaled value.: 0 Comment.: counter discharge hi
|
||||
battery_data[21] =
|
||||
52064; // Id.: p322 Value.: 0 Scaled value.: 0 Comment.: counter discharge lo....65536*92+7448 = 6036760 Wh?
|
||||
battery_data[22] = 230; // Id.: p323 Value.: 0 Scaled value.: 0 Comment.: device temperature (23 degrees)
|
||||
battery_data[23] = datalayer.battery.status.soh_pptt; // Id.: p324 Value.: 9900 Scaled value.: 99% Comment.: SOH
|
||||
static uint16_t i = 300;
|
||||
memcpy(&mbPV[i], battery_data, sizeof(battery_data));
|
||||
mbPV[300] = datalayer.battery.status.bms_status;
|
||||
mbPV[302] = 128 + bms_char_dis_status;
|
||||
mbPV[303] = datalayer.battery.status.reported_soc;
|
||||
mbPV[304] = std::min(datalayer.battery.info.total_capacity_Wh, static_cast<uint32_t>(60000u)); //Cap to 60kWh
|
||||
mbPV[305] = std::min(datalayer.battery.status.remaining_capacity_Wh, static_cast<uint32_t>(60000u)); //Cap to 60kWh
|
||||
mbPV[306] = std::min(max_discharge_W, static_cast<uint32_t>(30000u)); //Cap to 30000 if exceeding
|
||||
mbPV[307] = std::min(max_charge_W, static_cast<uint32_t>(30000u)); //Cap to 30000 if exceeding
|
||||
mbPV[310] = datalayer.battery.status.voltage_dV;
|
||||
mbPV[312] = datalayer.battery.status.temperature_min_dC;
|
||||
mbPV[313] = datalayer.battery.status.temperature_max_dC;
|
||||
mbPV[323] = datalayer.battery.status.soh_pptt;
|
||||
}
|
||||
|
||||
void verify_temperature_modbus() {
|
||||
|
@ -168,4 +114,32 @@ void verify_temperature_modbus() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
void verify_inverter_modbus() {
|
||||
// Every 60 seconds, the Gen24 writes to this 401 register, alternating between 00FF and FF00.
|
||||
// We sample the register every 60 seconds. Incase the value has not changed for 3 minutes, we raise an event
|
||||
unsigned long currentMillis = millis();
|
||||
|
||||
if (currentMillis - previousMillis60s >= INTERVAL_60_S) {
|
||||
previousMillis60s = currentMillis;
|
||||
|
||||
all_401_values_equal = true;
|
||||
for (int i = 0; i < HISTORY_LENGTH; ++i) {
|
||||
if (register_401_history[i] != mbPV[401]) {
|
||||
all_401_values_equal = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (all_401_values_equal) {
|
||||
set_event(EVENT_MODBUS_INVERTER_MISSING, 0);
|
||||
} else {
|
||||
clear_event(EVENT_MODBUS_INVERTER_MISSING);
|
||||
}
|
||||
|
||||
// Update history
|
||||
register_401_history[history_index] = mbPV[401];
|
||||
history_index = (history_index + 1) % HISTORY_LENGTH;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
#define BYD_MODBUS_H
|
||||
#include "../include.h"
|
||||
|
||||
#define INVERTER_SELECTED
|
||||
#define MODBUS_INVERTER_SELECTED
|
||||
|
||||
#define MB_RTU_NUM_VALUES 30000
|
||||
#define MAX_POWER 40960 //BYD Modbus specific value
|
||||
|
@ -11,7 +11,7 @@ extern uint16_t mbPV[MB_RTU_NUM_VALUES];
|
|||
|
||||
void handle_static_data_modbus_byd();
|
||||
void verify_temperature_modbus();
|
||||
void verify_inverter_modbus();
|
||||
void handle_update_data_modbusp201_byd();
|
||||
void handle_update_data_modbusp301_byd();
|
||||
void update_modbus_registers_byd();
|
||||
#endif
|
||||
|
|
|
@ -39,4 +39,15 @@
|
|||
#include "SERIAL-LINK-TRANSMITTER-INVERTER.h"
|
||||
#endif
|
||||
|
||||
#ifdef CAN_INVERTER_SELECTED
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h" // This include is annoying, consider defining a frame type in types.h
|
||||
void update_values_can_inverter();
|
||||
void receive_can_inverter(CAN_frame_t rx_frame);
|
||||
void send_can_inverter();
|
||||
#endif
|
||||
|
||||
#ifdef MODBUS_INVERTER_SELECTED
|
||||
void update_modbus_registers_inverter();
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
|
|
@ -3,8 +3,7 @@
|
|||
#include "../datalayer/datalayer.h"
|
||||
#include "LUNA2000-MODBUS.h"
|
||||
|
||||
void update_modbus_registers_luna2000() {
|
||||
//Updata for ModbusRTU Server for Luna2000
|
||||
void update_modbus_registers_inverter() {
|
||||
handle_update_data_modbus32051();
|
||||
handle_update_data_modbus39500();
|
||||
}
|
||||
|
|
|
@ -2,13 +2,12 @@
|
|||
#define LUNA2000_MODBUS_H
|
||||
#include "../include.h"
|
||||
|
||||
#define INVERTER_SELECTED
|
||||
#define MODBUS_INVERTER_SELECTED
|
||||
|
||||
#define MB_RTU_NUM_VALUES 30000
|
||||
|
||||
extern uint16_t mbPV[MB_RTU_NUM_VALUES];
|
||||
|
||||
void update_modbus_registers_luna2000();
|
||||
void handle_update_data_modbus32051();
|
||||
void handle_update_data_modbus39500();
|
||||
#endif
|
||||
|
|
|
@ -7,8 +7,9 @@
|
|||
|
||||
#define SEND_0 //If defined, the messages will have ID ending with 0 (useful for some inverters)
|
||||
//#define SEND_1 //If defined, the messages will have ID ending with 1 (useful for some inverters)
|
||||
#define INVERT_VOLTAGE //If defined, the min/max voltage frames will be inverted, \
|
||||
#define INVERT_LOW_HIGH_BYTES //If defined, certain frames will have inverted low/high bytes \
|
||||
//useful for some inverters like Sofar that report the voltages incorrect otherwise
|
||||
//#define SET_30K_OFFSET //If defined, current values are sent with a 30k offest (useful for ferroamp)
|
||||
|
||||
/* Do not change code below unless you are sure what you are doing */
|
||||
//Actual content messages
|
||||
|
@ -170,10 +171,32 @@ CAN_frame_t PYLON_4291 = {.FIR = {.B =
|
|||
.MsgID = 0x4291,
|
||||
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
|
||||
void update_values_can_pylon() { //This function maps all the values fetched from battery CAN to the correct CAN messages
|
||||
static int16_t max_charge_current = 0;
|
||||
static int16_t max_discharge_current = 0;
|
||||
|
||||
void update_values_can_inverter() { //This function maps all the values fetched from battery CAN to the correct CAN messages
|
||||
//There are more mappings that could be added, but this should be enough to use as a starting point
|
||||
// Note we map both 0 and 1 messages
|
||||
|
||||
if (datalayer.battery.status.voltage_dV > 10) { //div0 safeguard
|
||||
max_charge_current = (datalayer.battery.status.max_charge_power_W * 100) / datalayer.battery.status.voltage_dV;
|
||||
if (max_charge_current > datalayer.battery.info.max_charge_amp_dA) {
|
||||
max_charge_current =
|
||||
datalayer.battery.info
|
||||
.max_charge_amp_dA; //Cap the value to the max allowed Amp. Some inverters cannot handle large values.
|
||||
}
|
||||
max_discharge_current =
|
||||
(datalayer.battery.status.max_discharge_power_W * 100) / datalayer.battery.status.voltage_dV;
|
||||
if (max_discharge_current > datalayer.battery.info.max_discharge_amp_dA) {
|
||||
max_discharge_current =
|
||||
datalayer.battery.info
|
||||
.max_discharge_amp_dA; //Cap the value to the max allowed Amp. Some inverters cannot handle large values.
|
||||
}
|
||||
} else {
|
||||
max_charge_current = 0;
|
||||
max_discharge_current = 0;
|
||||
}
|
||||
|
||||
//Charge / Discharge allowed
|
||||
PYLON_4280.data.u8[0] = 0;
|
||||
PYLON_4280.data.u8[1] = 0;
|
||||
|
@ -196,6 +219,18 @@ void update_values_can_pylon() { //This function maps all the values fetched fr
|
|||
PYLON_4211.data.u8[2] = (datalayer.battery.status.current_dA >> 8);
|
||||
PYLON_4211.data.u8[3] = (datalayer.battery.status.current_dA & 0x00FF);
|
||||
|
||||
// BMS Temperature (We dont have BMS temp, send max cell voltage instead)
|
||||
#ifdef INVERT_LOW_HIGH_BYTES //Useful for Sofar inverters
|
||||
PYLON_4210.data.u8[4] = ((datalayer.battery.status.temperature_max_dC + 1000) & 0x00FF);
|
||||
PYLON_4210.data.u8[5] = ((datalayer.battery.status.temperature_max_dC + 1000) >> 8);
|
||||
PYLON_4211.data.u8[4] = ((datalayer.battery.status.temperature_max_dC + 1000) & 0x00FF);
|
||||
PYLON_4211.data.u8[5] = ((datalayer.battery.status.temperature_max_dC + 1000) >> 8);
|
||||
#else
|
||||
PYLON_4210.data.u8[4] = ((datalayer.battery.status.temperature_max_dC + 1000) >> 8);
|
||||
PYLON_4210.data.u8[5] = ((datalayer.battery.status.temperature_max_dC + 1000) & 0x00FF);
|
||||
PYLON_4211.data.u8[4] = ((datalayer.battery.status.temperature_max_dC + 1000) >> 8);
|
||||
PYLON_4211.data.u8[5] = ((datalayer.battery.status.temperature_max_dC + 1000) & 0x00FF);
|
||||
#endif
|
||||
//SOC (100.00%)
|
||||
PYLON_4210.data.u8[6] = (datalayer.battery.status.reported_soc / 100); //Remove decimals
|
||||
PYLON_4211.data.u8[6] = (datalayer.battery.status.reported_soc / 100); //Remove decimals
|
||||
|
@ -204,7 +239,41 @@ void update_values_can_pylon() { //This function maps all the values fetched fr
|
|||
PYLON_4210.data.u8[7] = (datalayer.battery.status.soh_pptt / 100);
|
||||
PYLON_4211.data.u8[7] = (datalayer.battery.status.soh_pptt / 100);
|
||||
|
||||
#ifdef INVERT_VOLTAGE //Useful for Sofar inverters \
|
||||
// Status=Bit 0,1,2= 0:Sleep, 1:Charge, 2:Discharge 3:Idle. Bit3 ForceChargeReq. Bit4 Balance charge Request
|
||||
if (datalayer.battery.status.current_dA < 0) {
|
||||
PYLON_4251.data.u8[0] = (0x11); // Charge
|
||||
} else if (datalayer.battery.status.current_dA > 0) {
|
||||
PYLON_4251.data.u8[0] = (0x12); // Discharge
|
||||
} else if (datalayer.battery.status.current_dA == 0) {
|
||||
PYLON_4251.data.u8[0] = (0x13); // Idle
|
||||
}
|
||||
|
||||
#ifdef INVERT_LOW_HIGH_BYTES //Useful for Sofar inverters
|
||||
//Voltage (370.0)
|
||||
PYLON_4210.data.u8[0] = (datalayer.battery.status.voltage_dV & 0x00FF);
|
||||
PYLON_4210.data.u8[1] = (datalayer.battery.status.voltage_dV >> 8);
|
||||
PYLON_4211.data.u8[0] = (datalayer.battery.status.voltage_dV & 0x00FF);
|
||||
PYLON_4211.data.u8[1] = (datalayer.battery.status.voltage_dV >> 8);
|
||||
|
||||
#ifdef SET_30K_OFFSET
|
||||
//Current (15.0)
|
||||
PYLON_4210.data.u8[2] = ((datalayer.battery.status.current_dA + 30000) & 0x00FF);
|
||||
PYLON_4210.data.u8[3] = ((datalayer.battery.status.current_dA + 30000) >> 8);
|
||||
PYLON_4211.data.u8[2] = ((datalayer.battery.status.current_dA + 30000) & 0x00FF);
|
||||
PYLON_4211.data.u8[3] = ((datalayer.battery.status.current_dA + 30000) >> 8);
|
||||
#else
|
||||
PYLON_4210.data.u8[2] = (datalayer.battery.status.current_dA & 0x00FF);
|
||||
PYLON_4210.data.u8[3] = (datalayer.battery.status.current_dA >> 8);
|
||||
PYLON_4211.data.u8[2] = (datalayer.battery.status.current_dA & 0x00FF);
|
||||
PYLON_4211.data.u8[3] = (datalayer.battery.status.current_dA >> 8);
|
||||
#endif
|
||||
|
||||
// BMS Temperature (We dont have BMS temp, send max cell voltage instead)
|
||||
PYLON_4210.data.u8[4] = ((datalayer.battery.status.temperature_max_dC + 1000) & 0x00FF);
|
||||
PYLON_4210.data.u8[5] = ((datalayer.battery.status.temperature_max_dC + 1000) >> 8);
|
||||
PYLON_4211.data.u8[4] = ((datalayer.battery.status.temperature_max_dC + 1000) & 0x00FF);
|
||||
PYLON_4211.data.u8[5] = ((datalayer.battery.status.temperature_max_dC + 1000) >> 8);
|
||||
|
||||
//Maxvoltage (eg 400.0V = 4000 , 16bits long) Discharge Cutoff Voltage
|
||||
PYLON_4220.data.u8[0] = (datalayer.battery.info.max_design_voltage_dV & 0x00FF);
|
||||
PYLON_4220.data.u8[1] = (datalayer.battery.info.max_design_voltage_dV >> 8);
|
||||
|
@ -216,20 +285,187 @@ void update_values_can_pylon() { //This function maps all the values fetched fr
|
|||
PYLON_4220.data.u8[3] = (datalayer.battery.info.min_design_voltage_dV >> 8);
|
||||
PYLON_4221.data.u8[2] = (datalayer.battery.info.min_design_voltage_dV & 0x00FF);
|
||||
PYLON_4221.data.u8[3] = (datalayer.battery.info.min_design_voltage_dV >> 8);
|
||||
|
||||
#ifdef SET_30K_OFFSET
|
||||
//Max ChargeCurrent
|
||||
PYLON_4220.data.u8[4] = ((max_charge_current + 30000) & 0x00FF);
|
||||
PYLON_4220.data.u8[5] = ((max_charge_current + 30000) >> 8);
|
||||
PYLON_4221.data.u8[4] = ((max_charge_current + 30000) & 0x00FF);
|
||||
PYLON_4221.data.u8[5] = ((max_charge_current + 30000) >> 8);
|
||||
|
||||
//Max DischargeCurrent
|
||||
PYLON_4220.data.u8[6] = ((30000 - max_discharge_current) & 0x00FF);
|
||||
PYLON_4220.data.u8[7] = ((30000 - max_discharge_current) >> 8);
|
||||
PYLON_4221.data.u8[6] = ((30000 - max_discharge_current) & 0x00FF);
|
||||
PYLON_4221.data.u8[7] = ((30000 - max_discharge_current) >> 8);
|
||||
#else
|
||||
//Minvoltage (eg 300.0V = 3000 , 16bits long) Charge Cutoff Voltage
|
||||
PYLON_4220.data.u8[0] = (datalayer.battery.info.min_design_voltage_dV >> 8);
|
||||
PYLON_4220.data.u8[1] = (datalayer.battery.info.min_design_voltage_dV & 0x00FF);
|
||||
PYLON_4221.data.u8[0] = (datalayer.battery.info.min_design_voltage_dV >> 8);
|
||||
PYLON_4221.data.u8[1] = (datalayer.battery.info.min_design_voltage_dV & 0x00FF);
|
||||
//Max ChargeCurrent
|
||||
PYLON_4220.data.u8[4] = (max_charge_current & 0x00FF);
|
||||
PYLON_4220.data.u8[5] = (max_charge_current >> 8);
|
||||
PYLON_4221.data.u8[4] = (max_charge_current & 0x00FF);
|
||||
PYLON_4221.data.u8[5] = (max_charge_current >> 8);
|
||||
|
||||
//Max DishargeCurrent
|
||||
PYLON_4220.data.u8[6] = (max_discharge_current & 0x00FF);
|
||||
PYLON_4220.data.u8[7] = (max_discharge_current >> 8);
|
||||
PYLON_4221.data.u8[6] = (max_discharge_current & 0x00FF);
|
||||
PYLON_4221.data.u8[7] = (max_discharge_current >> 8);
|
||||
#endif
|
||||
|
||||
//Max cell voltage
|
||||
PYLON_4230.data.u8[0] = (datalayer.battery.status.cell_max_voltage_mV & 0x00FF);
|
||||
PYLON_4230.data.u8[1] = (datalayer.battery.status.cell_max_voltage_mV >> 8);
|
||||
PYLON_4231.data.u8[0] = (datalayer.battery.status.cell_max_voltage_mV & 0x00FF);
|
||||
PYLON_4231.data.u8[1] = (datalayer.battery.status.cell_max_voltage_mV >> 8);
|
||||
|
||||
//Min cell voltage
|
||||
PYLON_4230.data.u8[2] = (datalayer.battery.status.cell_min_voltage_mV & 0x00FF);
|
||||
PYLON_4230.data.u8[3] = (datalayer.battery.status.cell_min_voltage_mV >> 8);
|
||||
PYLON_4231.data.u8[2] = (datalayer.battery.status.cell_min_voltage_mV & 0x00FF);
|
||||
PYLON_4231.data.u8[3] = (datalayer.battery.status.cell_min_voltage_mV >> 8);
|
||||
|
||||
//Max temperature per cell
|
||||
PYLON_4240.data.u8[0] = (datalayer.battery.status.temperature_max_dC & 0x00FF);
|
||||
PYLON_4240.data.u8[1] = (datalayer.battery.status.temperature_max_dC >> 8);
|
||||
PYLON_4241.data.u8[0] = (datalayer.battery.status.temperature_max_dC & 0x00FF);
|
||||
PYLON_4241.data.u8[1] = (datalayer.battery.status.temperature_max_dC >> 8);
|
||||
|
||||
//Max/Min temperature per cell
|
||||
PYLON_4240.data.u8[2] = (datalayer.battery.status.temperature_min_dC & 0x00FF);
|
||||
PYLON_4240.data.u8[3] = (datalayer.battery.status.temperature_min_dC >> 8);
|
||||
PYLON_4240.data.u8[2] = (datalayer.battery.status.temperature_min_dC & 0x00FF);
|
||||
PYLON_4240.data.u8[3] = (datalayer.battery.status.temperature_min_dC >> 8);
|
||||
|
||||
//Max temperature per module
|
||||
PYLON_4270.data.u8[0] = (datalayer.battery.status.temperature_max_dC & 0x00FF);
|
||||
PYLON_4270.data.u8[1] = (datalayer.battery.status.temperature_max_dC >> 8);
|
||||
PYLON_4271.data.u8[0] = (datalayer.battery.status.temperature_max_dC & 0x00FF);
|
||||
PYLON_4271.data.u8[1] = (datalayer.battery.status.temperature_max_dC >> 8);
|
||||
|
||||
//Min temperature per module
|
||||
PYLON_4270.data.u8[2] = (datalayer.battery.status.temperature_min_dC & 0x00FF);
|
||||
PYLON_4270.data.u8[3] = (datalayer.battery.status.temperature_min_dC >> 8);
|
||||
PYLON_4271.data.u8[2] = (datalayer.battery.status.temperature_min_dC & 0x00FF);
|
||||
PYLON_4271.data.u8[3] = (datalayer.battery.status.temperature_min_dC >> 8);
|
||||
#else
|
||||
//Voltage (370.0)
|
||||
PYLON_4210.data.u8[0] = (datalayer.battery.status.voltage_dV >> 8;
|
||||
PYLON_4210.data.u8[1] = (datalayer.battery.status.voltage_dV & 0x00FF);
|
||||
PYLON_4211.data.u8[0] = (datalayer.battery.status.voltage_dV >> 8);
|
||||
PYLON_4211.data.u8[1] = (datalayer.battery.status.voltage_dV & 0x00FF);
|
||||
|
||||
#ifdef SET_30K_OFFSET
|
||||
//Current (15.0)
|
||||
PYLON_4210.data.u8[2] = ((datalayer.battery.status.current_dA + 30000) >> 8);
|
||||
PYLON_4210.data.u8[3] = ((datalayer.battery.status.current_dA + 30000) & 0x00FF);
|
||||
PYLON_4211.data.u8[2] = ((datalayer.battery.status.current_dA + 30000) >> 8);
|
||||
PYLON_4211.data.u8[3] = ((datalayer.battery.status.current_dA + 30000) & 0x00FF);
|
||||
#else
|
||||
PYLON_4210.data.u8[2] = (datalayer.battery.status.current_dA >> 8);
|
||||
PYLON_4210.data.u8[3] = (datalayer.battery.status.current_dA & 0x00FF);
|
||||
PYLON_4211.data.u8[2] = (datalayer.battery.status.current_dA >> 8);
|
||||
PYLON_4211.data.u8[3] = (datalayer.battery.status.current_dA & 0x00FF);
|
||||
#endif
|
||||
|
||||
// BMS Temperature (We dont have BMS temp, send max cell voltage instead)
|
||||
PYLON_4210.data.u8[4] = ((datalayer.battery.status.temperature_max_dC + 1000) >> 8);
|
||||
PYLON_4210.data.u8[5] = ((datalayer.battery.status.temperature_max_dC + 1000) & 0x00FF);
|
||||
PYLON_4211.data.u8[4] = ((datalayer.battery.status.temperature_max_dC + 1000) >> 8);
|
||||
PYLON_4211.data.u8[5] = ((datalayer.battery.status.temperature_max_dC + 1000) & 0x00FF);
|
||||
|
||||
//Maxvoltage (eg 400.0V = 4000 , 16bits long) Discharge Cutoff Voltage
|
||||
PYLON_4220.data.u8[2] = (datalayer.battery.info.max_design_voltage_dV >> 8);
|
||||
PYLON_4220.data.u8[3] = (datalayer.battery.info.max_design_voltage_dV & 0x00FF);
|
||||
PYLON_4221.data.u8[2] = (datalayer.battery.info.max_design_voltage_dV >> 8);
|
||||
PYLON_4221.data.u8[3] = (datalayer.battery.info.max_design_voltage_dV & 0x00FF);
|
||||
PYLON_4220.data.u8[0] = (datalayer.battery.info.max_design_voltage_dV >> 8);
|
||||
PYLON_4220.data.u8[1] = (datalayer.battery.info.max_design_voltage_dV & 0x00FF);
|
||||
PYLON_4221.data.u8[0] = (datalayer.battery.info.max_design_voltage_dV >> 8);
|
||||
PYLON_4221.data.u8[1] = (datalayer.battery.info.max_design_voltage_dV & 0x00FF);
|
||||
|
||||
//Minvoltage (eg 300.0V = 3000 , 16bits long) Charge Cutoff Voltage
|
||||
PYLON_4220.data.u8[2] = (datalayer.battery.info.min_design_voltage_dV >> 8);
|
||||
PYLON_4220.data.u8[3] = (datalayer.battery.info.min_design_voltage_dV & 0x00FF);
|
||||
PYLON_4221.data.u8[2] = (datalayer.battery.info.min_design_voltage_dV >> 8);
|
||||
PYLON_4221.data.u8[3] = (datalayer.battery.info.min_design_voltage_dV & 0x00FF);
|
||||
|
||||
#ifdef SET_30K_OFFSET
|
||||
//Max ChargeCurrent
|
||||
PYLON_4220.data.u8[4] = ((max_charge_current + 30000) >> 8);
|
||||
PYLON_4220.data.u8[5] = ((max_charge_current + 30000) & 0x00FF);
|
||||
PYLON_4221.data.u8[4] = ((max_charge_current + 30000) >> 8);
|
||||
PYLON_4221.data.u8[5] = ((max_charge_current + 30000) & 0x00FF);
|
||||
|
||||
//Max DischargeCurrent
|
||||
PYLON_4220.data.u8[6] = ((30000 - max_discharge_current) >> 8);
|
||||
PYLON_4220.data.u8[7] = ((30000 - max_discharge_current) & 0x00FF);
|
||||
PYLON_4221.data.u8[6] = ((30000 - max_discharge_current) >> 8);
|
||||
PYLON_4221.data.u8[7] = ((30000 - max_discharge_current) & 0x00FF);
|
||||
#else
|
||||
//Max ChargeCurrent
|
||||
PYLON_4220.data.u8[4] = (max_charge_current >> 8);
|
||||
PYLON_4220.data.u8[5] = (max_charge_current & 0x00FF);
|
||||
PYLON_4221.data.u8[4] = (max_charge_current >> 8);
|
||||
PYLON_4221.data.u8[5] = (max_charge_current & 0x00FF);
|
||||
|
||||
//Max DishargeCurrent
|
||||
PYLON_4220.data.u8[6] = (max_discharge_current >> 8);
|
||||
PYLON_4220.data.u8[7] = (max_discharge_current & 0x00FF);
|
||||
PYLON_4221.data.u8[6] = (max_discharge_current >> 8);
|
||||
PYLON_4221.data.u8[7] = (max_discharge_current & 0x00FF);
|
||||
#endif
|
||||
|
||||
//Max cell voltage
|
||||
PYLON_4230.data.u8[0] = (datalayer.battery.status.cell_max_voltage_mV >> 8);
|
||||
PYLON_4230.data.u8[1] = (datalayer.battery.status.cell_max_voltage_mV & 0x00FF);
|
||||
PYLON_4231.data.u8[0] = (datalayer.battery.status.cell_max_voltage_mV >> 8);
|
||||
PYLON_4231.data.u8[1] = (datalayer.battery.status.cell_max_voltage_mV & 0x00FF);
|
||||
|
||||
//Min cell voltage
|
||||
PYLON_4230.data.u8[2] = (datalayer.battery.status.cell_min_voltage_mV >> 8);
|
||||
PYLON_4230.data.u8[3] = (datalayer.battery.status.cell_min_voltage_mV & 0x00FF);
|
||||
PYLON_4231.data.u8[2] = (datalayer.battery.status.cell_min_voltage_mV >> 8);
|
||||
PYLON_4231.data.u8[3] = (datalayer.battery.status.cell_min_voltage_mV & 0x00FF);
|
||||
|
||||
//Max temperature per cell
|
||||
PYLON_4240.data.u8[0] = (datalayer.battery.status.temperature_max_dC >> 8);
|
||||
PYLON_4240.data.u8[1] = (datalayer.battery.status.temperature_max_dC & 0x00FF);
|
||||
PYLON_4241.data.u8[0] = (datalayer.battery.status.temperature_max_dC >> 8);
|
||||
PYLON_4241.data.u8[1] = (datalayer.battery.status.temperature_max_dC & 0x00FF);
|
||||
|
||||
//Max/Min temperature per cell
|
||||
PYLON_4240.data.u8[2] = (datalayer.battery.status.temperature_min_dC >> 8);
|
||||
PYLON_4240.data.u8[3] = (datalayer.battery.status.temperature_min_dC & 0x00FF);
|
||||
PYLON_4240.data.u8[2] = (datalayer.battery.status.temperature_min_dC >> 8);
|
||||
PYLON_4240.data.u8[3] = (datalayer.battery.status.temperature_min_dC & 0x00FF);
|
||||
|
||||
//Max temperature per module
|
||||
PYLON_4270.data.u8[0] = (datalayer.battery.status.temperature_max_dC >> 8);
|
||||
PYLON_4270.data.u8[1] = (datalayer.battery.status.temperature_max_dC & 0x00FF);
|
||||
PYLON_4271.data.u8[0] = (datalayer.battery.status.temperature_max_dC >> 8);
|
||||
PYLON_4271.data.u8[1] = (datalayer.battery.status.temperature_max_dC & 0x00FF);
|
||||
|
||||
//Min temperature per module
|
||||
PYLON_4270.data.u8[2] = (datalayer.battery.status.temperature_min_dC >> 8);
|
||||
PYLON_4270.data.u8[3] = (datalayer.battery.status.temperature_min_dC & 0x00FF);
|
||||
PYLON_4271.data.u8[2] = (datalayer.battery.status.temperature_min_dC >> 8);
|
||||
PYLON_4271.data.u8[3] = (datalayer.battery.status.temperature_min_dC & 0x00FF);
|
||||
#endif
|
||||
|
||||
//Max/Min cell voltage
|
||||
PYLON_4230.data.u8[0] = (datalayer.battery.status.cell_max_voltage_mV >> 8);
|
||||
PYLON_4230.data.u8[1] = (datalayer.battery.status.cell_max_voltage_mV & 0x00FF);
|
||||
PYLON_4230.data.u8[2] = (datalayer.battery.status.cell_min_voltage_mV >> 8);
|
||||
PYLON_4230.data.u8[3] = (datalayer.battery.status.cell_min_voltage_mV & 0x00FF);
|
||||
|
||||
//Max/Min temperature per cell
|
||||
PYLON_4240.data.u8[0] = (datalayer.battery.status.temperature_max_dC >> 8);
|
||||
PYLON_4240.data.u8[1] = (datalayer.battery.status.temperature_max_dC & 0x00FF);
|
||||
PYLON_4240.data.u8[2] = (datalayer.battery.status.temperature_min_dC >> 8);
|
||||
PYLON_4240.data.u8[3] = (datalayer.battery.status.temperature_min_dC & 0x00FF);
|
||||
|
||||
//Max/Min temperature per module
|
||||
PYLON_4270.data.u8[0] = (datalayer.battery.status.temperature_max_dC >> 8);
|
||||
PYLON_4270.data.u8[1] = (datalayer.battery.status.temperature_max_dC & 0x00FF);
|
||||
PYLON_4270.data.u8[2] = (datalayer.battery.status.temperature_min_dC >> 8);
|
||||
PYLON_4270.data.u8[3] = (datalayer.battery.status.temperature_min_dC & 0x00FF);
|
||||
|
||||
//In case we run into any errors/faults, we can set charge / discharge forbidden
|
||||
if (datalayer.battery.status.bms_status == FAULT) {
|
||||
PYLON_4280.data.u8[0] = 0xAA;
|
||||
|
@ -243,7 +479,7 @@ void update_values_can_pylon() { //This function maps all the values fetched fr
|
|||
}
|
||||
}
|
||||
|
||||
void receive_can_pylon(CAN_frame_t rx_frame) {
|
||||
void receive_can_inverter(CAN_frame_t rx_frame) {
|
||||
switch (rx_frame.MsgID) {
|
||||
case 0x4200: //Message originating from inverter. Depending on which data is required, act accordingly
|
||||
if (rx_frame.data.u8[0] == 0x02) {
|
||||
|
@ -258,7 +494,20 @@ void receive_can_pylon(CAN_frame_t rx_frame) {
|
|||
}
|
||||
}
|
||||
|
||||
void send_can_inverter() {
|
||||
// No periodic sending, we only react on received can messages
|
||||
}
|
||||
|
||||
void send_setup_info() { //Ensemble information
|
||||
//Amount of cells
|
||||
PYLON_7320.data.u8[0] = datalayer.battery.info.number_of_cells;
|
||||
//Modules in series (not really how EV packs work, but let's map it to a reasonable Pylon value)
|
||||
PYLON_7320.data.u8[2] = (datalayer.battery.info.number_of_cells / 15);
|
||||
//Capacity in AH
|
||||
if (datalayer.battery.status.voltage_dV > 10) { //div0 safeguard
|
||||
PYLON_7320.data.u8[6] = (datalayer.battery.info.total_capacity_Wh / (datalayer.battery.status.voltage_dV / 10));
|
||||
}
|
||||
|
||||
#ifdef SEND_0
|
||||
ESP32Can.CANWriteFrame(&PYLON_7310);
|
||||
ESP32Can.CANWriteFrame(&PYLON_7320);
|
||||
|
|
|
@ -3,10 +3,8 @@
|
|||
#include "../include.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
|
||||
#define INVERTER_SELECTED
|
||||
#define CAN_INVERTER_SELECTED
|
||||
|
||||
void update_values_can_pylon();
|
||||
void receive_can_pylon(CAN_frame_t rx_frame);
|
||||
void send_system_data();
|
||||
void send_setup_info();
|
||||
|
||||
|
|
|
@ -5,8 +5,6 @@
|
|||
#include "../include.h"
|
||||
#include "../lib/mackelec-SerialDataLink/SerialDataLink.h"
|
||||
|
||||
#define INVERTER_SELECTED
|
||||
|
||||
void manageSerialLinkTransmitter();
|
||||
|
||||
#endif
|
||||
|
|
|
@ -103,24 +103,26 @@ static int16_t charge_current = 0;
|
|||
static int16_t temperature_average = 0;
|
||||
static uint16_t ampere_hours_remaining = 0;
|
||||
|
||||
void update_values_can_sma() { //This function maps all the values fetched from battery CAN to the correct CAN messages
|
||||
void update_values_can_inverter() { //This function maps all the values fetched from battery CAN to the correct CAN messages
|
||||
//Calculate values
|
||||
|
||||
if (datalayer.battery.status.voltage_dV > 10) { // Only update value when we have voltage available to avoid div0
|
||||
discharge_current =
|
||||
((datalayer.battery.status.max_discharge_power_W * 10) /
|
||||
datalayer.battery.status.voltage_dV); //Charge power in W , max volt in V+1decimal (P=UI, solve for I)
|
||||
discharge_current = (discharge_current * 10); //Value needs a decimal before getting sent to inverter (81.0A)
|
||||
charge_current =
|
||||
((datalayer.battery.status.max_charge_power_W * 10) /
|
||||
datalayer.battery.info.max_design_voltage_dV); //Charge power in W , max volt in V+1decimal (P=UI, solve for I)
|
||||
//The above calculation results in (30 000*10)/3700=81A
|
||||
datalayer.battery.status.voltage_dV); //Charge power in W , max volt in V+1decimal (P=UI, solve for I)
|
||||
charge_current = (charge_current * 10); //Value needs a decimal before getting sent to inverter (81.0A)
|
||||
}
|
||||
|
||||
if (charge_current > datalayer.battery.info.max_charge_amp_dA) {
|
||||
charge_current =
|
||||
datalayer.battery.info
|
||||
.max_charge_amp_dA; //Cap the value to the max allowed Amp. Some inverters cannot handle large values.
|
||||
}
|
||||
|
||||
discharge_current =
|
||||
((datalayer.battery.status.max_discharge_power_W * 10) /
|
||||
datalayer.battery.info.max_design_voltage_dV); //Charge power in W , max volt in V+1decimal (P=UI, solve for I)
|
||||
//The above calculation results in (30 000*10)/3700=81A
|
||||
discharge_current = (discharge_current * 10); //Value needs a decimal before getting sent to inverter (81.0A)
|
||||
if (discharge_current > datalayer.battery.info.max_discharge_amp_dA) {
|
||||
discharge_current =
|
||||
datalayer.battery.info
|
||||
|
@ -130,8 +132,10 @@ void update_values_can_sma() { //This function maps all the values fetched from
|
|||
temperature_average =
|
||||
((datalayer.battery.status.temperature_max_dC + datalayer.battery.status.temperature_min_dC) / 2);
|
||||
|
||||
if (datalayer.battery.status.voltage_dV > 10) { // Only update value when we have voltage available to avoid div0
|
||||
ampere_hours_remaining = ((datalayer.battery.status.remaining_capacity_Wh / datalayer.battery.status.voltage_dV) *
|
||||
100); //(WH[10000] * V+1[3600])*100 = 270 (27.0Ah)
|
||||
}
|
||||
|
||||
//Map values to CAN messages
|
||||
//Maxvoltage (eg 400.0V = 4000 , 16bits long)
|
||||
|
@ -226,7 +230,7 @@ void update_values_can_sma() { //This function maps all the values fetched from
|
|||
*/
|
||||
}
|
||||
|
||||
void receive_can_sma(CAN_frame_t rx_frame) {
|
||||
void receive_can_inverter(CAN_frame_t rx_frame) {
|
||||
switch (rx_frame.MsgID) {
|
||||
case 0x360: //Message originating from SMA inverter - Voltage and current
|
||||
//Frame0-1 Voltage
|
||||
|
@ -246,7 +250,7 @@ void receive_can_sma(CAN_frame_t rx_frame) {
|
|||
}
|
||||
}
|
||||
|
||||
void send_can_sma() {
|
||||
void send_can_inverter() {
|
||||
unsigned long currentMillis = millis();
|
||||
|
||||
// Send CAN Message every 100ms
|
||||
|
|
|
@ -3,13 +3,9 @@
|
|||
#include "../include.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
|
||||
#define INVERTER_SELECTED
|
||||
#define CAN_INVERTER_SELECTED
|
||||
|
||||
#define READY_STATE 0x03
|
||||
#define STOP_STATE 0x02
|
||||
|
||||
void update_values_can_sma();
|
||||
void send_can_sma();
|
||||
void receive_can_sma(CAN_frame_t rx_frame);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -161,24 +161,26 @@ Command2Battery command2Battery = RUN;
|
|||
enum InvInitState { SYSTEM_FREQUENCY, XPHASE_SYSTEM, BLACKSTART_OPERATION };
|
||||
InvInitState invInitState = SYSTEM_FREQUENCY;
|
||||
|
||||
void update_values_can_sma_tripower() { //This function maps all the values fetched from battery CAN to the inverter CAN
|
||||
void update_values_can_inverter() { //This function maps all the values fetched from battery CAN to the inverter CAN
|
||||
//Calculate values
|
||||
|
||||
if (datalayer.battery.status.voltage_dV > 10) { // Only update value when we have voltage available to avoid div0
|
||||
charge_current =
|
||||
((datalayer.battery.status.max_charge_power_W * 10) /
|
||||
datalayer.battery.info.max_design_voltage_dV); //Charge power in W , max volt in V+1decimal (P=UI, solve for I)
|
||||
//The above calculation results in (30 000*10)/3700=81A
|
||||
datalayer.battery.status.voltage_dV); //Charge power in W , max volt in V+1decimal (P=UI, solve for I)
|
||||
charge_current = (charge_current * 10); //Value needs a decimal before getting sent to inverter (81.0A)
|
||||
discharge_current =
|
||||
((datalayer.battery.status.max_discharge_power_W * 10) /
|
||||
datalayer.battery.status.voltage_dV); //Charge power in W , max volt in V+1decimal (P=UI, solve for I)
|
||||
discharge_current = (discharge_current * 10); //Value needs a decimal before getting sent to inverter (81.0A)
|
||||
}
|
||||
|
||||
if (charge_current > datalayer.battery.info.max_charge_amp_dA) {
|
||||
charge_current =
|
||||
datalayer.battery.info
|
||||
.max_charge_amp_dA; //Cap the value to the max allowed Amp. Some inverters cannot handle large values.
|
||||
}
|
||||
|
||||
discharge_current =
|
||||
((datalayer.battery.status.max_discharge_power_W * 10) /
|
||||
datalayer.battery.info.max_design_voltage_dV); //Charge power in W , max volt in V+1decimal (P=UI, solve for I)
|
||||
//The above calculation results in (30 000*10)/3700=81A
|
||||
discharge_current = (discharge_current * 10); //Value needs a decimal before getting sent to inverter (81.0A)
|
||||
if (discharge_current > datalayer.battery.info.max_discharge_amp_dA) {
|
||||
discharge_current =
|
||||
datalayer.battery.info
|
||||
|
@ -328,7 +330,7 @@ void update_values_can_sma_tripower() { //This function maps all the values fet
|
|||
//SMA_018.data.u8[7] = BatteryName;
|
||||
}
|
||||
|
||||
void receive_can_sma_tripower(CAN_frame_t rx_frame) {
|
||||
void receive_can_inverter(CAN_frame_t rx_frame) {
|
||||
switch (rx_frame.MsgID) {
|
||||
case 0x00D: //Inverter Measurements
|
||||
break;
|
||||
|
@ -347,7 +349,7 @@ void receive_can_sma_tripower(CAN_frame_t rx_frame) {
|
|||
}
|
||||
}
|
||||
|
||||
void send_can_sma_tripower() {
|
||||
void send_can_inverter() {
|
||||
unsigned long currentMillis = millis();
|
||||
|
||||
// Send CAN Message every 500ms
|
||||
|
|
|
@ -3,11 +3,8 @@
|
|||
#include "../include.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
|
||||
#define INVERTER_SELECTED
|
||||
#define CAN_INVERTER_SELECTED
|
||||
|
||||
void update_values_can_sma_tripower();
|
||||
void send_can_sma_tripower();
|
||||
void receive_can_sma_tripower(CAN_frame_t rx_frame);
|
||||
void send_tripower_init();
|
||||
|
||||
#endif
|
||||
|
|
|
@ -280,7 +280,7 @@ CAN_frame_t SOFAR_7C0 = {.FIR = {.B =
|
|||
.MsgID = 0x7C0,
|
||||
.data = {0x00, 0x00, 0x00, 0x04, 0x00, 0x04, 0x80, 0x00}};
|
||||
|
||||
void update_values_can_sofar() { //This function maps all the values fetched from battery CAN to the correct CAN messages
|
||||
void update_values_can_inverter() { //This function maps all the values fetched from battery CAN to the correct CAN messages
|
||||
|
||||
//Maxvoltage (eg 400.0V = 4000 , 16bits long) Charge Cutoff Voltage
|
||||
SOFAR_351.data.u8[0] = (datalayer.battery.info.max_design_voltage_dV >> 8);
|
||||
|
@ -308,7 +308,7 @@ void update_values_can_sofar() { //This function maps all the values fetched fr
|
|||
SOFAR_356.data.u8[3] = (datalayer.battery.status.temperature_max_dC & 0x00FF);
|
||||
}
|
||||
|
||||
void receive_can_sofar(CAN_frame_t rx_frame) {
|
||||
void receive_can_inverter(CAN_frame_t rx_frame) {
|
||||
switch (rx_frame.MsgID) { //In here we need to respond to the inverter. TODO: make logic
|
||||
case 0x605:
|
||||
//frame1_605 = rx_frame.data.u8[1];
|
||||
|
@ -323,7 +323,7 @@ void receive_can_sofar(CAN_frame_t rx_frame) {
|
|||
}
|
||||
}
|
||||
|
||||
void send_can_sofar() {
|
||||
void send_can_inverter() {
|
||||
unsigned long currentMillis = millis();
|
||||
// Send 100ms CAN Message
|
||||
if (currentMillis - previousMillis100 >= INTERVAL_100_MS) {
|
||||
|
|
|
@ -3,10 +3,6 @@
|
|||
#include "../include.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
|
||||
#define INVERTER_SELECTED
|
||||
|
||||
void update_values_can_sofar();
|
||||
void send_can_sofar();
|
||||
void receive_can_sofar(CAN_frame_t rx_frame);
|
||||
#define CAN_INVERTER_SELECTED
|
||||
|
||||
#endif
|
||||
|
|
|
@ -121,7 +121,7 @@ void CAN_WriteFrame(CAN_frame_t* tx_frame) {
|
|||
#endif
|
||||
}
|
||||
|
||||
void update_values_can_solax() { //This function maps all the values fetched from battery CAN to the correct CAN messages
|
||||
void update_values_can_inverter() { //This function maps all the values fetched from battery CAN to the correct CAN messages
|
||||
// If not receiveing any communication from the inverter, open contactors and return to battery announce state
|
||||
if (millis() - LastFrameTime >= SolaxTimeout) {
|
||||
datalayer.system.status.inverter_allows_contactor_closing = false;
|
||||
|
@ -136,28 +136,38 @@ void update_values_can_solax() { //This function maps all the values fetched fr
|
|||
((datalayer.battery.status.temperature_max_dC + datalayer.battery.status.temperature_min_dC) / 2);
|
||||
|
||||
//datalayer.battery.status.max_charge_power_W (30000W max)
|
||||
if (datalayer.battery.status.reported_soc > 9999) //99.99%
|
||||
{ //Additional safety incase SOC% is 100, then do not charge battery further
|
||||
if (datalayer.battery.status.reported_soc > 9999) { // 99.99%
|
||||
// Additional safety incase SOC% is 100, then do not charge battery further
|
||||
max_charge_rate_amp = 0;
|
||||
} else { //We can pass on the battery charge rate (in W) to the inverter (that takes A)
|
||||
} else { // We can pass on the battery charge rate (in W) to the inverter (that takes A)
|
||||
if (datalayer.battery.status.max_charge_power_W >= 30000) {
|
||||
max_charge_rate_amp = 75; //Incase battery can take over 30kW, cap value to 75A
|
||||
} else { //Calculate the W value into A
|
||||
max_charge_rate_amp = 75; // Incase battery can take over 30kW, cap value to 75A
|
||||
} else { // Calculate the W value into A
|
||||
if (datalayer.battery.status.voltage_dV > 10) {
|
||||
max_charge_rate_amp =
|
||||
(datalayer.battery.status.max_charge_power_W / (datalayer.battery.status.voltage_dV * 0.1)); // P/U = I
|
||||
datalayer.battery.status.max_charge_power_W / (datalayer.battery.status.voltage_dV * 0.1); // P/U=I
|
||||
} else { // We avoid dividing by 0 and crashing the board
|
||||
// If we have no voltage, something has gone wrong, do not allow charging
|
||||
max_charge_rate_amp = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//datalayer.battery.status.max_discharge_power_W (30000W max)
|
||||
if (datalayer.battery.status.reported_soc < 100) //1.00%
|
||||
{ //Additional safety incase SOC% is below 1, then do not charge battery further
|
||||
if (datalayer.battery.status.reported_soc < 100) { // 1.00%
|
||||
// Additional safety in case SOC% is below 1, then do not discharge battery further
|
||||
max_discharge_rate_amp = 0;
|
||||
} else { //We can pass on the battery discharge rate to the inverter
|
||||
} else { // We can pass on the battery discharge rate to the inverter
|
||||
if (datalayer.battery.status.max_discharge_power_W >= 30000) {
|
||||
max_discharge_rate_amp = 75; //Incase battery can be charged with over 30kW, cap value to 75A
|
||||
} else { //Calculate the W value into A
|
||||
max_discharge_rate_amp = 75; // Incase battery can be charged with over 30kW, cap value to 75A
|
||||
} else { // Calculate the W value into A
|
||||
if (datalayer.battery.status.voltage_dV > 10) {
|
||||
max_discharge_rate_amp =
|
||||
(datalayer.battery.status.max_discharge_power_W / (datalayer.battery.status.voltage_dV * 0.1)); // P/U = I
|
||||
datalayer.battery.status.max_discharge_power_W / (datalayer.battery.status.voltage_dV * 0.1); // P/U=I
|
||||
} else { // We avoid dividing by 0 and crashing the board
|
||||
// If we have no voltage, something has gone wrong, do not allow discharging
|
||||
max_discharge_rate_amp = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -192,8 +202,8 @@ void update_values_can_solax() { //This function maps all the values fetched fr
|
|||
SOLAX_1873.data.u8[3] = (datalayer.battery.status.current_dA >> 8);
|
||||
SOLAX_1873.data.u8[4] = (uint8_t)(datalayer.battery.status.reported_soc / 100); //SOC (100.00%)
|
||||
//SOLAX_1873.data.u8[5] = //Seems like this is not required? Or shall we put SOC decimals here?
|
||||
SOLAX_1873.data.u8[6] = (uint8_t)(capped_remaining_capacity_Wh / 100);
|
||||
SOLAX_1873.data.u8[7] = ((capped_remaining_capacity_Wh / 100) >> 8);
|
||||
SOLAX_1873.data.u8[6] = (uint8_t)(capped_remaining_capacity_Wh / 10);
|
||||
SOLAX_1873.data.u8[7] = ((capped_remaining_capacity_Wh / 10) >> 8);
|
||||
|
||||
//BMS_CellData
|
||||
SOLAX_1874.data.u8[0] = (int8_t)datalayer.battery.status.temperature_max_dC;
|
||||
|
@ -237,7 +247,11 @@ void update_values_can_solax() { //This function maps all the values fetched fr
|
|||
SOLAX_1801.data.u8[4] = 1;
|
||||
}
|
||||
|
||||
void receive_can_solax(CAN_frame_t rx_frame) {
|
||||
void send_can_inverter() {
|
||||
// No periodic sending used on this protocol, we react only on incoming CAN messages!
|
||||
}
|
||||
|
||||
void receive_can_inverter(CAN_frame_t rx_frame) {
|
||||
if (rx_frame.MsgID == 0x1871 && rx_frame.data.u8[0] == (0x01) ||
|
||||
rx_frame.MsgID == 0x1871 && rx_frame.data.u8[0] == (0x02)) {
|
||||
LastFrameTime = millis();
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
#include "../lib/pierremolinaro-acan2515/ACAN2515.h"
|
||||
|
||||
#define INVERTER_SELECTED
|
||||
#define CAN_INVERTER_SELECTED
|
||||
|
||||
extern ACAN2515 can;
|
||||
|
||||
|
@ -18,6 +18,5 @@ extern ACAN2515 can;
|
|||
#define FAULT_SOLAX 3
|
||||
#define UPDATING_FW 4
|
||||
|
||||
void update_values_can_solax();
|
||||
void receive_can_solax(CAN_frame_t rx_frame);
|
||||
#endif
|
||||
|
|
674
Software/src/lib/YiannisBourkelis-Uptime-Library/LICENSE
Normal file
674
Software/src/lib/YiannisBourkelis-Uptime-Library/LICENSE
Normal file
|
@ -0,0 +1,674 @@
|
|||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
103
Software/src/lib/YiannisBourkelis-Uptime-Library/README.md
Normal file
103
Software/src/lib/YiannisBourkelis-Uptime-Library/README.md
Normal file
|
@ -0,0 +1,103 @@
|
|||
# Uptime Library
|
||||
|
||||
With the uptime library for Arduino boards and compatible systems you can read the time passed since device startup, without the 49 days overflow limitation of the millis() function.
|
||||
|
||||
# Usage
|
||||
|
||||
#### Example 1: [Device Uptime](https://github.com/YiannisBourkelis/Uptime-Library/tree/master/examples/DeviceUptime "Device Uptime")
|
||||
```cpp
|
||||
#include "uptime_formatter.h"
|
||||
|
||||
void setup() {
|
||||
// connect at 115200 so we can read the uptime fast enough
|
||||
Serial.begin(115200);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
//uptime_formatter::get_uptime() returns a string
|
||||
//containing the total device uptime since startup in days, hours, minutes and seconds
|
||||
Serial.println("up " + uptime_formatter::getUptime());
|
||||
|
||||
//wait 1 second
|
||||
delay(1000);
|
||||
}
|
||||
```
|
||||
|
||||
#### Output:
|
||||
```
|
||||
up 0 days, 0 hours, 0 minutes, 56 seconds
|
||||
up 0 days, 0 hours, 0 minutes, 57 seconds
|
||||
up 0 days, 0 hours, 0 minutes, 58 seconds
|
||||
up 0 days, 0 hours, 0 minutes, 59 seconds
|
||||
up 0 days, 0 hours, 1 minutes, 0 seconds
|
||||
up 0 days, 0 hours, 1 minutes, 1 seconds
|
||||
up 0 days, 0 hours, 1 minutes, 2 seconds
|
||||
up 0 days, 0 hours, 1 minutes, 3 seconds
|
||||
up 0 days, 0 hours, 1 minutes, 4 seconds
|
||||
up 0 days, 0 hours, 1 minutes, 5 seconds
|
||||
up 0 days, 0 hours, 1 minutes, 6 seconds
|
||||
up 0 days, 0 hours, 1 minutes, 7 seconds
|
||||
up 0 days, 0 hours, 1 minutes, 8 seconds
|
||||
up 0 days, 0 hours, 1 minutes, 9 seconds
|
||||
```
|
||||
|
||||
#### Example 2: [Device Uptime Custom Formatting](https://github.com/YiannisBourkelis/Uptime-Library/tree/master/examples/DeviceUptimeCustomFormatting "Device Uptime Custom Formatting")
|
||||
```cpp
|
||||
#include "uptime.h"
|
||||
|
||||
void setup() {
|
||||
// connect at 115200 so we can read the uptime fast enough
|
||||
Serial.begin(115200);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
//If you do not want to use the string library
|
||||
//you can get the total uptime variables
|
||||
//and format the output the way you want.
|
||||
|
||||
//First call calculate_uptime() to calculate the uptime
|
||||
//and then read the uptime variables.
|
||||
uptime::calculateUptime();
|
||||
|
||||
Serial.print("days: ");
|
||||
Serial.println(uptime::getDays());
|
||||
|
||||
Serial.print("hours: ");
|
||||
Serial.println(uptime::getHours());
|
||||
|
||||
Serial.print("minutes: ");
|
||||
Serial.println(uptime::getMinutes());
|
||||
|
||||
Serial.print("seconds: ");
|
||||
Serial.println(uptime::getSeconds());
|
||||
|
||||
Serial.print("milliseconds: ");
|
||||
Serial.println(uptime::getMilliseconds());
|
||||
|
||||
Serial.print("\n");
|
||||
|
||||
//wait 1 second
|
||||
delay(1000);
|
||||
}
|
||||
```
|
||||
|
||||
#### Output:
|
||||
```
|
||||
days: 0
|
||||
hours: 0
|
||||
minutes: 23
|
||||
seconds: 16
|
||||
milliseconds: 23
|
||||
|
||||
days: 0
|
||||
hours: 0
|
||||
minutes: 23
|
||||
seconds: 17
|
||||
milliseconds: 23
|
||||
|
||||
days: 0
|
||||
hours: 0
|
||||
minutes: 23
|
||||
seconds: 18
|
||||
milliseconds: 23
|
||||
```
|
|
@ -0,0 +1,35 @@
|
|||
/* ***********************************************************************
|
||||
* Uptime library for Arduino boards and compatible systems
|
||||
* (C) 2019 by Yiannis Bourkelis (https://github.com/YiannisBourkelis/)
|
||||
*
|
||||
* This file is part of Uptime library for Arduino boards and compatible systems
|
||||
*
|
||||
* Uptime library for Arduino boards and compatible systems is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Uptime library for Arduino boards and compatible systems is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Uptime library for Arduino boards and compatible systems. If not, see <http://www.gnu.org/licenses/>.
|
||||
* ***********************************************************************/
|
||||
|
||||
#include "uptime_formatter.h"
|
||||
|
||||
void setup() {
|
||||
// connect at 115200 so we can read the uptime fast enough
|
||||
Serial.begin(115200);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
//uptime_formatter::get_uptime() returns a string
|
||||
//containing the total device uptime since startup in days, hours, minutes and seconds
|
||||
Serial.println("up " + uptime_formatter::getUptime());
|
||||
|
||||
//wait 1 second
|
||||
delay(1000);
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
/* ***********************************************************************
|
||||
* Uptime library for Arduino boards and compatible systems
|
||||
* (C) 2019 by Yiannis Bourkelis (https://github.com/YiannisBourkelis/)
|
||||
*
|
||||
* This file is part of Uptime library for Arduino boards and compatible systems
|
||||
*
|
||||
* Uptime library for Arduino boards and compatible systems is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Uptime library for Arduino boards and compatible systems is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Uptime library for Arduino boards and compatible systems. If not, see <http://www.gnu.org/licenses/>.
|
||||
* ***********************************************************************/
|
||||
|
||||
#include "uptime.h"
|
||||
|
||||
void setup() {
|
||||
// connect at 115200 so we can read the uptime fast enough
|
||||
Serial.begin(115200);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
//If you do not want to use the string library
|
||||
//you can get the total uptime variables
|
||||
//and format the output the way you want.
|
||||
|
||||
//First call calculate_uptime() to calculate the uptime
|
||||
//and then read the uptime variables.
|
||||
uptime::calculateUptime();
|
||||
|
||||
Serial.print("days: ");
|
||||
Serial.println(uptime::getDays());
|
||||
|
||||
Serial.print("hours: ");
|
||||
Serial.println(uptime::getHours());
|
||||
|
||||
Serial.print("minutes: ");
|
||||
Serial.println(uptime::getMinutes());
|
||||
|
||||
Serial.print("seconds: ");
|
||||
Serial.println(uptime::getSeconds());
|
||||
|
||||
Serial.print("milliseconds: ");
|
||||
Serial.println(uptime::getMilliseconds());
|
||||
|
||||
Serial.print("\n");
|
||||
|
||||
//wait 1 second
|
||||
delay(1000);
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
name=Uptime Library
|
||||
version=1.0.0
|
||||
author=Yiannis Bourkelis
|
||||
maintainer=Yiannis Bourkelis
|
||||
sentence=Uptime library for Arduino boards and compatible systems
|
||||
paragraph=Easily read the uptime since device startup, in days, hours, minutes and milliseconds, without the 49 days overflow limitation of the millis() function.
|
||||
category=Timing
|
||||
url=https://github.com/YiannisBourkelis/Uptime-Library
|
||||
architectures=*
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue